package de.tu_darmstadt.tk.SmartHomeNetworkSim.core;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.util.Pair;

/**
 * Protocol according to which the packages are created. Can contain different
 * Roles and multiple participating SmartDevice(Port)s.
 * 
 * @author Andreas T. Meyer-Berg
 */
public interface Protocol {
	/*
	 * Topology options
	 */
	/**
	 * Every Port/Device is connected to every other Port/Device
	 */
	public static final byte FULLY_CONNECTED = 0;
	
	/**
	 * Every Port/Device is connected to the Device of Role 0. (Has to be exactly one device of Role 0)
	 */
	public static final byte STAR = 1;
	
	/**
	 * Custom topology, getTopology has to be overwritten.
	 */
	public static final byte CUSTOM = 127;
	
	/**
	 * Generates the next packets<br>
	 * If {@code port==null} the terminating packets shall be sent.<br>
	 * This method should update port.setLastTrigger.<br>
	 * 
	 * @param port SmartDevice(Port) which sends the package, null if terminating packets shall be sent.
	 * @param timestep
	 *            Time the package should be sent, in System.currentTimeMillis
	 * @param packetLost True if the packet was lost
	 * @return next Packet, which was sent
	 */
	public Collection<Packet> generateNextPackets(Port port, long timestep, boolean packetLost);

	/**
	 * Returns the number of different roles the participating SmartDevice(Port)s
	 * could have
	 * 
	 * @return number of different roles
	 */
	public default int getNumberOfRoles(){
		return getRoles() == null ? 0 : getRoles().length;
	}

	/**
	 * Returns the textual representation of the different Roles. The Array
	 * should contain {@code NumberOfRoles} Strings. {@code Array[role]} should be
	 * a human-readable String representation of the Role at its position
	 * {@code role}.
	 * 
	 * @return String representations of the roles
	 */
	public String[] getRoles();

	/**
	 * Returns all SmartDevice(Port)s of the given Role. Returns {@code null}, if the role
	 * number was invalid.
	 * 
	 * @param role
	 *            Position of the role in {@code getNumberOfRoles}
	 * @return SmartDevices of Role with index {@code role}
	 */
	public Collection<Port> getDevicesWithRole(int role);

	/**
	 * Returns the role of the given device, returns -1, if the Device is not part of the protocol
	 * 
	 * @param device device which roles should be calculated
	 * @return role of the device, -1 if it has no role
	 */
	public default int getRoleOfDevice(Port device){
		if(device == null)
			return -1;
		for(int i = 0; i<getNumberOfRoles(); i++)
			if(getDevicesWithRole(i).contains(device))
				return i;
		return -1;
	}
	
	/**
	 * Adds a new SmartDevice to the role, returns {@code true} if it was
	 * assigned successfully, {@code false} if it wasn't. (Either invalid role
	 * number or maximum number of devices for the role reached)
	 * 
	 * @param device
	 *            SmartDevice(Port) that should be assigned to the given role
	 * @param role
	 *            Position of the role in {@code getNumberOfRoles}
	 * @return true, if the SmartDevice(Port) was added
	 */
	public boolean addDeviceOfRole(Port device, int role);

	/**
	 * Remove a SmartDevice(Port) from this Protocol
	 * 
	 * @param device
	 *            device that should be removed
	 */
	public void removeDevice(Port device);

	/**
	 * Returns all Devices which participate in this Protocol
	 * 
	 * @return devices in this Protocol
	 */
	public default Collection<Port> getDevices(){
		LinkedList<Port> devices = new LinkedList<Port>();
		for(int i=0; i<getNumberOfRoles(); i++)
			devices.addAll(getDevicesWithRole(i));
		return devices;
	} 
	
	/**
	 * Returns name of the protocol
	 * 
	 * @return name of the protocol 
	 */
	public String getName();
	
	/**
	 * Returns the topology of this protocol as specified by the static final protocol fields.
	 * 
	 * @return used topology
	 */
	public default byte getTopologyType() {
		return FULLY_CONNECTED;
	}
	
	/**
	 * Returns the different connections of this protocol, how the Ports are connected and transmitting packets.
	 * 
	 * @return Connections
	 */
	public default Collection<Pair<Port,Port>> getTopology(){
		LinkedList<Pair<Port,Port>> topology = new LinkedList<Pair<Port,Port>>();
		switch (getTopologyType()){			
		case STAR:
			Collection<Port> devices = getDevicesWithRole(0);
			Port router = null;
			if(devices.size() == 1)
				router = devices.iterator().next();
			for (Iterator<Port> iterator = getDevices().iterator(); iterator.hasNext();) {
				Port right = (Port) (iterator.next());
				if(router != right)
					topology.add(new Pair<Port, Port>(router, right));
			}
			break;
		case FULLY_CONNECTED:
		case CUSTOM:
		default:
			for (Iterator<Port> iterator = getDevices().iterator(); iterator.hasNext();) {
				Port left = (Port) (iterator.next());
				boolean samePosition = false;
				for(Iterator<Port> iterator2 = getDevices().iterator(); iterator2.hasNext();){
					Port right = iterator2.next();
					if(!samePosition){
						if(left==right)
							samePosition=true;
					}else{
						topology.add(new Pair<Port, Port>(left, right));						
					}
				}
			}
			break;
		}
		return topology;
	}
	
	/**
	 * Returns the deleted
	 * 
	 * @return deleted connection parts
	 */
	public Collection<Pair<Port,Port>> getDeletedTopology();
}
