/**
 * 
 */
package ucd.rubicon.proxy;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import ucd.rubicon.network.RubiconMessageUpdates;
import ucd.rubicon.network.RubiconAddress;
import ucd.rubicon.network.RubiconMessageJoin;


/**
 * Repository of devices connected to the RUBICON Proxy through the joining 
 * Holds node specification information
 * detailing sensing and actuator capabilities of the devices. Thread-safe device addition
 * and removal is regulated through <code>synchronized</code> methods.
 * 
 * 
 * @author Davide Bacciu - UNIPI (<a href="mailto:bacciu@di.unipi.it">Contact</a>)
 * @version %I%, %G%
 * 
 * 
 **/
public class DeviceRepository {
	
	//The private map of devices
	private Map<RubiconAddress, NodeInformation> repository = new  ConcurrentHashMap<RubiconAddress, NodeInformation>();
	
	public DeviceRepository() {
	}
		
	/**
	 * Service class describing the current state of the device 
	 *
	 * @see generics.DeviceRepository
	 */
	public static enum DeviceState{
		/**
		 * Denotes a device with a pending connection request  
		 */
		CONNECTING,
		/**
		 * Denotes a device with a pending disconnection request  
		 */
		DISCONNECTING,
		/**
		 * Denotes a connected device  
		 */
		CONNECTED,
	}	
	
	/**
	 * Thread-safe addition of a device to the repository. The device is assigned the ID
	 * passed as argument and is associated to the specification information.
	 * The state of the device is set to {@link DeviceState#CONNECTED} 
	 *  
	 * @param networkAddress Rubicon Address for the device
	 * @param spec Specification of the device capabilities
	 * 
	 * @see DeviceRepository.DeviceState
	 * @see NodeInformation
	 */
	public synchronized void addDevice(RubiconMessageJoin messageJoin) {
		NodeInformation d = new NodeInformation(messageJoin);		
		d.state = DeviceState.CONNECTED;
		repository.put(messageJoin.getSender(), d);
	}
	
	/**
	 * Thread-safe removal of a device from the repository. 
	 *  
	 */
	public synchronized void removeDevice(RubiconAddress address){
		repository.remove(address);
	}
	
	/**
	 * Sets the device state to one of the {@link DeviceState}. 
	 * Thread-safe implementation.
	 *  
	 * @param nodeID Identifier of the device
	 * @param state New device state
	 * 
	 * @see DeviceRepository.DeviceState
	 */
	public synchronized void setState(RubiconAddress address, DeviceState state) {
		NodeInformation nodeInfo = repository.get(address);
		if (nodeInfo != null) {
			nodeInfo.state = state;
		}		
	}
	
		
	/**
	 * Retrieves the current runtime information for a device in the repository.
	 * 
	 * @param address Rubicon address of the device
	 * @return the current NodeInformation for the device
	 */	
	public synchronized NodeInformation getNodeInformation(RubiconAddress address) {
		return repository.get(address);
	}
	
	/**
	 * Retrieves the current runtime information for a device in the repository.
	 * 
	 * @param address Rubicon address of the device
	 * @return the current RuntimeNodeInformation for the device
	 */	
	public synchronized NodeInformation updateNode(RubiconMessageUpdates msgUpdate) {
		NodeInformation node = repository.get(msgUpdate.getSender());	
		if (node == null) {
			System.out.println("DeviceRepository: updateNode(): received msg from a not joined node. THIS SHOULD NOT HAPPEN!");
			// TODO: Log and ask Claudio, will nodes send only one join message?
			// what if I miss it and start receive sensory updates?
			// for the moment do nothing and returns
			// this node will be picked up when the node sends its next join message
			return null;
		}
		node.update(msgUpdate);
		return node;
	}
	
	
	public void print() {
		System.out.println("REPOSITORY");
		System.out.println("-----------------------------------");
		for (RubiconAddress address:repository.keySet()) {
			NodeInformation node = repository.get(address);
			Properties resources = node.nodeSpec.getResources();
			System.out.println(" |_node "+node.nodeSpec.getIsland_addr()+"."+node.nodeSpec.getAddr());
			for (Object key : resources.keySet()) {
				String resourceName = (String)key;
				String resourceType = (String)resources.get(key);
				System.out.println("   |__"+resourceName+" :"+resourceType);
			}			
		}
		
	}

	public synchronized Collection<NodeInformation> getNodes() {
		return repository.values();
	}
	
}
