/**
 * 
 */
package de.tu_darmstadt.tk.SmartHomeNetworkSim.core.anomalydetectors;

import java.io.File;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map.Entry;

import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Link;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Packet;
import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.PacketSniffer;
import weka.classifiers.AbstractClassifier;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.converters.ArffSaver;

/**
 * @author Camille Peto
 *
 */
public abstract class BaseAnomalyDetector implements PacketSniffer {

	/**
	 * True, if instances should be used for training
	 */
	protected boolean training = true;
	
	/**
	 * Attributes which should be taken into account
	 */
	protected ArrayList<Attribute> atts = new ArrayList<Attribute>();
	
	/**
	 * Collected Packets
	 */
	protected Instances dataset;
	
	/**
	 * Number of packets used for number of packets per second
	 */
	protected int NUMBER_OF_PACKETS = 30;
	
	/**
	 * HashMap for calculating transmission delay
	 */
	protected HashMap<Link, LinkedList<Packet>> lastPackets = new HashMap<Link, LinkedList<Packet>>();
	
	protected HashMap<String,Integer> link_mappings = new HashMap<String, Integer>();

	protected HashMap<String,Integer> source_mappings = new HashMap<String, Integer>();
	
	protected HashMap<String,Integer> destination_mappings = new HashMap<String, Integer>();
	
	protected HashMap<String,Integer> protocol_mappings = new HashMap<String, Integer>();
	
	public BaseAnomalyDetector() {
		link_mappings.put("unknown", 0);
		atts.add(new Attribute("Link-Name", false));
		source_mappings.put("unknown", 0);
		atts.add(new Attribute("Source-Port-number", false));
		destination_mappings.put("unknown", 0);
		atts.add(new Attribute("Destination-Port-number", false));
		protocol_mappings.put("unknown", 0);
		atts.add(new Attribute("Packets-per-simulationstep", false));
		// Initialize data set
		dataset = new Instances("Directions", atts, 100000);
	}
	
	@Override
	public void processPackets(HashMap<Link, LinkedList<Packet>> packets) {
		for (Iterator<Entry<Link, LinkedList<Packet>>> it = packets.entrySet().iterator(); it.hasNext();) {
			Entry<Link, LinkedList<Packet>> entry = it.next();
			/**
			 * Link the packet was captured on
			 */
			Link l = entry.getKey();
			
			int[][] directions = new int[l.getPackets().size()][3];
			for (Iterator<Packet> itPacket = entry.getValue().iterator(); itPacket.hasNext();) {
				/**
				 * Directions of packets added to dataset
				 */
				Packet packet = (Packet) itPacket.next();
				for(int i = 0; i < directions.length; i++) {
					int j = 0;
					boolean sameSource = false;
					if(directions[i][j] != packet.getSource().getPortNumber() && directions[i][j] == 0) {
						directions[i][j] = packet.getSource().getPortNumber();
						sameSource = true;
					}
					else if(directions[i][j] == packet.getSource().getPortNumber()) sameSource = true;
					
					j++;
					
					boolean sameDestination = false;
					if(directions[i][j] != packet.getDestination().getPortNumber() && directions[i][j] == 0) {
						directions[i][j] = packet.getDestination().getPortNumber();
						sameDestination = true;
					}
					else if(directions[i][j] == packet.getDestination().getPortNumber()) sameDestination = true;
					
					j++;
					
					if(sameSource && sameDestination) {
						directions[i][j] = directions[i][j] +1;
						break;
					}
					
					}

				
				}
			
			for(int i = 0; i < directions.length; i ++) {
				if(training)
					try {
						training(l, directions[i]);
					} catch (Exception e) {
						e.printStackTrace();
					}
				else {
					classify(l, directions[i]);
					
				}
				
			}
			
			
		}
		
	}
	
	public void classify(Link l, int[] direction) {
		try {
				boolean anomaly = classifyInstance(direction2Instance(l, direction));
				if(anomaly) {
					System.out.println("Anomaly found on direction " + direction[1] + " to " + direction[2]);
				}
			} catch (Exception e) {
					/**
					 * Anomaly found
					 */
					System.out.println("Error classifying instance");
					e.printStackTrace();
			}
	}
	
	/**
	 * Collect the packets
	 * 
	 * @param packets packets to be learned
	 */
	protected void training(Link l, int[] direction) {
		dataset.add(direction2Instance(l, direction));
	}
	
	private Instance direction2Instance(Link link, int[] direction) {
		
			DenseInstance instance = new DenseInstance(dataset.numAttributes());
			instance.setDataset(dataset);
			
			// link
			instance.setValue(0, link == null ? 0 : stringToNumber(link_mappings, link.getName()));
			
			//Source
			instance.setValue(1, direction[0]);
			
			//Destination
			instance.setValue(2, direction[1]);
			
			//NumOfPackets
			instance.setValue(3, direction[2]);
			
			return instance;
	}

	/**
	 * Transforms the String into an Number
	 * @param map
	 * @param s
	 * @return
	 */
	double stringToNumber(HashMap<String, Integer> map, String s) {
		Integer i = map.get(s);
		if(i == null) {
			int size = map.size();
			map.put(s, size);
			return size;
		}else {
			return i;
		}
	}
	
	@Override
	public void setMode(boolean testing) {
		training = !testing;
		if(testing) {
			try {
				finishDataCollection();
			} catch (Exception e) {
				System.out.println("Clustering/Classifing failed");
				e.printStackTrace();
			}	
		}
	}

	@Override
	public boolean getMode() {
		return !training;
	}

	
	/**
	 * Finishes the collection and trains the clusterer on the collected packets
	 * 
	 * @throws Exception
	 */
	private void finishDataCollection() throws Exception{
		System.out.println("Number of instances: " + dataset.numInstances());
		
		trainModel(dataset);
	}
	
	/**
	 * Train the model using the given instances
	 * @param instances training set, which should be learned
	 */
	public abstract void trainModel(Instances instances);
	
	public abstract boolean classifyInstance(Instance instance) throws Exception;
	
	public abstract void saveModel() throws Exception;
	
}
