package model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.stream.Collectors;

import classes.AbstractCanvasObject;
import classes.Edge;
import classes.Node;
import model.ExitCable.ExitCableState;
import classes.GroupNode;

public class VisualRepresentationalState {
	private ArrayList<Supplier> supplierList = new ArrayList<Supplier>();
	private ArrayList<Passiv> passivList= new ArrayList<Passiv>();
	private ArrayList<Consumer> consumerList= new ArrayList<Consumer>();
	private ArrayList<Node> nodeList= new ArrayList<Node>();
	private ArrayList<DecoratedCable> cableList= new ArrayList<DecoratedCable>();
	private ArrayList<DecoratedSwitch> switchList= new ArrayList<DecoratedSwitch>();
	private ArrayList<DecoratedGroupNode> groupNodeList= new ArrayList<DecoratedGroupNode>();
	private ArrayList<ExitCable> exitCableList= new ArrayList<ExitCable>();
	
	//ForFastAccessIndividualGroupNodes:
	private HashMap<GroupNode, DecoratedGroupNode> createdGroupNodes;
	
	public VisualRepresentationalState(DecoratedState stateFromThisTimestep, MinimumModel minimumModel) {
		reassignObjects(stateFromThisTimestep, minimumModel);
	}


	//Getter:
	public ArrayList<Supplier> getSupplierList() {
		return supplierList;
	}
	public ArrayList<Passiv> getPassivList() {
		return passivList;
	}
	public ArrayList<Consumer> getConsumerList() {
		return consumerList;
	}
	public ArrayList<Node> getNodeList() {
		return nodeList;
	}
	public ArrayList<DecoratedCable> getCableList() {
		return cableList;
	}
	public ArrayList<DecoratedSwitch> getSwitchList() {
		return switchList;
	}
	public ArrayList<DecoratedGroupNode> getGroupNodeList() {
		return groupNodeList;
	}
	
	//Reassignments:
	private void reassignObjects(DecoratedState stateFromThisTimestep, MinimumModel minimumModel) {
		HashMap<AbstractCanvasObject, GroupNode> inGroupObjects = minimumModel.getInGroupObjects();
		
		//generate CableLookUp
		HashMap<Edge, ArrayList<GroupNode>> inGroupEdges = minimumModel.getInGroupEdges();
		
		createdGroupNodes =  new HashMap<GroupNode, DecoratedGroupNode>();
		
		ArrayList<IntermediateCalculationCable> exitCables = new ArrayList<IntermediateCalculationCable>();
		//createThem
		for(GroupNode groupNode :  minimumModel.getUppderNodeList()) {
			createdGroupNodes.put(groupNode, new DecoratedGroupNode(groupNode));
		}
		//unrolling Networks
		for(DecoratedNetwork net : stateFromThisTimestep.getNetworkList()) {
			for(Consumer con : net.getConsumerList()) {
				DecoratedGroupNode groupNodeFromObject = addObject(inGroupObjects, con.getModel(), consumerList, con, createdGroupNodes);
				if(groupNodeFromObject != null) {
					addToGroupNode(con, groupNodeFromObject.getConsumerList());
				}
			}
			for(Consumer con : net.getConsumerSelfSuppliedList()) {
				DecoratedGroupNode groupNodeFromObject = addObject(inGroupObjects, con.getModel(), consumerList, con, createdGroupNodes);
				if(groupNodeFromObject != null) {
					addToGroupNode(con, groupNodeFromObject.getConsumerList());
				}
			}
			for(Supplier sup : net.getSupplierList()) {
				DecoratedGroupNode groupNodeFromObject = addObject(inGroupObjects, sup.getModel(), supplierList, sup, createdGroupNodes);
				if(groupNodeFromObject != null) {
					addToGroupNode(sup, groupNodeFromObject.getSupplierList());
				}
			}
			for(Passiv pas : net.getPassivNoEnergyList()) {
				DecoratedGroupNode groupNodeFromObject = addObject(inGroupObjects, pas.getModel(), passivList, pas, createdGroupNodes);
				if(groupNodeFromObject != null) {
					addToGroupNode(pas, groupNodeFromObject.getPassivList());
				}
			}
			for(DecoratedCable cable : net.getDecoratedCableList()) {
				addCable(cable, inGroupEdges, inGroupObjects,createdGroupNodes, exitCables);
			}
		}
		for(DecoratedCable cable : stateFromThisTimestep.getLeftOverEdges()) {
			addCable(cable, inGroupEdges, inGroupObjects, createdGroupNodes, exitCables);
		}
		for(Node node : minimumModel.getNodeList()) {
			DecoratedGroupNode groupNodeFromObject = addObject(inGroupObjects, node, nodeList ,node, createdGroupNodes);
			if(groupNodeFromObject != null) {
				addToGroupNode(node, groupNodeFromObject.getNodeList());
			}
		}
		for(DecoratedSwitch dSwitch: stateFromThisTimestep.getDecoratedSwitches()) {
			DecoratedGroupNode groupNodeFromObject = addObject(inGroupObjects, dSwitch.getModel(), switchList, dSwitch, createdGroupNodes);
			if(groupNodeFromObject != null) {
				addToGroupNode(dSwitch, groupNodeFromObject.getSwitchList());
			}
		}
		for(DecoratedGroupNode dGroupNode: createdGroupNodes.values()) {
			DecoratedGroupNode groupNodeFromObject = addObject(inGroupObjects, dGroupNode.getModel(), groupNodeList, dGroupNode, createdGroupNodes);
			if(groupNodeFromObject != null) {
				addToGroupNode(dGroupNode, groupNodeFromObject.getGroupNodeList());
			}
		}
		//Create TreeNodeModel:
		HashMap<GroupNode, TreeNode<TreeGroupNodeData>> fastaccess= new HashMap<GroupNode, TreeNode<TreeGroupNodeData>>();
		TreeNode<TreeGroupNodeData> root = new TreeNode<TreeGroupNodeData>(null, new ArrayList<TreeNode<TreeGroupNodeData>>(), new TreeGroupNodeData(null, 0));
		fastaccess.put(null, root);
		for(DecoratedGroupNode dGroupNode: getGroupNodeList()) {
			addTreeNode(root, dGroupNode, 1, fastaccess);
		}
		for(IntermediateCalculationCable cable : exitCables) {
			createExitEdgesV2(root,cable.getCable() , cable.getInsideObject(),cable.getInsideUpperNode(),cable.getOusideObject(),cable.getOutsideUpperNode(), fastaccess);			
		}
	}


	private void createExitEdgesV2(TreeNode<TreeGroupNodeData> root, DecoratedCable cable,  AbstractCanvasObject insideObject,
			GroupNode insideUpperNode, AbstractCanvasObject ousideObject, GroupNode outsideUpperNode, HashMap<GroupNode, TreeNode<TreeGroupNodeData>> fastaccess) {
		
		
		
		
		//Create Up List
		LinkedList<TreeNode<TreeGroupNodeData>> listFromStart = createList(insideUpperNode, fastaccess);
		LinkedList<TreeNode<TreeGroupNodeData>> listFromEnd = createList(outsideUpperNode, fastaccess);
		LinkedList<TreeNode<TreeGroupNodeData>> common = new LinkedList<TreeNode<TreeGroupNodeData>>(listFromStart);
		common.retainAll(listFromEnd);
		TreeNode<TreeGroupNodeData> firstCommon = common.getFirst();
		LinkedList<TreeNode<TreeGroupNodeData>> resultList = new LinkedList<TreeNode<TreeGroupNodeData>>();
		//Add from listFromStart till firstCommon
		createresultList(listFromStart, firstCommon, resultList);		
		//Add firstCommon
		resultList.add(firstCommon);
		//Add from listFromEnd till firstCommon
		createresultList(listFromEnd, firstCommon, resultList);
		LinkedList<NodeInfo> infoList = new LinkedList<NodeInfo>();
		//Categorize:
		ListIterator<TreeNode<TreeGroupNodeData>> iter = resultList.listIterator();
		
		while(iter.hasNext()) {
			//categorize
			TreeNode<TreeGroupNodeData> actual, next = null, previous = null;
			if(iter.hasPrevious()) {
				previous =iter.previous();
				iter.next();
			}
			actual = iter.next();
			if(iter.hasNext()) {
				next =iter.next();
				iter.previous();
			}
			NodeInfo actualInfo = new NodeInfo(actual.getData().groupNode);
			if(previous!= null) {
				actualInfo.previousGroupNode = previous.getData().groupNode;
				if(previous == actual.getParent()) {
					actualInfo.previous = Info.Parent;
				}else {
					actualInfo.previous = Info.Child;
				}
			}
			if(next!= null) {
				actualInfo.nextGroupNode = next.getData().groupNode;
				if(next == actual.getParent()) {
					actualInfo.next = Info.Parent;
				}else {
					actualInfo.next = Info.Child;
				}
			}
			infoList.add(actualInfo);
		}
		for(NodeInfo info: infoList) {
			DecoratedGroupNode group = this.createdGroupNodes.get(info.groupNode);
			ArrayList<ExitCable> mylist;
			if(group == null) {
				mylist = this.getExitCableList();
			}else{
				mylist = group.getExitCableList();
			}
			ExitCableState state =null;
			if(info.previous == Info.Nothing) {
				if(info.next == Info.Child) {
					state = ExitCableState.DOWN;
					mylist.add(new ExitCable(state, insideObject, info.nextGroupNode, cable));
				}else if(info.next == Info.Parent) {
					state = ExitCableState.UP;
					mylist.add(new ExitCable(state, insideObject, ousideObject, cable));
				}else {
					System.out.println("Error in VisualState");
				}
			}else if(info.previous == Info.Child) {
				if(info.next == Info.Child) {
					state = ExitCableState.DOWNDOWN;
					mylist.add(new ExitCable(state, info.previousGroupNode, info.nextGroupNode, cable));
				}else if(info.next == Info.Parent) {
					state = ExitCableState.DOWNUP;
					mylist.add(new ExitCable(state, info.previousGroupNode, ousideObject, cable));
				}else {
					state = ExitCableState.DOWN;
					mylist.add(new ExitCable(state, info.previousGroupNode, ousideObject, cable));
				}
			}else {//(info.previous == Info.Parent)
				if(info.next == Info.Child) {
					state = ExitCableState.DOWNUP;
					mylist.add(new ExitCable(state, info.nextGroupNode, insideObject, cable));
				}else if(info.next == Info.Parent) {
					System.out.println("Error in VisualState");
				}else {
					state = ExitCableState.UP;
					mylist.add(new ExitCable(state, ousideObject, insideObject, cable));
				}
			}
		}
	}


	private void createresultList(LinkedList<TreeNode<TreeGroupNodeData>> list,
			TreeNode<TreeGroupNodeData> firstCommon, LinkedList<TreeNode<TreeGroupNodeData>> resultList) {
		for(TreeNode<TreeGroupNodeData> node: list) {
			if(node == firstCommon) {
				break;
			}
			resultList.add(node);
		}
	}


	private LinkedList<TreeNode<TreeGroupNodeData>> createList(GroupNode insideUpperNode,
			HashMap<GroupNode, TreeNode<TreeGroupNodeData>> fastaccess) {
		TreeNode<TreeGroupNodeData> actualNode = fastaccess.get(insideUpperNode);
		LinkedList<TreeNode<TreeGroupNodeData>> list = new LinkedList<TreeNode<TreeGroupNodeData>>();
		list.add(actualNode);
		while(actualNode.getParent() != null) {
			actualNode = actualNode.getParent();
			list.add(actualNode);
		}
		return list;
	}


	private void addCable(DecoratedCable cable, HashMap<Edge, ArrayList<GroupNode>> inGroupEdges, HashMap<AbstractCanvasObject, GroupNode> inGroupObjects,
			HashMap<GroupNode, DecoratedGroupNode> createdGroupNodes, ArrayList<IntermediateCalculationCable> exitCables) {
		boolean isInGroup = false;
		if(inGroupObjects.containsKey(cable.getModel().getA())) {
			isInGroup = true;
		}
		if(inGroupObjects.containsKey(cable.getModel().getB())) {
			isInGroup = true;
		}
		if(isInGroup) {
			
			boolean isIntern = inGroupObjects.get(cable.getModel().getA()) == inGroupObjects.get(cable.getModel().getB()); //Case null == null is not possible trough before Filtering MinimumModel#addUpperObjects(CpsUpperNode)
			if(isIntern) {
				DecoratedGroupNode groupNodeFromBoth = createdGroupNodes.get(inGroupObjects.get(cable.getModel().getA()));
				groupNodeFromBoth.getInternCableList().add(cable);				
			}else {
				if(inGroupObjects.containsKey(cable.getModel().getA())) {
					exitCables.add(new IntermediateCalculationCable(cable, inGroupObjects.get(cable.getModel().getA()),inGroupObjects.get(cable.getModel().getB()), cable.getModel().getA(), cable.getModel().getB()));
				} else if(inGroupObjects.containsKey(cable.getModel().getB())) {
					exitCables.add(new IntermediateCalculationCable(cable, inGroupObjects.get(cable.getModel().getB()),inGroupObjects.get(cable.getModel().getA()), cable.getModel().getB(), cable.getModel().getA()));
				}
				
			}
		}else {
			cableList.add(cable);					
		}
	}

	private <DecoratedObject> void addToGroupNode(DecoratedObject object, ArrayList<DecoratedObject> groupNodeListPar) {
		groupNodeListPar.add(object);
	}
	
	
	
	
	public int getAmountfOfGroupNodes() {
		return groupNodeList.stream().map(groupNode -> groupNode.getAmountOfGroupNodes()).reduce(0, Integer::sum);
	}
	
	
	
	public float getConsumptionFromConsumer() {		
		return consumerList.stream().map(con -> con.getEnergyNeededFromNetwork()).reduce(0.f, Float::sum)+
				groupNodeList.stream().map(groupNode -> groupNode.getConsumptionFromConsumer()).reduce(0.f, Float::sum);
	}
	public float getProductionFromSupplier() {		
		return supplierList.stream().map(sup -> sup.getEnergyToSupplyNetwork()).reduce(0.f, Float::sum)+
				groupNodeList.stream().map(groupNode -> groupNode.getProductionFromSupplier()).reduce(0.f, Float::sum);
	}
	
	public float getAverageConsumption() {
		return getConsumptionFromConsumer() / (float)getAmountfOfGroupNodes();
	}
	public float getAverageProduction() {
		return getProductionFromSupplier() / (float)getAmountfOfGroupNodes();
	}
	
	
	
	
	

	//Generics
	private <ModelOfObject, DecoratedObject> DecoratedGroupNode addObject(HashMap<ModelOfObject, GroupNode> inGroupObjects, ModelOfObject modelOfObject, ArrayList<DecoratedObject> listToAdd, DecoratedObject object, HashMap<GroupNode, DecoratedGroupNode> createdGroupNodes) {
		if(inGroupObjects.containsKey(modelOfObject)) {
			return  createdGroupNodes.get(inGroupObjects.get(modelOfObject));
		}
		listToAdd.add(object);
		return null;
	}

	public enum Info{
		Nothing, Parent, Child
	}
	private class NodeInfo{
		public GroupNode groupNode;
		public Info previous = Info.Nothing;
		public Info next = Info.Nothing;
		public GroupNode previousGroupNode = null;
		public GroupNode nextGroupNode = null;
		
		public NodeInfo(GroupNode groupNode) {
			this.groupNode = groupNode;
		}
		public String toString() {
			return "Previuos: " + previous.toString() + "|Next: " + next.toString();
		}
		
	}
	public HashMap<GroupNode, DecoratedGroupNode> getCreatedGroupNodes() {
		return createdGroupNodes;
	}
	private class TreeNode<T> {
		private  TreeNode<T> parentNode;
		private List<TreeNode<T>> children;
		private T data;
		
		public TreeNode( TreeNode<T> parentNode, List<TreeNode<T>> children, T data) {
			this.parentNode = parentNode;
			this.children = children;
			this.data = data;
		}
		
		//Methods
		public TreeNode<T> getParent(){
			return parentNode;
		}
		public List<TreeNode<T>> getChildren(){
			return children;
		}
		public T getData() {
			return data;
		}
		public String toString() {
			return "[" + data.toString() + " Children(" + children.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")]";
		}
	}
	private class TreeGroupNodeData{
		public GroupNode groupNode;
		public int layer;
		public TreeGroupNodeData(GroupNode groupNode, int layer) {
			this.groupNode = groupNode;
			this.layer = layer;
		}
		public String toString() {
			return "Layer:" + layer;
		}
	}
	
	private void addTreeNode(TreeNode<TreeGroupNodeData> node, DecoratedGroupNode dGroupNode, int layer, HashMap<GroupNode, TreeNode<TreeGroupNodeData>> fastaccess) {
		TreeNode<TreeGroupNodeData> newNode = new TreeNode<TreeGroupNodeData> (node, new ArrayList<TreeNode<TreeGroupNodeData>>() , new TreeGroupNodeData(dGroupNode.getModel(), layer));
		node.getChildren().add(newNode);
		fastaccess.put(newNode.data.groupNode, newNode);
		for(DecoratedGroupNode dGroupNodeIntern: dGroupNode.getGroupNodeList()) {
			addTreeNode(newNode, dGroupNodeIntern, layer+1, fastaccess);
		}
	}


	public ArrayList<ExitCable> getExitCableList() {
		return exitCableList;
	}
}

