package de.tu_darmstadt.tk.SmartHomeNetworkSim.control;

import java.util.LinkedList;
import java.util.List;

import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Connection;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.ConnectionPrecision;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Link;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Port;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.PrecisionLink;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Protocol;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.SmartDevice;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.distributionHandler.NormalDistributionHandler;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.simpleImplementation.SimpleProtocol;

/**
 * Controller, which allows the user to create a few example anomalies
 * @author Andreas T. Meyer-Berg
 *
 */
public class ExampleAnomalyController {

	/**
	 * Controller which is used to manipulates the network parts
	 */
	private Controller controller;
	
	/**
	 * Initializes the Anomaly Controller
	 * @param controller controller to be used
	 */
	public ExampleAnomalyController(Controller controller) {
		this.controller = controller;
	}
	
	/**
	 * Opens a DDosCreation Menu which targets the given Device
	 * So far only a DDoS Attack is created
	 * @param d Device which should be targeted
	 * @return DDoS Connection, which was created 
	 */
	public Connection openDDosCreationMenu(SmartDevice d, List<SmartDevice> sources) {
		/**
		 * Port of the largest connected connection to disguise DDoS
		 */
		Port example = null;
		for(Port p: d.getPorts()) {
			if(example == null || example.getConnection() == null || example.getConnection().getProtocol()==null) {
				example = p;
			} else {
				if(p.getConnection() == null || p.getConnection().getProtocol() == null) {
					continue;
				} else if(p.getConnection().getParticipants().size()>example.getConnection().getParticipants().size()){
					example = p;
				}
			}
		}
		if(example == null || example.getConnection() == null || example.getConnection().getProtocol()==null)
			example = null;
		/**
		 * Role of the target device
		 */
		int targetRole = 0;
		if(example != null) {
			targetRole = example.getConnection().getProtocol().getRoleOfDevice(example);
			if(targetRole == -1)
				targetRole = 0;
		}
		/**
		 * Attack Interval per device
		 */
		long attackInterval = 1;
		if(example!=null) {
			int numDevices = 0;
			for(Port exP:example.getConnection().getParticipants()) {
				attackInterval+=exP.getTriggerInterval();
				numDevices++;
			}
			attackInterval /= numDevices == 0 ? 1 : numDevices;
			/**
			 * Frequency Less or equal
			 */
			attackInterval /= 2.0;
		}
		
		/**
		 * Link of the DDoS attack
		 */
		Link link = null;
		if(example !=null)
			link = example.getConnection().getLink();
		if(link == null) {
			if(d.getLinks().isEmpty()) {
				System.out.println("WARNING: Could not create DDos, as Device "+d.getName()+" is not connected to any Link");
				return null;
			}
			link = d.getLinks().get(0);
		}
		NetworkController networkController = controller.getNetworkController();
		Connection ddosConnection = new ConnectionPrecision();
		ddosConnection.setLabel((short)1);
		ddosConnection.setName("DDOS against "+d.getName());
		networkController.addConnectionToLink(ddosConnection, link);
		try {
			@SuppressWarnings("unchecked")
			Class<Protocol> exampleProtocol = (Class<Protocol>) example.getConnection().getProtocol().getClass();
			ddosConnection.setProtocol(exampleProtocol.newInstance());	
		} catch (Exception e) {
			ddosConnection.setProtocol(new SimpleProtocol());			
		}
		LinkedList<SmartDevice> devices = new LinkedList<SmartDevice>(sources);
		devices.retainAll(link.getDevices());
		
		
		Port pTarget = new Port(d, (short)80, 1000L);
		pTarget.setStatus(Port.OPEN);
		if(!networkController.addDeviceToConnectionAndProtocol(pTarget, ddosConnection, targetRole)) {
			System.out.println("WARNING: Could not add DDoS Target to role "+targetRole);
		}
			
		for(SmartDevice src: devices) {
			if(src==d)continue;
			Port pSource = new Port(src, (short)80, 1L);
			pSource.setTriggerHandler(new NormalDistributionHandler(attackInterval, attackInterval*0.05));
			pSource.setStatus(Port.SENDING);
			// Ten tries of assigning random role
			for(int i=0; i<10;i++) {
				int sourceRole = (int) Math.floor(Math.random()*ddosConnection.getProtocol().getNumberOfRoles());
				if(sourceRole==targetRole)
					continue;
				if(networkController.addDeviceToConnectionAndProtocol(pSource, ddosConnection, sourceRole)) 
					break;
			}
		}
		networkController.addConnection(ddosConnection);
		
		controller.notifyObservers();
		return ddosConnection;
	}
	
	/**
	 * Crash Device, which wont send packets any longer
	 * @param d Device to be crashed
	 */
	public void crashDevice(SmartDevice d) {
		for(Port p:d.getPorts()) {
			p.setStatus(Port.CLOSED);
		}
		controller.notifyObservers();
	}

	/**
	 * Inserts DOS attack into the network
	 * @param source source of the attack
	 * @param target destination of the attack
	 * @return DDoS Connection, which was created
	 */
	public Connection runDosAttack(SmartDevice source, SmartDevice target) {
		NetworkController networkController = controller.getNetworkController();
		Connection dosConnection = new ConnectionPrecision();
		dosConnection.setLabel((short)1);
		dosConnection.setName("DOS: "+source.getName()+"-"+target.getName());
		networkController.addConnectionToLink(dosConnection, getCommonLink(source, target));
		dosConnection.setProtocol(new SimpleProtocol());
		Port pSource = new Port(source, (short)80, 1L);
		pSource.setStatus(Port.SENDING);
		networkController.addDeviceToConnectionAndProtocol(pSource, dosConnection, 0);
		Port pTarget = new Port(target, (short)80, 1000L);
		pTarget.setStatus(Port.OPEN);
		networkController.addDeviceToConnectionAndProtocol(pSource, dosConnection, 0);
		networkController.addDeviceToConnectionAndProtocol(pTarget, dosConnection, 1);
		networkController.addConnection(dosConnection);
		controller.notifyObservers();
		return dosConnection;
	}
	
	/**
	 * Returns a common Link of the given devices
	 * @param a
	 * @param b
	 * @return Link, both devices are connected to
	 */
	private Link getCommonLink(SmartDevice a, SmartDevice b) {
		LinkedList<Link> l = new LinkedList<Link>(a.getLinks());
		l.addAll(b.getLinks());
		if(l.isEmpty()) {
			NetworkController net = controller.getNetworkController();
			Link newLink = new PrecisionLink("Direct Link: " + a.getName()+"-"+b.getName());
			net.addLinkToDevice(newLink, a);
			net.addLinkToDevice(newLink, b);
			net.addLink(newLink);
			return newLink;
		}else
			return l.getFirst();
	}
}
