/**
 *  Component manager that handles ports and component creation
 *  
 *  @author     ETSI / STF424
 *  @version    $URL$
 *              $Id$
 *
 */
package org.etsi.its.adapter;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Observer;
import java.util.logging.Logger;

import org.etsi.common.ITuple;
import org.etsi.common.Tuple;
import org.etsi.its.adapter.ports.IObservable;
import org.etsi.its.adapter.ports.IPort;
import org.etsi.ttcn.tri.TriComponentId;
import org.etsi.ttcn.tri.TriPortId;

/**
 * Component manager that handles ports and component creation
 */
public class ComponentMgr implements IComponentMgr {

	/**
	 * Module version
	 */
	public static final String Version = "1.0.0.0";

	/**
	 * Logger instance
	 */
	private final static Logger _logger = Logger.getLogger("org.etsi.its");

	/** 
	 * Association Component ID <-> Component reference
	 */
	private LinkedHashMap<String, TriComponentId> _mapCompNameToTriComp;

	/** 
	 * Association Component ID <-> { (TTCN-3 Port reference / SUT Port reference) }
	 */
	private LinkedHashMap<String, Map<String, ITuple<TriPortId, IPort>>> _mapTriPortToTuple;

	/** 
	 * Used to add Observer object
	 */
	private TestAdapter _adapter;

	/** 
	 * Constructor
	 * @param  adapter TestAdapter reference, required for Observer/Observables pattern
	 */
	public ComponentMgr(final TestAdapter adapter) {
		_logger.entering("ComponentMgr", "Constructor", String.format("version:%s", Version));

		_adapter = adapter;
		_mapCompNameToTriComp = new LinkedHashMap<String, TriComponentId>();
		_mapTriPortToTuple = new LinkedHashMap<String, Map<String, ITuple<TriPortId, IPort>>>();
	}

	/** 
	 * Adds a new component
	 * If the component was already added, nothing is done
	 * @param  component   The component to add
	 */
	@Override
	public void addComponent(TriComponentId component) {
		_logger.entering("ComponentMgr", "addComponent", String.format("%s", component.getComponentId()));

		// Sanity check
		if(component == null) {
			_logger.severe("Wrong parameter");
			return;
		}
		
		if(!_mapCompNameToTriComp.containsKey(component.getComponentId())) {
			_logger.info(String.format("Component '%s' does not exist", component.getComponentId()));
			// Create an entry in the list of Component
			_mapCompNameToTriComp.put(component.getComponentId(), component); 
			if(!_mapTriPortToTuple.containsKey(component.getComponentId())) {
			 // Create an entry in the list of Component/Ports
				_mapTriPortToTuple.put(component.getComponentId(), new LinkedHashMap<String, ITuple<TriPortId, IPort>>()); 
			}
		}
	} // End of method addComponent

	/**
	 * Adds a new port to the specified component
	 * @param  component   The component reference
	 * @param  portname    The port name
	 * @param  port        The port to add
	 */
	@Override
	public void addPort(final String componentName, final TriPortId ttcnPort, final IPort port) {
		_logger.entering("ComponentMgr", "addPort", String.format("%s - %s - %s", componentName, ttcnPort.getPortName(), port.toString()));

		// Sanity checks
		if(componentName.isEmpty() || (ttcnPort == null) || (port == null)) {
			_logger.severe("Wrong parameters");
			return;
		}
		if(!_mapCompNameToTriComp.containsKey(componentName)) {
			_logger.severe("Unknown component");
			return;
		}
		if(!_mapTriPortToTuple.containsKey(componentName)) {
			_logger.severe("No port list entry");
			return;
		}
		

		Map<String, ITuple<TriPortId, IPort>> portItem = _mapTriPortToTuple.get(componentName);
		if(!portItem.containsKey(ttcnPort.getPortName())) {
			portItem.put(ttcnPort.getPortName(), new Tuple<TriPortId, IPort>(ttcnPort, port));
			((IObservable)port).addObserver((Observer) _adapter);
			_mapTriPortToTuple.put(componentName, portItem);
			_logger.info(String.format("Port added: %s - %s - %s", componentName, _mapTriPortToTuple.get(componentName).get(ttcnPort.getPortName()).getA().getPortName(), _mapTriPortToTuple.get(componentName).get(ttcnPort.getPortName()).getB().toString()));
		}
	}

	/** 
	 * Gets the component reference from its name
	 * @param  componentName   The component name
	 * @return The component reference if the component exists, null otherwise
	 */
	@Override
	public TriComponentId getComponent(String componentName) {
		_logger.entering("ComponentMgr", "getComponent", componentName);

		// Sanity checks
		if(componentName.isEmpty()) {
			_logger.severe("Wrong parameters");
			return null;
		}

		return _mapCompNameToTriComp.get(componentName);
	}

	/** 
	 * Retrieves the TTCN-3 port identifier (TriPortId) from component/port names
	 * @param  componentName   The component reference
	 * @param  portName        The port name
	 * @return The TTCN-3 port identifier if the component and the port exists, null otherwise
	 */
	@Override
	public TriPortId getPortId(String componentName, String portName) {
		_logger.entering("ComponentMgr", "getPortId", String.format("%s - %s", componentName, portName));

		// Sanity checks
		if(componentName.isEmpty() || portName.isEmpty()) {
			_logger.severe("Wrong parameters");
			return null;
		}
		if(!_mapCompNameToTriComp.containsKey(componentName)) {
			_logger.severe("Unknown component");
			return null;
		}
		if(!_mapTriPortToTuple.containsKey(componentName)) {
			_logger.severe("No port list entry");
			return null;
		}

		Map<String, ITuple<TriPortId, IPort>> portItem = _mapTriPortToTuple.get(componentName);
		if(!portItem.containsKey(portName)) {
			return null;
		}
		
		ITuple<TriPortId, IPort> item = portItem.get(portName);
		return item.getA();
	}

	/** 
	 * Retrieves the test adapter port identifier (Port or IAdapterPort) from component/port names
	 * @param  componentName   The component owner
	 * @return The port reference if the component and the port exists, null otherwise
	 * 
	 * @see Port
	 * @see IAdapterPort
	 */
	@Override
	public IPort getPort(String componentName, String portName) {
		_logger.entering("ComponentMgr", "getPort", String.format("%s - %s", componentName, portName));

		// Sanity checks
		if(componentName.isEmpty() || portName.isEmpty()) {
			_logger.severe("Wrong parameters");
			return null;
		}
		if(!_mapCompNameToTriComp.containsKey(componentName)) {
			_logger.severe("Unknown component");
			return null;
		}
		if(!_mapTriPortToTuple.containsKey(componentName)) {
			_logger.severe("No port list entry");
			return null;
		}

		Map<String, ITuple<TriPortId, IPort>> portItem = _mapTriPortToTuple.get(componentName);
		if(!portItem.containsKey(portName)) {
			return null;
		}
		
		ITuple<TriPortId, IPort> item = portItem.get(portName);
		return item.getB();
	}

	/**
	 * Removes the specified component
	 * 
	 * Note that after the port removal, if the component has no more port, it will be removed also
	 * 
	 * @param  component   The component reference
	 */
	@Override
	public void removeComponent(String componentName) {
		_logger.entering("ComponentMgr", "removeComponent", componentName);

		removeAllPorts();
	}

	/** 
	 * Removes the specified port
	 * 
	 * Note that all ports attached to this component will be removed also
	 * 
	 * @param  componentName   The component name to remove
	 */
	@Override
	public void removePort(String componentName, String portName) {
		_logger.entering("ComponentMgr", "removePort", String.format("%s - %s", componentName, portName));

		// Sanity checks
		if(componentName.isEmpty() || portName.isEmpty()) {
			_logger.severe("Wrong parameters");
			return;
		}
		if(!_mapCompNameToTriComp.containsKey(componentName)) {
			_logger.severe("Unknown component");
			return;
		}
		if(!_mapTriPortToTuple.containsKey(componentName)) {
			_logger.severe("No port list entry");
			return;
		}
		
		Map<String, ITuple<TriPortId, IPort>> portItem = _mapTriPortToTuple.get(componentName);
		if(!portItem.containsKey(portName)) {
			return;
		}
		// Remove Observers
		((IObservable)portItem.get(portName).getB()).deleteObservers();

		// Call dispose 
		_logger.info(String.format("Call dispose for [%s - %s]", componentName, portName));
		((IPort)portItem.get(portName).getB()).dispose();

		// Remove item
		portItem.remove(portName);
		if(portItem.size() != 0) {
			_mapTriPortToTuple.put(componentName, portItem);
		} else {
			_mapTriPortToTuple.remove(componentName);
		}
		if(_mapTriPortToTuple.isEmpty()) {
			_mapTriPortToTuple.clear();
			
			_logger.info(String.format("Remove component %s - %s", componentName, portName));
			_mapCompNameToTriComp.remove(componentName);
			if(_mapCompNameToTriComp.isEmpty()) {
				_mapCompNameToTriComp.clear();
			}
		}
	}
	
	/** 
	 * Removes all ports.
	 */
	@Override
	public void removeAllPorts() {
		_logger.entering("ComponentMgr", "removeAllPorts");
		
		for(Object componentName : _mapTriPortToTuple.keySet().toArray()) {
			Map<String, ITuple<TriPortId, IPort>> portItem = _mapTriPortToTuple.get(componentName);
			for(Object portName : portItem.keySet().toArray()) {
				removePort((String)componentName, (String)portName);
			}
		}
	}

} // End of class ComponentMgr
