package de.tu_darmstadt.tk.SmartHomeNetworkSim.core;

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

/**
 * Implementation of the Connection Interface, with main focus on performance
 *
 * @author Andreas T. Meyer-Berg
 */
public class ConnectionPerformance implements Connection {

	/** Link on which this connection runs */
	protected Link link;
	/** Link on which this connection runs */
	protected LinkedList<Port> participants;
	/** Link on which this connection runs */
	protected LinkedList<Port> removedParticipants;
	/** Link on which this connection runs */
	protected Protocol protocol;
	/** Link on which this connection runs */
	protected double packetLossRate;
	/** Link on which this connection runs */
	protected byte status;
	/** Link on which this connection runs */
	protected boolean changed = false;
	/** Link on which this connection runs */
	protected String name;
	/** default label */
	protected short label = 0;
	/**
	 * Initializes the connection, adds participants of the protocol
	 * 
	 * @param l Link which this connection uses
	 * @param p Protocol of the connection
	 */
	public ConnectionPerformance(Link l, Protocol p) {
		link = l;
		participants=new LinkedList<Port>();
		removedParticipants = new LinkedList<Port>();
		this.protocol = p;
		status =Connection.ACTIVE;
		if(p==null)
			name = "unspecified-connection";
		else
			name = p.getName()+"-connection";
	}
	
	public ConnectionPerformance() {
		link = null;
		this.protocol = null;
		participants=new LinkedList<Port>();
		removedParticipants = new LinkedList<Port>();
		status =Connection.ACTIVE;
		name = "unspecified-connection";

	}
	
	@Override
	public Link getLink() {
		return link;
	}
	
	@Override
	public void setLink(Link link){
		this.link = link;
	}

	@Override
	public Collection<Port> getParticipants() {
		LinkedList<Port> out = new LinkedList<Port>();
		out.addAll(participants);
		return out;
	}
	@Override
	public Collection<Port> getParticipantsAndRemoved() {
		LinkedList<Port> out = new LinkedList<Port>();
		out.addAll(participants);
		out.addAll(removedParticipants);
		return out;
	}

	@Override
	public boolean removeSmartDevice(Port sd) {
		boolean removed = participants.remove(sd);
		if(removed)
			removedParticipants.add(sd);
		if(participants.size()<2)setStatus(Connection.TERMINATED);
		return removed;
	}

	@Override
	public boolean addSmartDevice(Port sd) {
		return participants.add(sd);
	}

	@Override
	public Collection<Packet> simulateTimeInterval(long startTime, long duration) {
		LinkedList<Packet> returnPackets=new LinkedList<Packet>();
		returnPackets.addAll(getTerminationPackages(startTime));
		for(Port p:participants){
			if(p.getLastTrigger()+p.getTriggerInterval()<startTime && p.getStatus()==Port.SENDING){
				returnPackets.addAll(protocol.generateNextPackets(p, (long) (startTime+p.getJitter()*Math.random()),Math.random()<packetLossRate));
			}
			while(p.getLastTrigger()+p.getTriggerInterval()<startTime+duration &&p.getStatus()==Port.SENDING)
				returnPackets.addAll(protocol.generateNextPackets(p, (long) Math.max((p.getLastTrigger()+p.getTriggerInterval()+p.getJitter()*(Math.random())),p.getLastTrigger()+p.getTriggerInterval()),Math.random()<packetLossRate));
		}
		returnPackets.sort((a,b)->(Long.compare(a.getTimestamp(),b.getTimestamp())));
		if(label!=0)
			returnPackets.forEach(p->p.setLabel(label));
		return returnPackets;
	}
	
	@Override
	public Collection<Packet> encapsulatePackages(Collection<Packet> packets) {
		packets.forEach(p->p.setLabel(label));
		return packets;
	}


	@Override
	public Collection<Packet> getTerminationPackages(long startTime) {
		changed=!removedParticipants.isEmpty();
		removedParticipants.clear();
		if(status==TERMINATED)status = DONE;
		return protocol.generateNextPackets(null, startTime, false);
	}

	@Override
	public Protocol getProtocol() {
		return protocol;
	}

	@Override
	public boolean setProtocol(Protocol protocol) {
		this.protocol = protocol;
		return true;
	}

	@Override
	public byte getStatus() {
		return status;
	}

	@Override
	public void setStatus(byte status) {
		this.status = status;
	}

	@Override
	public void setPacketLossProbability(double lossPercentage) {
		packetLossRate=lossPercentage;
	}

	@Override
	public double getPacketLossProbability() {
		return packetLossRate;
	}

	@Override
	public boolean getStatusChanged() {
		return changed;
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public short getLabel() {
		return label;
	}

	@Override
	public void setLabel(short label) {
		this.label = label;
	}
}
