package de.tu_darmstadt.tk.SmartHomeNetworkSim.core;

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

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

/**
 * Implementation of the Connection Interface, with focus on precision
 *
 * @author Andreas T. Meyer-Berg
 */
public class ConnectionPrecision extends ConnectionPerformance {

	/**
	 * Packets which should be returned in the next time step
	 */
	private LinkedList<Packet> outOfBoundsPackets = new LinkedList<Packet>();
	
	/**
	 * Initializes the connection, adds participants of the protocol
	 * 
	 * @param l Link which this connection uses
	 * @param p Protocol of the connection
	 */
	public ConnectionPrecision(Link l, Protocol p) {
		super(l,p);
	}
	
	/**
	 * Initializes the connection
	 */
	public ConnectionPrecision() {
		super();
	}
	
	@Override
	public Collection<Packet> simulateTimeInterval(long startTime, long duration) {
		/**
		 * Time step the simulation interval ends
		 */
		long endTime = startTime+duration;
		
		/**
		 * All packets which should be returned in this time step
		 */
		LinkedList<Packet> returnPackets = new LinkedList<Packet>();
		returnPackets.addAll(getTerminationPackages(startTime));
		startTime += returnPackets.size();
		
		/**
		 * Add packets of the last time step
		 */
		returnPackets.addAll(outOfBoundsPackets);
		
		/**
		 * Sorted Tree (Sorted by next trigger time)
		 */
		TreeSet<Port> portTree = new TreeSet<Port>(new PortComparator());
		portTree.addAll(participants);

		/**
		 * Iterator to move through the Tree
		 */
		Iterator<Port> it = portTree.iterator();
		
		/**
		 * Traverse the tree ascending
		 */
		while(it.hasNext()){
			Port p = it.next();
			
			if(p.getStatus()==Port.SENDING && p.getLastTrigger()+p.getTriggerInterval()<endTime){
					/**
					 * Remove current Port (as the nextTrigger time will change, and therefore its position in the tree)
					 */
					it.remove();
					/**
					 * Generate first package in the simulation interval (not before startTime though), and at least 1ms after the last Trigger Time
					 */
					returnPackets.addAll(protocol.generateNextPackets(p, (long)Math.max( p.getLastTrigger()+p.getTriggerInterval()+p.getJitter()*Math.random(),Math.max(p.getLastTrigger(),startTime)),Math.random()<packetLossRate));
					/**
					 * If Port should simulate again in this interval -> add back to the tree
					 */
					if(p.getLastTrigger()+p.getTriggerInterval()<endTime){
						portTree.add(p);
						/**
						 * Reset iterator, to start from the next simulating port
						 */
						it = portTree.iterator();
					}
			}else{
				/**
				 * Remove ports, which are not sending or which won't simulate in this interval again
				 */
				it.remove();
			}
		}
		
		/**
		 * Sort and return packages
		 */
		returnPackets.sort(new PacketComparator());
		
		/**
		 * Remove packets which are not being sent in this time interval
		 */
		outOfBoundsPackets.clear();
		/**
		 * Last package, which should be sent in the next time step
		 */
		Packet last = returnPackets.isEmpty()? null : returnPackets.getLast();
		
		while (last != null&&last.getTimestamp()>endTime) {
			outOfBoundsPackets.addFirst(returnPackets.removeLast());
			if(returnPackets.isEmpty())
				break;
			last = returnPackets.getLast();
		}
		if(label!=0)
			returnPackets.forEach(p->p.setLabel(label));
		
		return returnPackets;
	}
	
	/**
	 * Comparator for comparing the next trigger time of two ports, the lower one should trigger first
	 *
	 * @author Andreas T. Meyer-Berg
	 */
	private class PortComparator implements Comparator<Port>{

		@Override
		public int compare(Port o1, Port o2) {
			return Long.compare(o1.getLastTrigger()+o1.getTriggerInterval(),o2.getLastTrigger()+o2.getTriggerInterval());
		}
		
	}
}
