package ui.controller;

import classes.AbstractCanvasObject;
import classes.Edge;
import classes.Node;
import classes.GroupNode;
import classes.Position;
import classes.IntermediateCalculationCable;
import ui.model.Model;

import java.awt.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;


class NodeController {

	private Model model;
	private CanvasController cvs;
	private MultiPurposeController mpC;
	private Point point;

    NodeController(Model model, CanvasController cvs, MultiPurposeController mpC) {
        this.model = model;
		this.cvs = cvs;
		this.mpC = mpC;
	}

	/**
	 * Add a CpsUpperNode into Canvas
	 */
    void doUpperNode(String nodeName, GroupNode upperNode, ArrayList<AbstractCanvasObject> toGroup) {
		GroupNode node = new GroupNode(nodeName);
		node.setPosition(calculatePos(toGroup));
		makeAdjacent(node, upperNode, toGroup);
		makeNodeOfNodes(node, upperNode, toGroup);
		if (upperNode == null)
			cvs.addNewObject(node);
		else
			addObjectInUpperNode(node, upperNode, false);

	}

	/**
	 * Delete a CpsUpperNode from the Canvas
	 */
    void undoUpperNode(GroupNode node, GroupNode upperNode) {
    	if(node.getNodes().size() == 0) {
    		cvs.deleteObjectOnCanvas(node);
    		return;
    	}
		Position old = calculatePos(node.getNodes());
		Position p = node.getPosition();
		point = new Point(old.x - p.x, old.y - p.y);

		unmakeNodesOfNodes(node, upperNode);
		if (upperNode == null)
			cvs.deleteObjectOnCanvas(node);
		else
			deleteObjectInUpperNode(node, upperNode);
	}

	/**
	 * Put selected Nodes inside the Upper Node
	 */
	private void makeNodeOfNodes(GroupNode node, GroupNode upperNode, ArrayList<AbstractCanvasObject> toGroup) {

		ArrayList<AbstractCanvasObject> nodes = node.getNodes();
	

		// Put all selected Nodes into the Upper Node
        for (AbstractCanvasObject obj : toGroup) {
            // füge Neue Objecte in Neuen Node hinzu
			addObjectInUpperNode(obj, node, false);
		}

		for (AbstractCanvasObject abs : toGroup) {
			if (upperNode == null)
				removeForNodeOfNode(abs, null);
			else
				removeForNodeOfNode(abs, upperNode);
		}

	}

	/**
	 * Transfer all relevant data from Node into the next higher layer of Node
	 * (upperNode)
	 */
	private void unmakeNodesOfNodes(GroupNode node, GroupNode upperNode) {
		// add all nodes into upperNode

		for (Edge edge : node.getConnections()) {
			if (edge.getA().equals(node))
				edge.getB().getConnections().remove(edge);
			if (edge.getB().equals(node))
				edge.getA().getConnections().remove(edge);
		}

		for (AbstractCanvasObject obj : node.getNodes()) {
			updatePosition(obj, upperNode);
			if (upperNode == null)
				obj.setSav("CVS");
			else
				obj.setSav("" + upperNode.getId());
		}

		(upperNode == null ? model.getObjectsOnCanvas() : upperNode.getNodes()).addAll(node.getNodes());
		// change the indices accordingly the higher layer
		mpC.adjustIdx(mpC.getHighestIdx((upperNode == null ? model.getCvsObjIdx() : upperNode.getNodesIdx())),
				node.getNodesIdx());
		// add all indices of nodes into upperNode
		(upperNode == null ? model.getCvsObjIdx() : upperNode.getNodesIdx()).putAll(node.getNodesIdx());

	}

	/**
	 * Look for adjacent Nodes connected with the new CpsUpperNode and make
	 * Connections
	 */
	private void makeAdjacent(GroupNode node, GroupNode upperNode, ArrayList<AbstractCanvasObject> toGroup) {

		ArrayList<AbstractCanvasObject> adj = new ArrayList<>();
		ArrayList<Float> maxCapacity = new ArrayList<>();

		
	}

	/**
	 * Just checking if an Egde already exists
	 */
    boolean lookforDuplicates(AbstractCanvasObject a, AbstractCanvasObject b, ArrayList<Edge> list) {
        for (Edge cpsEdge : list) {
			if ((a.equals(cpsEdge.getA()) && b.equals(cpsEdge.getB()))
					|| (b.equals(cpsEdge.getA()) && a.equals(cpsEdge.getB())))
				return true;
		}
		return false;
	}



	/**
	 * Calculate new Position of the Upper Node
	 */
    Position calculatePos(ArrayList<AbstractCanvasObject> toGroup) {

		Position pos = new Position(0, 0);

		// sum(x0 .. xn) / numOfPos, y analog
		for (AbstractCanvasObject abs : toGroup) {
			pos.x += abs.getPosition().x;
			pos.y += abs.getPosition().y;
		}
		pos.x /= toGroup.size();
		pos.y /= toGroup.size();

		return pos;
	}

	/**
	 * Removes the Given Obj from current Layer and adjusts the idx
	 */
	private void removeForNodeOfNode(AbstractCanvasObject obj, GroupNode upperNode) {

		mpC.decIdx(obj.getId(), (upperNode == null ? model.getCvsObjIdx() : upperNode.getNodesIdx()));
		(upperNode == null ? model.getCvsObjIdx() : upperNode.getNodesIdx()).remove(obj.getId());
		(upperNode == null ? model.getObjectsOnCanvas() : upperNode.getNodes()).remove(obj);
	}

	/**
	 * Search Lost Nodes by Backtracking by looking for edges
	 * 
	 * @param object
	 *            actual object which we are looking at
	 * @param find
	 *            one part of the Edge
	 * @param lost
	 *            other part of the Edge
	 * @return boolean if found
	 */
	private boolean backtrackLostChild(AbstractCanvasObject object, AbstractCanvasObject find, AbstractCanvasObject lost) {
		//if not instance of CpsUpperNode
		if (!(object instanceof GroupNode)) {
			//forEach connection look if lost and found inside
			for (Edge edge : object.getConnections()) {
				if (edge.getA().equals(find) && edge.getB().equals(lost)
						|| edge.getA().equals(lost) && edge.getB().equals(find))
					return true;
			}
			return false;
		} else {
			//if instance of Uppernode Call again
			for (AbstractCanvasObject cps : ((GroupNode) object).getNodes()) {
				if (backtrackLostChild(cps, find, lost))
					return true;
			}
			return false;
		}
	}


	/**
	 * Adds object to the upperNode, might replace objects if replace==true
	 * @param object
	 * @param upperNode
	 * @param replace
	 */
    void addObjectInUpperNode(AbstractCanvasObject object, GroupNode upperNode, boolean replace) {
        if(object == null){
        		new Error("object == null while adding to "+upperNode.toString()).printStackTrace();
        		return;
        	}
        if(upperNode == null){
    		new Error("upperNode == null while adding "+object.toString()).printStackTrace();
    		return;
    	}
    	object.setSav("" + upperNode.getId());
		upperNode.getNodesIdx().put(object.getId(), upperNode.getNodes().size());
		upperNode.getNodes().add(object);
		
		/**
		 * check if we should drag & drop replace
		 */
		if(replace && !(object instanceof Node) ){
			/** x of the dragged Object */
			int x = object.getPosition().x;
		
			/** y of the dragged Object */
			int y = object.getPosition().y;
			
			/** distance threshold for replacement */
			int treshhold = model.getScale()/2;
		
			/** number of Objects that might be replaced (should be 1) */
			int replaceCounter = 0;
		
			/** last object that could be replaced */
			AbstractCanvasObject toBeReplaced = null;
			
			/** for each cps on Canvas */
			for (AbstractCanvasObject cps : upperNode.getNodes()){
				
				/** same object -> ignore */
				if(cps == object)continue;
			
				/** x of object that might get replaced */
				int c_x = cps.getPosition().x;
				
				/** y of object that might get replaced */
				int c_y = cps.getPosition().y;
				
				/** if near enough */
				if(Math.abs(x-c_x)<treshhold && Math.abs(y-c_y)<treshhold){
					replaceCounter++;
					toBeReplaced = cps;
				}
			}
			/** if replacement of exactly one object possible */
			if(replaceCounter == 1 && toBeReplaced != null){
				replaceObjectInUpperNode(toBeReplaced, object, upperNode);
			}
		}
	}

	/**
	 * Delete a AbstactCpsObject from CPSUpperNode
	 */
    void deleteObjectInUpperNode(AbstractCanvasObject object, GroupNode upperNode) {
		LinkedList<Edge> edgesToDelete = new LinkedList<Edge>();
		for (Edge p : model.getEdgesOnCanvas()) {
			if(p.isConnectedTo(object)) {
				edgesToDelete.add(p);
			}
		}
		model.getEdgesOnCanvas().removeAll(edgesToDelete);				
		mpC.decIdx(object.getId(), upperNode.getNodesIdx());
		upperNode.getNodesIdx().remove(object.getId());
		upperNode.getNodes().remove(object);
	}

    /**
     * Replaces {@code toBePlaced} by {@code by} in {@code upperNode}
     * @param toBeReplaced
     * @param by
     * @param upperNode
     */
	public void replaceObjectInUpperNode(AbstractCanvasObject toBeReplaced,
			AbstractCanvasObject by, GroupNode upperNode) {
		/** let all edges of 'toBeReplaced' connect to 'by' */
		for(Edge e: toBeReplaced.getConnections()){
			if(e.getA() == toBeReplaced){
				e.setA(by);
			}else if(e.getB() == toBeReplaced){
				e.setB(by);
			}
			
			/** if edge from an object to itself -> remove it */
			if(e.getA() == e.getB())
				;//TODO Delte me if not nessesary
			else/** else add edge to 'by' */
				by.addConnection(e);
		}
		/** delete 'toBeReplaced' new empty connections, to prevent Nullpointer*/
		toBeReplaced.setConnections(new ArrayList<Edge>(1));
		/**
		 * set Position of by to exactly toBeReplaced
		 */
		by.setPosition(toBeReplaced.getPosition());
		deleteObjectInUpperNode(toBeReplaced, upperNode);
	}

	
	
	/**
	 * If Position is out of boundaries adjust it
	 */
	private void updatePosition(AbstractCanvasObject temp, GroupNode upperNode) {
		int x = temp.getPosition().x - point.x;
		int y = temp.getPosition().y - point.y;

		if (y < 0)
			y = 0 + model.getScaleDiv2() + 1;
		if (upperNode != null) {
			if (x < upperNode.getLeftBorder() + model.getScaleDiv2() + 1)
				x = upperNode.getLeftBorder() + model.getScaleDiv2() + 1;
		} else if (x < 0)
			x = 0 + model.getScaleDiv2() + 1;
		if (x > model.getCanvasX())
			x = model.getCanvasX() - model.getScaleDiv2() - 1;
		if (y > model.getCanvasX())
			y = model.getCanvasY() - model.getScaleDiv2() - 1;

		temp.setPosition(new Position(x, y));

	}
}
