package ui.controller;

import java.util.ArrayList;
import java.util.HashMap;

import classes.CpsEdge;
import classes.CpsNode;
import classes.AbstractCpsObject;
import classes.HolonElement;
import classes.HolonObject;
import classes.HolonSwitch;
import classes.SubNet;
import ui.model.Model;
import ui.view.MyCanvas;

/**
 * Controller for Simulation.
 * 
 * @author Gruppe14
 */
public class SimulationManager {
	private Model model;
	private ArrayList<AbstractCpsObject> objectsToHandle;
	private ArrayList<CpsEdge> allConnections;
	private ArrayList<SubNet> subNets;
	private MyCanvas canvas;
	private int timeStep;
	private boolean simMode;
	private HashMap<Integer, Float> tagTable = new HashMap<Integer, Float>();

	/**
	 * Constructor.
	 * 
	 * @param m
	 *            Model
	 */
	public SimulationManager(Model m) {
		canvas = null;
		model = m;
		subNets = new ArrayList<SubNet>();
		simMode = model.getIsSimulation();
	}

	/**
	 * calculates the flow of the edges and the supply for objects.
	 * 
	 * @param x
	 *            current Iteration
	 */
	public void calculateStateForTimeStep(int x) {
		simMode = model.getIsSimulation();
		timeStep = x;
		searchForSubNets();
		for (SubNet singleSubNet : subNets) {
			resetConnections(singleSubNet.getObjects().get(0), new ArrayList<Integer>(), new ArrayList<CpsEdge>());
		}
		for (SubNet singleSubNet : subNets) {
			float production = calculateEnergy("prod", singleSubNet, timeStep);
			float consumption = calculateEnergy("cons", singleSubNet, timeStep);
			float minConsumption = calculateMinimumEnergy(singleSubNet, timeStep);
			setFlow(singleSubNet, simMode);
			for (HolonObject hl : singleSubNet.getObjects()) {
				if (!(hl.getState() == 0) && !(hl.getState() == 3)) {
					for (int i = 0; i < hl.getConnections().size(); i++) {
						CpsEdge edge = hl.getConnectedTo().get(i);
						if (edge.getState() && edge.getFlow() > 0 || edge.getCapacity() == -1) {
							// 0 = no energy, 1 = not supplied, 2 = supplied, 3
							// = producer, 4 = partially supplied
							if ((production + consumption) >= 0) {
								hl.setState(2);
							}
							if ((production + consumption) < 0) {
								if ((production + minConsumption) >= 0) {
									hl.setState(4);
								} else if (hl.checkIfPartiallySupplied(timeStep)) {
									hl.setState(4);
								} else {
									hl.setState(1);
								}
							}
							break;
						}
						hl.setState(1);
					}
					if (hl.checkIfPartiallySupplied(timeStep) && !(hl.getState() == 2)) {
						hl.setState(4);
					}
				}
			}
		}
		canvas.repaint();
	}

	/**
	 * Sets the Flow.
	 * 
	 * @param sN
	 *            Subnet
	 * @param simulation
	 *            boolean if Simulation is on
	 */
	public void setFlow(SubNet sN, boolean simulation) {
		if (simulation) {
			setFlowSimulation(sN);
		} else {
			setFlowModelation(sN);
		}
	}

	/**
	 * set the Flow Modelation.
	 * 
	 * @param sN
	 *            Subnet
	 */
	public void setFlowModelation(SubNet sN) {
		for (HolonObject hl : sN.getObjects()) {
			float energy = hl.getCurrentEnergyAtTimeStep(timeStep);
			if (energy > 0) {
				for (CpsEdge e : sN.getEdges()) {
					e.setFlow(e.getFlow() + energy);
					e.calculateState(simMode);
				}
			}
		}
	}

	/**
	 * Set Flow Simulation.
	 * 
	 * @param sN
	 *            Subnet
	 */
	public void setFlowSimulation(SubNet sN) {
		ArrayList<AbstractCpsObject> producers = new ArrayList<AbstractCpsObject>();
		AbstractCpsObject tmp = null;
		tagTable = new HashMap<Integer, Float>();
		System.out.println("producers:");
		for (HolonObject hl : sN.getObjects()) {
			float energy = hl.getCurrentEnergyAtTimeStep(timeStep);
			if (energy > 0) {
				System.out.println(hl.getID());
				tagTable.put(hl.getID(), energy);
				hl.addTag(hl.getID());
				for (CpsEdge edge : hl.getConnections()) {
					if (edge.getState()) {
						if (edge.getA().getID() == hl.getID()) {
							edge.getB().addTag(hl.getID());
							tmp = edge.getB();
						}
						if (edge.getB().getID() == hl.getID()) {
							edge.getA().addTag(hl.getID());
							tmp = edge.getA();
						}
						edge.setFlow(edge.getFlow() + energy);
						edge.calculateState(true);
						edge.addTag(hl.getID());
						if (edge.getState() && !producers.contains(tmp)) {
							if(tmp instanceof HolonSwitch){
								if(((HolonSwitch)tmp).getState(timeStep)){
									producers.add(tmp);
								}
							}else{
								producers.add(tmp);
							}
						}
					}
				}
			}
		}
		printNodes(producers);
		setFlowSimRec(producers, 0);
	}

	/**
	 * Set Flow Rimulation Rec.
	 * 
	 * @param nodes
	 *            the nodes
	 * @param iter
	 *            the Iteration
	 */
	public void setFlowSimRec(ArrayList<AbstractCpsObject> nodes, int iter) {
		System.out.println("iteration: " + iter);
		ArrayList<AbstractCpsObject> newNodes = new ArrayList<AbstractCpsObject>();
		ArrayList<CpsEdge> changedEdges = new ArrayList<CpsEdge>();
		AbstractCpsObject tmp;
		if(nodes.size() != 0){
			for(AbstractCpsObject cps: nodes){
				if(legitState(cps)){
					for(CpsEdge edge: cps.getConnections()){
						if(edge.getPseudoTags() == null){
							System.out.println("edge null");
						}
						if(edge.getState() && (!(edge.containsTags(edge.getTags(), cps.getTag())))){
							if(edge.getA().getID() == cps.getID()){
								tmp = edge.getB();
							}else{
								tmp = edge.getA();
							}	
							for(Integer tag: cps.getTag()){
								if(!(edge.getTags().contains(tag)) && !(edge.getPseudoTags().contains(tag))){
									edge.setFlow(edge.getFlow() + tagTable.get(tag));
									edge.addTag(tag);
								}
							}
							for(Integer tag: tmp.getTag()){
								if(!(edge.getTags().contains(tag)) && tagTable.get(tag) != null && !(edge.getPseudoTags().contains(tag))){
									edge.setFlow(edge.getFlow() + tagTable.get(tag));
									edge.addPseudoTag(tag);
									changedEdges.add(edge);
								}
							}
							edge.calculateState(true);
							if(edge.getState()){
								tmp.addAllPseudoTags(cps.getTag());
								if(!newNodes.contains(tmp)){
								newNodes.add(tmp);
								}
							}
						}
					}
				}
			}
			setPseudoTags(newNodes, changedEdges);
			printNodes(newNodes);
			setFlowSimRec(newNodes, iter+1);
		}
	}

	/**
	 * Set the Pseudo Tags.
	 * 
	 * @param nodes
	 *            Array of AbstractCpsObjects
	 */
	public void setPseudoTags(ArrayList<AbstractCpsObject> nodes, ArrayList<CpsEdge> edges) {
		for (AbstractCpsObject node : nodes) {
			node.recalculateTags();
			node.setPseudoTags(new ArrayList<Integer>());
		}
		for(CpsEdge edge : edges){
			edge.recalculateTags();
			edge.setPseudoTag(new ArrayList<Integer>());
		}
	}

	/**
	 * Print the nodes.
	 * 
	 * @param nodes
	 *            Array of AbstractCpsObject
	 */
	public void printNodes(ArrayList<AbstractCpsObject> nodes) {
		System.out.println("new Nodes:");
		for (AbstractCpsObject node : nodes) {
			System.out.println(node.getID());
		}
	}

	/**
	 * Get String.
	 * 
	 * @param tags
	 *            the tags
	 * @return the String
	 */
	public String getString(ArrayList<Integer> tags) {
		String result = "";
		for (Integer i : tags) {
			result = result + ", " + i;
		}
		return result;
	}

	/**
	 * Merge the Lists.
	 * 
	 * @param a
	 *            first liust
	 * @param b
	 *            second list
	 * @return the Result
	 */
	public ArrayList<Integer> mergeLists(ArrayList<Integer> a, ArrayList<Integer> b) {
		ArrayList<Integer> result = new ArrayList<Integer>();
		for (Integer i : a) {
			if (!(result.contains(i))) {
				result.add(i);
			}
		}
		for (Integer j : b) {
			if (!(result.contains(j))) {
				result.add(j);
			}
		}
		return result;
	}

	/**
	 * Reset the Connection.
	 * 
	 * @param cps
	 *            CpsObject
	 * @param visitedObj
	 *            the visited Objects
	 * @param visitedEdges
	 *            the visited Edges
	 */
	public void resetConnections(AbstractCpsObject cps, ArrayList<Integer> visitedObj,
			ArrayList<CpsEdge> visitedEdges) {
		visitedObj.add(cps.getID());
		cps.resetTags();
		for (CpsEdge e : cps.getConnections()) {
			if (!(visitedEdges.contains(e))) {
				e.setFlow(0);
				e.calculateState(simMode);
				e.setTags(new ArrayList<Integer>());
				visitedEdges.add(e);
				if (!(visitedObj.contains(e.getA().getID()))) {
					resetConnections(e.getA(), visitedObj, visitedEdges);
					e.getA().resetTags();
				}
				if (!(visitedObj.contains(e.getB().getID()))) {
					resetConnections(e.getB(), visitedObj, visitedEdges);
					e.getB().resetTags();
				}
			}
		}
	}

	/**
	 * calculates the energy of either all producers or consumers.
	 * 
	 * @param type
	 *            Type
	 * @param sN
	 *            Subnet
	 * @param x
	 *            Integer
	 * 
	 * @return The Energy
	 */
	public float calculateEnergy(String type, SubNet sN, int x) {
		float energy = 0;
		for (HolonObject hl : sN.getObjects()) {
			if (type.equals("prod")) {
				if (hl.getCurrentEnergyAtTimeStep(x) > 0) {
					energy = energy + hl.getCurrentEnergyAtTimeStep(x);
					hl.setState(3);
				}
			}
			if (type.equals("cons")) {
				if (hl.getCurrentEnergyAtTimeStep(x) < 0) {
					energy = energy + hl.getCurrentEnergyAtTimeStep(x);
					hl.setState(1);
				}
			}
			if (hl.getCurrentEnergyAtTimeStep(x) == 0) {
				hl.setState(0);
			}
		}
		return energy;
	}

	/**
	 * Calculate the Minimum Energy.
	 * 
	 * @param sN
	 *            Subnet
	 * @param x
	 *            Integer
	 * @return the Calculated minimum Energy
	 */
	public float calculateMinimumEnergy(SubNet sN, int x) {
		float min = 0;
		float minElement = 0;
		for (HolonObject hl : sN.getObjects()) {
			if (hl.getElements().size() > 0 && hl.getElements().get(0).getTotalEnergyAtTimeStep(x) < 0) {
				minElement = hl.getElements().get(0).getTotalEnergyAtTimeStep(x);
			}
			for (HolonElement he : hl.getElements()) {
				if (minElement < he.getTotalEnergyAtTimeStep(x) && he.getTotalEnergyAtTimeStep(x) < 0) {
					minElement = he.getTotalEnergyAtTimeStep(x);
				}
			}
			min = min + minElement;
		}
		return min;
	}

	/**
	 * generates all subNets from all objectsToHandle.
	 */
	public void searchForSubNets() {
		subNets = new ArrayList<SubNet>();
		boolean end = false;
		int i = 0;
		AbstractCpsObject cps;
		if (objectsToHandle.size() > 0) {
			while (!end) {
				cps = objectsToHandle.get(i);
				SubNet singleSubNet = new SubNet(new ArrayList<HolonObject>(), new ArrayList<CpsEdge>(),
						new ArrayList<HolonSwitch>());
				singleSubNet = buildSubNet(cps, new ArrayList<Integer>(), singleSubNet);
				if (singleSubNet.getObjects().size() != 0) {
					subNets.add(singleSubNet);
				}
				if (0 == objectsToHandle.size()) {
					end = true;
				}
			}
		}
	}

	/**
	 * recursivly generates a subnet of all objects, that one specific object is
	 * connected to.
	 * 
	 * @param cps
	 *            AbstractCpsObject
	 * @param visited
	 *            visited Array of Integer
	 * @param sN
	 *            Subnets
	 * @return Subnet
	 */
	public SubNet buildSubNet(AbstractCpsObject cps, ArrayList<Integer> visited, SubNet sN) {
		visited.add(cps.getID());
		if (cps instanceof HolonObject) {
			sN.getObjects().add((HolonObject) cps);
		}
		if (cps instanceof HolonSwitch) {
			sN.getSwitches().add((HolonSwitch) cps);
		}
		removeFromToHandle(cps.getID());
		AbstractCpsObject a;
		AbstractCpsObject b;
		for (CpsEdge edge : cps.getConnections()) {
			if(!(simMode && !edge.getState())){
				a = edge.getA();
				b = edge.getB();
				if (!(cps instanceof HolonSwitch)) {
					if (!(sN.getEdges().contains(edge))) {
						sN.getEdges().add(edge);
					}
				}
				if(cps instanceof HolonSwitch && ((HolonSwitch)cps).getState(timeStep)){
					if (!(sN.getEdges().contains(edge))) {
						sN.getEdges().add(edge);
					}
				}
				if (!visited.contains(a.getID()) && legitState(cps)) {
					sN = buildSubNet(a, visited, sN);
				}
				if (!visited.contains(b.getID()) && legitState(cps)) {
					sN = buildSubNet(b, visited, sN);
				}
			}
		}
		return sN;
	}

	/**
	 * is the Switch in a legitimate State.
	 * 
	 * @param neighbor AbstractCpsObject
	 * @param current AbstractCpsObject
	 * @return boolean
	 */
	public boolean legitState(AbstractCpsObject current) {
		if (current instanceof HolonSwitch) {
			if (((HolonSwitch) current).getState(timeStep)) {
				return true;
			}else{
				return false;
			}
		}
		return true;
	}

	/**
	 * removes an Object that already has been handled with.
	 * 
	 * @param id the Object ID
	 */
	public void removeFromToHandle(int id) {
		for (int i = 0; i < objectsToHandle.size(); i++) {
			if (objectsToHandle.get(i).getID() == id) {
				objectsToHandle.remove(i);
			}
		}
	}

	/**
	 * ensures that objectsToHandle only contains HolonObjects.
	 */
	public void cleanObjectsToHandle() {
		for (int i = 0; i < objectsToHandle.size(); i++) {
			if (!(objectsToHandle.get(i) instanceof HolonObject)) {
				objectsToHandle.remove(i);
			}
		}
	}

	/**
	 * copies the data of an array of Objects.
	 * 
	 * @param toCopy the ArrayList of CpsObjects co Copy
	 */
	public void copyObjects(ArrayList<AbstractCpsObject> toCopy) {
		objectsToHandle = new ArrayList<AbstractCpsObject>();
		for (AbstractCpsObject cps : toCopy) {
			objectsToHandle.add(cps);
		}
	}

	/**
	 * Prints the Components auf all subnets.
	 */
	public void printNet() {
		for (int i = 0; i < subNets.size(); i++) {
			System.out.println("SUBNET NR:" + i);
			System.out.println("  Objects:");
			for (int j = 0; j < subNets.get(i).getObjects().size(); j++) {
				HolonObject hl = subNets.get(i).getObjects().get(j);
				System.out.println("    " + hl.getName() + " " + hl.getID());
			}
			System.out.println("  Edges:");
			for (int j = 0; j < subNets.get(i).getEdges().size(); j++) {
				CpsEdge edge = subNets.get(i).getEdges().get(j);
				System.out.println("     " + edge.getA().getName() + " connected To " + edge.getB().getName());
			}
			System.out.println("  Switches:");
			for (int j = 0; j < subNets.get(i).getSwitches().size(); j++) {
				HolonSwitch sw = subNets.get(i).getSwitches().get(j);
				System.out.println("    " + sw.getName() + " " + sw.getID() + " State:" + sw.getActiveAt()[timeStep]);
			}
		}
	}

	/**
	 * Set the Canvas.
	 * 
	 * @param can
	 *            the Canvas
	 */
	public void setCanvas(MyCanvas can) {
		canvas = can;
	}

	/**
	 * Reset all Data to the current state of the Model.
	 */
	public void reset() {
		copyObjects(model.getObjectsOnCanvas());
	}

	/**
	 * Get all Subnets.
	 * 
	 * @return all Subnets
	 */
	public ArrayList<SubNet> getSubNets() {
		return subNets;
	}

}
