package ui.controller;

import classes.*;
import classes.comparator.EnergyMinToMaxComparator;
import classes.comparator.MinEnergyComparator;
import classes.comparator.WeakestBattery;
import ui.model.IntermediateCableWithState;
import ui.model.DecoratedCable;
import ui.model.DecoratedCable.CableState;
import ui.model.DecoratedSwitch.SwitchState;
import ui.model.DecoratedNetwork;
import ui.model.DecoratedState;
import ui.model.DecoratedSwitch;
import ui.model.MinimumModel;
import ui.model.MinimumNetwork;
import ui.model.Model;
import ui.model.Model.FairnessModel;
import ui.model.VisualRepresentationalState;
import ui.view.FlexiblePane;
import ui.view.GUI;
import ui.view.MyCanvas;
import ui.view.Outliner;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.ListIterator;

import javax.swing.JPanel;


/**
 * Controller for Simulation.
 *
 * @author Gruppe14
 */
public class SimulationManager {
	int global = 0;
	private Model model;
	private HashMap<Integer, DecoratedState> saves = new HashMap<Integer, DecoratedState>();
	private HashMap<Integer, VisualRepresentationalState> savesVisual = new HashMap<Integer, VisualRepresentationalState>();
	private int timeStep;
	private FlexiblePane flexPane;

	/**
	 * Constructor.
	 *
	 * @param m
	 *            Model
	 */
	SimulationManager(Model m) {
		model = m;
	}
	/**
	 * calculates the flow of the edges and the supply for objects and consider old timesteps for burned cables.
	 *
	 * @param timestep
	 *            current Iteration
	 */
	void calculateStateForTimeStep(int timestep) {
		
		
		HashMap<CpsEdge, CableState> map = new HashMap<CpsEdge, CableState>();
		if(timestep > 0 && saves.containsKey(timestep-1)) //if the state before exist
		{
			//make cable hastmap
			DecoratedState theStateBefore = saves.get(timestep-1);
			for(DecoratedCable edge : theStateBefore.getLeftOverEdges())
			{
				map.put(edge.getModel(), edge.getState());
			}
		}
		timeStep = timestep;
		ArrayList<MinimumNetwork> list =  new ArrayList<MinimumNetwork>();
		MinimumModel minimumModel = new MinimumModel(model.getObjectsOnCanvas(), model.getEdgesOnCanvas());
		//set all working:
		for(IntermediateCableWithState cable : minimumModel.getEdgeList()) {
			if(map.containsKey(cable.getModel())) cable.setState(map.get(cable.getModel()));
		}
		ArrayList<IntermediateCableWithState> leftOver = new ArrayList<IntermediateCableWithState>();
		boolean doAnotherLoop = true;
		while(doAnotherLoop) {
			doAnotherLoop = false;
			list = calculateNetworks(minimumModel, timestep, leftOver);
			for(MinimumNetwork net : list) {
				float energyOnCables = net.getHolonObjectList().stream().filter(object -> object.getEnergyAtTimeStep(timestep) > 0.0f).map(object -> object.getEnergyAtTimeStep(timestep)).reduce(0.0f, ((a,b) -> a + b));
				//find the cable with the energy supplied from his two connected objects are the biggest, from all cables that the network give more energy than the cablecapacity. 
				IntermediateCableWithState cable = net.getEdgeList().stream().filter(aCable -> energyOnCables > aCable.getModel().getCapacity()).max((lhs,rhs) -> Float.compare(lhs.getEnergyFromConnetedAtTimestep(timestep), rhs.getEnergyFromConnetedAtTimestep(timestep))).orElse(null);
				if(cable != null) {
					cable.setState(CableState.Burned);
					doAnotherLoop = true;
				}
			}
		}
		ArrayList<DecoratedNetwork> decorNetworks = new ArrayList<DecoratedNetwork>();
		FairnessModel actualFairnessModel = model.getFairnessModel();
		for (MinimumNetwork net : list) {
			decorNetworks.add(new DecoratedNetwork(net, timestep, actualFairnessModel));
		}
		ArrayList<DecoratedCable> leftOverDecoratedCables = new ArrayList<DecoratedCable>();
		
		for(IntermediateCableWithState cable: leftOver) {
			leftOverDecoratedCables.add(new DecoratedCable(cable.getModel(), cable.getState(), 0.0f));
		}
		ArrayList<DecoratedSwitch> listOfDecoratedSwitches = decorateSwitches(minimumModel, timestep);
		DecoratedState stateFromThisTimestep = new DecoratedState(decorNetworks, leftOverDecoratedCables, listOfDecoratedSwitches, timestep);
		saves.put(timestep, stateFromThisTimestep);
		savesVisual.put(timestep, new VisualRepresentationalState(stateFromThisTimestep, minimumModel));
	}
	/** 
	 * Decorate a switch
	 * @param minModel
	 * @param iteration
	 * @return
	 */
	public static ArrayList<DecoratedSwitch> decorateSwitches(MinimumModel minModel, int iteration) {
		ArrayList<DecoratedSwitch> aListOfDecoratedSwitches = new ArrayList<DecoratedSwitch>(); 
		for(HolonSwitch hSwitch: minModel.getSwitchList()) {
			aListOfDecoratedSwitches.add(new DecoratedSwitch(hSwitch, hSwitch.getState(iteration) ? SwitchState.Closed : SwitchState.Open));
		}
		return aListOfDecoratedSwitches;
	}
	/**
	 * SubFunction to calculate the Networks from the model.
	 * @param minModel
	 * @param Iteration
	 * @param leftOver
	 * @return
	 */
	ArrayList<MinimumNetwork> calculateNetworks(MinimumModel minModel, int Iteration, ArrayList<IntermediateCableWithState> leftOver){
		//Copy minModel ObjectList
		ArrayList<HolonObject> holonObjectList = new ArrayList<HolonObject>();
		for(HolonObject holonObject: minModel.getHolonObjectList()) {
			holonObjectList.add(holonObject);
		}
		//Copy minModelEdgeList
		ArrayList<IntermediateCableWithState> edgeList = new ArrayList<IntermediateCableWithState>();
		for(IntermediateCableWithState cable: minModel.getEdgeList()) {
			edgeList.add(cable);
		}
		
		ArrayList<MinimumNetwork> listOfNetworks = new ArrayList<MinimumNetwork>();
		while(!holonObjectList.isEmpty()) {
			//lookAt the first holonObject and find his neighbors
			HolonObject lookAtObject = holonObjectList.get(0);
			//delete out of list
			holonObjectList.remove(0);
			//create a new Network
			MinimumNetwork actualNetwork = new MinimumNetwork(new ArrayList<HolonObject>(), new ArrayList<IntermediateCableWithState>());
			actualNetwork.getHolonObjectList().add(lookAtObject);
			//create List of neighbors
			LinkedList<AbstractCpsObject> neighbors = new LinkedList<AbstractCpsObject>();
			populateListOfNeighbors(edgeList, lookAtObject, actualNetwork, neighbors);
			while(!neighbors.isEmpty()) {
				AbstractCpsObject lookAtNeighbor = neighbors.getFirst();
				if(lookAtNeighbor instanceof HolonObject) {
					actualNetwork.getHolonObjectList().add((HolonObject) lookAtNeighbor);
					holonObjectList.remove(lookAtNeighbor);
				}
				//When HolonSwitch Check if closed
				if(!(lookAtNeighbor instanceof HolonSwitch) || ((HolonSwitch)lookAtNeighbor).getState(Iteration)) {
					populateListOfNeighbors(edgeList, lookAtNeighbor, actualNetwork, neighbors);
				}
				
				neighbors.removeFirst();
			}
			listOfNetworks.add(actualNetwork);
		}	
		if(leftOver!= null) {
			leftOver.clear();
			for(IntermediateCableWithState cable: edgeList) {
				leftOver.add(cable);
			}
		}
		return listOfNetworks;
	}
	/**
	 * Adds the neighbors.
	 * @param edgeList
	 * @param lookAtObject
	 * @param actualNetwork
	 * @param neighbors
	 */
	void populateListOfNeighbors(ArrayList<IntermediateCableWithState> edgeList, AbstractCpsObject lookAtObject,
			MinimumNetwork actualNetwork, LinkedList<AbstractCpsObject> neighbors) {
		ListIterator<IntermediateCableWithState> iter = edgeList.listIterator();
		while(iter.hasNext())
		{
			IntermediateCableWithState lookAtEdge = iter.next();
			if(lookAtEdge.getState() == CableState.Working && lookAtEdge.getModel().isConnectedTo(lookAtObject)) {
				iter.remove();
				actualNetwork.getEdgeList().add(lookAtEdge);
				
				//Add neighbar
				AbstractCpsObject edgeNeighbor;
				if(lookAtEdge.getModel().getA().equals(lookAtObject)) {
					edgeNeighbor = lookAtEdge.getModel().getB();
					
				}else {
					edgeNeighbor = lookAtEdge.getModel().getA();
				}
				if(!neighbors.contains(edgeNeighbor)) {
					neighbors.add(edgeNeighbor);
				}
			}
		}
	}

	/**
	 * Get all Subnets.Not functional.
	 *
	 * @return all Subnets
	 */
	@Deprecated
	public ArrayList<SubNet> getSubNets() {
		return new ArrayList<SubNet>();
	}



	public FlexiblePane getFlexiblePane() {
		return flexPane;
	}

	void setFlexiblePane(FlexiblePane fp) {
		flexPane = fp;
	}

	
	public DecoratedState getActualDecorState() {
		return getDecorState(timeStep);
	}
	public VisualRepresentationalState getActualVisualRepresentationalState(){
		return savesVisual.getOrDefault(timeStep, null);
	}
	
	public DecoratedState getDecorState(int timestep) {
		return saves.getOrDefault(timestep, null);
	}
	public VisualRepresentationalState getVisualRepresentationalState(int timestep) {
		return savesVisual.getOrDefault(timestep, null);
	}


}
