package ui.controller;

import classes.*;
import model.DecoratedCable;
import model.DecoratedNetwork;
import model.DecoratedState;
import model.DecoratedSwitch;
import model.IntermediateCableWithState;
import model.MinimumModel;
import model.MinimumNetwork;
import model.Model;
import model.VisualRepresentationalState;
import model.DecoratedCable.CableState;
import model.DecoratedSwitch.SwitchState;
import model.Model.FairnessModel;

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


/**
 * 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 HashMap<Integer, FlexManager> savesFlexManger = new HashMap<Integer, FlexManager>();
	
	private int timeStep;

	/**
	 * Constructor.
	 *
	 * @param m
	 *            Model
	 */
	public 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
	 * @param updateVisual TODO
	 */
	public void calculateStateForTimeStep(int timestep, boolean updateVisual) {
		boolean doesFlexManagerExist = savesFlexManger.containsKey(timestep);
		boolean createNew = updateVisual || !doesFlexManagerExist;
		FlexManager newFlexManager;
		if(createNew) {
			newFlexManager = new FlexManager(model, timestep, savesFlexManger.get(timestep-1));
			if(doesFlexManagerExist) newFlexManager.orderFlexFromList(savesFlexManger.get(timestep).getAllFlexesOrderedThisTimeStep());
			savesFlexManger.put(timestep, newFlexManager);
		}else {
			newFlexManager = savesFlexManger.get(timestep);
		}
		

		
		
		
		HashMap<Edge, CableState> map = new HashMap<Edge, CableState>();
		if(timestep > 0 && saves.containsKey(timestep-1)) //if the state before exist
		{
			//make cable hastmap
			DecoratedState theStateBefore = saves.get(timestep-1);
			//edges without HolonObjects or burned
			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 BreakedManuel Cable Burned:
		for(IntermediateCableWithState cable : minimumModel.getEdgeList()) {
			if(cable.getModel().isBreakedManuel()) cable.setState(CableState.Burned);
		}
		
		//set all the state before:
		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.getEnergyAtTimeStepWithFlex(timestep, newFlexManager) > 0.0f).map(object -> object.getEnergyAtTimeStepWithFlex(timestep, newFlexManager)).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() && !aCable.getModel().isUnlimitedCapacity()).max((lhs,rhs) -> Float.compare(lhs.getEnergyFromConnetedAtTimestep(timestep, newFlexManager), rhs.getEnergyFromConnetedAtTimestep(timestep, newFlexManager))).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, newFlexManager));
		}

		
		
		
		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, newFlexManager, timestep);
		saves.put(timestep, stateFromThisTimestep);
		if(updateVisual)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<AbstractCanvasObject> neighbors = new LinkedList<AbstractCanvasObject>();
			populateListOfNeighbors(edgeList, lookAtObject, actualNetwork, neighbors);
			while(!neighbors.isEmpty()) {
				AbstractCanvasObject lookAtNeighbor = neighbors.getFirst();
				if(lookAtNeighbor instanceof HolonObject) {
					actualNetwork.getHolonObjectList().add((HolonObject) lookAtNeighbor);
					holonObjectList.remove(lookAtNeighbor);
				}else {
					actualNetwork.getNodeAndSwitches().add(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, AbstractCanvasObject lookAtObject,
			MinimumNetwork actualNetwork, LinkedList<AbstractCanvasObject> 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
				AbstractCanvasObject edgeNeighbor;
				if(lookAtEdge.getModel().getA().equals(lookAtObject)) {
					edgeNeighbor = lookAtEdge.getModel().getB();
					
				}else {
					edgeNeighbor = lookAtEdge.getModel().getA();
				}
				if(!neighbors.contains(edgeNeighbor)) {
					neighbors.add(edgeNeighbor);
				}
			}
		}
	}
	
	public DecoratedState getActualDecorState() {
		return getDecorState(timeStep);
	}
	public VisualRepresentationalState getActualVisualRepresentationalState(){
		return savesVisual.getOrDefault(timeStep, null);
	}
	public FlexManager getActualFlexManager() {
		return savesFlexManger.getOrDefault(timeStep, null);
	}
	public void resetFlexManager(){
		savesFlexManger.clear();
	}
	public void resetFlexManagerForTimeStep(int timestep) {
		savesFlexManger.get(timestep).reset();
	}
	
	public DecoratedState getDecorState(int timestep) {
		return saves.get(timestep);
	}
	public VisualRepresentationalState getVisualRepresentationalState(int timestep) {
		return savesVisual.getOrDefault(timestep, null);
	}


}
