package ui.view.main.old;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.TimerTask;

import javax.swing.CellEditor;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.Timer;

import classes.AbstractCanvasObject;
import classes.Edge;
import classes.GroupNode;
import classes.HolonElement;
import classes.HolonObject;
import classes.Node;
import classes.Position;
import model.Model;
import ui.controller.Control;
import ui.controller.UpdateController;

/**
 * Collection of methods and values needed in both <code>MyCanvas</code> and
 * <code>UpperNodeCanvas</code>
 * <p>
 * Although Java works on references we chose to add explicit return values for
 * clearer code understanding in most cases
 *
 * @author: I. Dix
 */
public abstract class AbstractCanvas extends JPanel {
	/**
	 * Version
	 */
	private static final long serialVersionUID = 1L;
	
	final JMenuItem itemCut = new JMenuItem("Cut");
	final JMenuItem itemCopy = new JMenuItem("Copy");
	public final JMenuItem itemPaste = new JMenuItem("Paste");
	final JMenuItem itemDelete = new JMenuItem("Delete");
	final JMenuItem itemGroup = new JMenuItem("Group");
	final JMenuItem itemUngroup = new JMenuItem("Ungroup");
	final JMenuItem itemAlign = new JMenuItem("Align selected");
	final JMenuItem itemCreateTemplate = new JMenuItem("Create Template");
	
	final int ANIMTIME = 500; // animation Time
	private final int animFPS = 60;
	final int animDelay = 1000 / animFPS; // animation Delay
	protected Model model;
	protected Control controller;
	protected int x = 0;
	protected int y = 0;
	// Selection
	public AbstractCanvasObject tempCps = null;
	UpdateController updCon;
	
	//Replacement
	/**
	 * the CpsObject that might be replaced by drag&drop
	 */
	public AbstractCanvasObject mayBeReplaced = null;
	
	// PopUpMenu
	JPopupMenu popmenu = new JPopupMenu();
	// Tooltip
	boolean toolTip; // Tooltip on or off
	Position toolTipPos = new Position(); // Tooltip Position
	String toolTipText = "";
	ArrayList<HolonElement> dataSelected = new ArrayList<>();
	public ArrayList<AbstractCanvasObject> tempSelected = new ArrayList<>();
	boolean[] showedInformation = new boolean[5];
	boolean showConnectionInformation;
	boolean dragging = false; // for dragging
	boolean dragged = false; // if an object/objects was/were dragged
	boolean drawEdge = false; // for drawing edges
	boolean doMark = false; // for double click
	public Edge edgeHighlight = null;
	Point mousePosition = new Point(); // Mouse Position when
	ArrayList<Position> savePos;
	// edge Object Start Point
	int cx, cy;
	int sx, sy; // Mark Coords
	Position unPos;
	// Animation
	Timer animT; // animation Timer
	int animDuration = ANIMTIME; // animation Duration
	int animSteps = animDuration / animDelay; // animation Steps;
	ArrayList<AbstractCanvasObject> animCps = null;
	// Graphics
	Image img = null; // Contains the image to draw on the Canvas
	Graphics2D g2; // For Painting
	float scalediv20;
	// Mouse
	private boolean click = false;

	// ------------------------------------------ METHODS
	// ------------------------------------------
	

	
	

	class ACpsHandle{
		public AbstractCanvasObject object;
		ACpsHandle(AbstractCanvasObject object){
			this.object = object;
		}
		public String toString() {
			return object.toString();
		}
	}

	void drawMarker() {
		if (sx > x && sy > y) {
			g2.drawRect(x, y, sx - x, sy - y);
		} else if (sx < x && sy < y) {
			g2.drawRect(sx, sy, x - sx, y - sy);
		} else if (sx >= x) {
			g2.drawRect(x, sy, sx - x, y - sy);
		} else if (sy >= y) {
			g2.drawRect(sx, y, x - sx, sy - y);
		}
	}
	/**
	 * @deprecated
	 * @param g
	 */
	void showTooltip(Graphics g) {
		if (toolTip) {
			g2.setColor(new Color(255, 225, 150));
			g2.setStroke(new BasicStroke(1));
			int textWidth = g.getFontMetrics().stringWidth(toolTipText) + 2; // Text
			// width

			// fixed x and y Position to the screen
			int fixXPos = toolTipPos.x - (textWidth >> 1) + model.getScaleDiv2();
			int fixYPos = toolTipPos.y;

			if (fixXPos < 0) {
				fixXPos = 0;
			} else if (fixXPos + textWidth + 1 > this.getWidth()) {
				fixXPos -= (fixXPos + textWidth + 1) - this.getWidth();
			}
			if (fixYPos + 16 > this.getHeight()) {
				fixYPos -= (fixYPos + 16) - this.getHeight();
			}
			g2.fillRect(fixXPos, fixYPos, textWidth, 15);
			g2.setColor(Color.BLACK);
			g2.drawRect(fixXPos, fixYPos, textWidth, 15);
			g2.drawString(toolTipText, fixXPos + 2, fixYPos + 12);
		}
	}



	void setRightClickMenu(MouseEvent e) {
		if (e.getButton() == MouseEvent.BUTTON3) {
			itemPaste.setEnabled(true);
			if (tempCps != null) {
				itemPaste.setEnabled(true);
				itemDelete.setEnabled(true);
				itemCut.setEnabled(true);
				itemCopy.setEnabled(true);
				itemAlign.setEnabled(true);
				// tracking
				if (tempCps != null) {
					itemGroup.setEnabled(true);
				}
				// ungrouping
				if (tempCps instanceof GroupNode)
					itemUngroup.setEnabled(true);
				else
					itemUngroup.setEnabled(false);
				if (model.getSelectedCpsObjects().size() == 0) {
					controller.addSelectedObject(tempCps);
				}
				if (tempCps instanceof HolonObject) {
					itemCreateTemplate.setEnabled(true);
				} else {
					itemCreateTemplate.setEnabled(false);
				}
			} else {
				itemAlign.setEnabled(false);
				itemCut.setEnabled(false);
				itemCopy.setEnabled(false);
				itemGroup.setEnabled(false);
				itemUngroup.setEnabled(false);
				itemCreateTemplate.setEnabled(false);
				if (edgeHighlight != null) {
					itemDelete.setEnabled(true);
					itemPaste.setEnabled(false);
				} else {
					itemDelete.setEnabled(false);
					itemPaste.setEnabled(true);
				}
			}
			mousePosition = this.getMousePosition();
			popmenu.show(e.getComponent(), e.getX(), e.getY());
		}
	}

	void markObjects() {
		if (doMark) {
			doMark = false;
			for (AbstractCanvasObject cps : tempSelected) {
				if (!model.getSelectedCpsObjects().contains(cps)) {
					controller.addSelectedObject(cps);
				}
			}
			controller.getObjectsInDepth();
			tempSelected.clear();
		}
	}


	int[] determineMousePositionOnEdge(Edge p) {
		int lx, ly, hx, hy;

		if (p.getA().getPosition().x > p.getB().getPosition().x) {
			hx = p.getA().getPosition().x + model.getScaleDiv2() + 7;
			lx = p.getB().getPosition().x + model.getScaleDiv2() - 7;
		} else {
			lx = p.getA().getPosition().x + model.getScaleDiv2() - 7;
			hx = p.getB().getPosition().x + model.getScaleDiv2() + 7;
		}
		if (p.getA().getPosition().y > p.getB().getPosition().y) {
			hy = p.getA().getPosition().y + model.getScaleDiv2() + 7;
			ly = p.getB().getPosition().y + model.getScaleDiv2() - 7;
		} else {
			ly = p.getA().getPosition().y + model.getScaleDiv2() - 7;
			hy = p.getB().getPosition().y + model.getScaleDiv2() + 7;
		}

		return new int[] { lx, ly, hx, hy };
	}

	/**
	 * Checks if a double click was made.
	 *
	 * @return true if doublecklick, false if not
	 */
	boolean doubleClick() {
		if (click) {
			click = false;
			return true;
		} else {
			click = true;
			java.util.Timer t = new java.util.Timer("doubleclickTimer", false);
			t.schedule(new TimerTask() {
				@Override
				public void run() {
					click = false;
				}
			}, 500);
		}
		return false;
	}

	boolean setToolTipInfoAndPosition(boolean on, AbstractCanvasObject cps) {
		if (x - controller.getScale() <= cx && y - controller.getScale() <= cy && x >= cx && y >= cy) {
			on = true;
			toolTipPos.x = cps.getPosition().x - controller.getScaleDiv2();
			toolTipPos.y = cps.getPosition().y + controller.getScaleDiv2();
			toolTipText = cps.getName() + ", " + cps.getId();
		}

		return on;
	}

	abstract void drawDeleteEdge();

	void triggerUpdateController() {
		updCon.paintProperties(tempCps);
		updCon.refreshTableHolonElement(model.getMultiTable(), model.getSingleTable());
		updCon.refreshTableProperties(model.getPropertyTable());
	}
	
	
	/**
	 * Checks if {@code draggedCps} or a new cpsObject at Position (x,y) could replace exactly one object
	 * in {@code objects}.
	 * Saves the object that would be replaced in {@link AbstractCanvas}.{@code MayBeReplaced}
	 * @param objects list of objects that could be replaced
	 * @param draggedCps Object that might replace
	 * @param x Position of the objects that might replace
	 * @param y Position of the objects that might replace
	 * @return true if exactly one Object could be replaced 
	 */
	protected boolean checkForReplacement(ArrayList<AbstractCanvasObject> objects, AbstractCanvasObject draggedCps, int x, int y){
		
		/** distance treshold for replacement */
		int treshhold = controller.getScale()/2;
						
		/** number of Objects that might be replaced (should be 1) */
		int replaceCounter = 0;
		
		/** last object that could be replaced */
		AbstractCanvasObject toBeReplaced = null;
		
		/** Position of object that might be replaced */
		Position p;
		
		/** for each cps on Canvas */
		if(draggedCps == null || !(draggedCps instanceof Node) && !(draggedCps instanceof Node)){
			for (AbstractCanvasObject cps : objects){
				/** same object -> ignore */
				if(cps == draggedCps)continue;
						
				/** set Position of object that might be replaced */
				p = cps.getPosition();
				
				/** if near enough */
				if(Math.abs(x-p.x)<treshhold && Math.abs(y-p.y)<treshhold){
					replaceCounter++;
					toBeReplaced = cps;
					
					/**
					 * if too many Objects could be replaced:
					 * stop searching, because it would not be clear which one should
					 * be replaced
					 */
					if(replaceCounter>1)break;
				}
			}
		}
		/**
		 * return true if exactly one obect would be replaced
		 */
		if( replaceCounter == 1 && toBeReplaced != null){
			mayBeReplaced = toBeReplaced;
			return true;
		}else{
			mayBeReplaced = null;
			return false;
		}
	}
	
	/**
	 * Checks if an inserted new Object could replace exactly one object on the canvas.
	 * Saves the object that would be replaced in {@link AbstractCanvas}.{@code MayBeReplaced}
	 * @param x Position of the objects that might replace
	 * @param y Position of the objects that might replace
	 * @return true if exactly one Object could be replaced 
	 */
	public abstract boolean checkForReplacement(int x, int y);
	
		
	/**
	 * highlights the object that mayBeReplaced
	 * @param g2
	 */
	protected void highlightMayBeReplaced(Graphics2D g2) {
		if(mayBeReplaced != null){
			g2.setColor(Color.RED);
			g2.fillRect(
					(int) (mayBeReplaced.getPosition().x
							- controller.getScaleDiv2() - (scalediv20 + 3)),
					(int) (mayBeReplaced.getPosition().y
							- controller.getScaleDiv2() - (scalediv20 + 3)),
					(int) (controller.getScale() + ((scalediv20 + 3) * 2)),
					(int) (controller.getScale() + ((scalediv20 + 3) * 2)));
		}
	}
	
	/**
	 * Align alle Objects on the Canvas to a Grid with objects every 10 pixels
	 */
	public abstract void tryToAlignObjects();

	/**
	 * Aligns the Object the a grid
	 * @param cps Object that should be aligned
	 * @param distance distance between the AlignmentGrid Lines. (objects every 'distance' pixels
	 */
	protected void align(AbstractCanvasObject cps, int distance) {
		/** Position of the AbstractCpsObject which should be aligned */
		Position p = cps.getPosition();
		
		//calculate how many pixels the cps should be decreased to align
		/** x offset relative to a grid with lines every distance pixels */
		int x_off = cps.getPosition().x % distance;
		
		/** y offset relative to a grid with lines every distance pixels */
		int y_off = cps.getPosition().y % distance;
		
		//align to the other Line, if it is nearer
		if(x_off > distance/2)
			x_off -= distance;
		if(y_off > distance/2)
			y_off -= distance;
		
		/** set new Position */
		cps.setPosition(p.x-x_off, p.y-y_off);
	}
	
	/**
	 * Stops Editing in HolonElementTable and PropertyTable
	 */
	protected void stopEditing() {
		/**
		 * Stop Editing, if mouse exits the Table
		 */
		JTable holElem = model.getTableHolonElement();
		CellEditor cellEditor = holElem.getCellEditor();
		if (cellEditor != null) {
		    if (cellEditor.getCellEditorValue() != null) {
		    	/** TODO: Maybe try to save current Data */
		        cellEditor.stopCellEditing();
		    } else {
		        cellEditor.cancelCellEditing();
		    }
		}
		
		JTable propertys = model.getTableProperties();
		cellEditor = propertys.getCellEditor();
		if (cellEditor != null) {
		    if (cellEditor.getCellEditorValue() != null) {
		    	/** TODO: Maybe try to save current Data */
		        cellEditor.stopCellEditing();
		    } else {
		        cellEditor.cancelCellEditing();
		    }
		}
	}

	/**
	 * Closes a tab of the UpperNode with ID upperNodeID
	 * @param upperNodeId
	 */
	public abstract void closeUpperNodeTab(int upperNodeId);
}