package cnp.ew.diagram;

import java.awt.*;
import java.awt.image.*;
import java.util.*;
import cnp.ew.scrolling.*;
import cnp.ew.util.*;
import cnp.ew.lightweight.*;
import cnp.ew.displayer.*;

public class CpDiagram extends CpAbstractScrollable
{
    public static final int LEFT = 0;
    public static final int RIGHT = 1;
    public static final int CENTER_X = 2;
    public static final int CENTER_Y = 3;
    public static final int TOP = 4;
    public static final int BOTTOM = 5;
    public static final int HORIZONTAL = 6;
    public static final int VERTICAL = 7;
    public static final int BOTH = 8;

    public static final int HANDLEDRAG = 1;
	static final int RUBBERBAND = 3;
	static final int NORMAL = 4;
	static final int HIDEDOTS = 5;

	Vector selectedLcs;
	Vector loadedLcs;
	Vector clipboard;
	boolean showGrid;
	Point gridSize = new Point(20, 20);
	public int mode;
	public CpHandle currentHandle;
    CpRubberRectangleLc rubberBandLc;
    CpDiagrammableLc loadedLc, lastHit;
	Color gridColor = new Color(240, 240, 240);
	Color handleColor = Color.darkGray;
	boolean selectionChanged;
	boolean isDirty;

	CpBackgroundLc backgroundLc;

    public CpDiagram()
    {
        super();
		showGrid = false;
        newDiagram();
    }

    void newDiagram()
    {
        enableDamageRepair(false);
        remove((Vector)getChildren().clone()); // SHOULD HAVE removeAllChildren() in abstract lc
        if ((int)getScale() != 1) {
            scale(1);
        }
		selectedLcs = new Vector();
		loadedLcs = new Vector();
		if (backgroundLc != null) {
		    add(backgroundLc);
		}
		mode = NORMAL;
		isDirty = false;
		// Should we scroll to 0,0 here?
	    resize(2000, 2000);  // THIS SHOULD BE BASED ON KIDS
        enableDamageRepair(true);
        repaint();
	}

    public void printKids()
    {
        for (int i = 0; i < getChildren().size(); i++) {
            CpDiagrammableLc lc = (CpDiagrammableLc)getChildren().elementAt(i);
            System.out.println("  Kid: " + i + " = " + lc + " reshape(" + lc.location().x + ", " + lc.location().y + ", " + lc.size().width + ", " + lc.size().height + ")");
        }
    }

	public void setBackgroundLc(CpBackgroundLc newBackground)
	{
	    //printKids();
	    if (backgroundLc != null) {
	        remove(backgroundLc);
	    }
        backgroundLc = newBackground;
	    if (backgroundLc != null) {
	        add(backgroundLc);
	    }
	    //printKids();
	}

	public boolean checkDirty()
	{
        if (isDirty) {
           // System.out.println("Do you want to save changes?");
        }
        return false;
        // return true if cancel?
    }

    public Dimension getCellSize()
    {
        return new Dimension(5, 5);
    }

    public void setLcs(Vector newLcs)
    {
        enableDamageRepair(false);
        newDiagram();
        add(newLcs);
        enableDamageRepair(true);
        repaint();
    }

    public Vector getLcs()
    {
        return getChildren();
    }

    public void newDocument(Object frame)
    {
        if (checkDirty()) {
            return;
        }

        newDiagram();
      //  System.out.println("Lcs = " + lcs);
    }

    public void menuOpen(Frame frame)
    {
        if (checkDirty()) {
            return;
        }

        FileDialog dialog = new FileDialog(frame, "Open Window Definition", FileDialog.LOAD);
        dialog.setDirectory("/My Documents");
        dialog.show();
     //   System.out.println("Name is " + dialog.getFile());
    }

    public void menuClose()
    {
    }

    public void menuSave(Frame frame)
    {
        FileDialog dialog = new FileDialog(frame, "Open Window Definition", FileDialog.SAVE);
        dialog.setDirectory("/My Documents");
        dialog.show();
      //  System.out.println("Name is " + dialog.getFile());
        if (dialog.getFile() != null) {
            isDirty = false;
        }
    }

    public void menuGenerateCode()
    {
        // This should be in an interface building subclass...
     //   System.out.println("public void addComponents(Container container)");
      //  System.out.println("{");

        for (int i = 0; i < lcs().size(); i++) {
//            CpWidgetLc item = (CpWidgetLc)lcs.elementAt(i);
//            item.generateCodeOn(null, "    ");
        }
    }

    public void menuCut()
    {
        menuCopy();
        menuDelete();
    }

    public void menuCopy()
    {
		    // Hey!  This should be CpDiagrammableLc, but clone doesn't work... what up?
        CpAbstractLc lc;

        clipboard = new Vector();
		for (int i = 0; i < selectedLcs.size(); i++) {
            lc = (CpAbstractLc)selectedLcs.elementAt(i);
            clipboard.addElement(lc.getCopy());
        }
    }

    public void menuPaste()
    {
        CpDiagrammableLc lc;

        loadedLcs = new Vector();
		for (int i = 0; i < clipboard.size(); i++) {
            lc = (CpDiagrammableLc)clipboard.elementAt(i);
            menuAdd((CpDiagrammableLc)lc.getCopy());
        }
    }

    public void menuDelete()
    {
        CpDiagrammableLc lc;

        enableDamageRepair(false);
		for (int i = 0; i < selectedLcs.size(); i++) {
            lc = (CpDiagrammableLc)selectedLcs.elementAt(i);
            lc.notifyObservers(LC_DELETED);
            remove(lc);
        }
        enableDamageRepair(true);

        selectedLcs.removeAllElements();
       	// I don't just damage the rects, that way I don't have to hide the sel dots
       	repaint();
    }

    public CpDiagrammableLc getFirstSelected()
    {
        if (selectedLcs.isEmpty()) {
            return null;
        }
        return (CpDiagrammableLc)selectedLcs.firstElement();
    }

    public void menuDistribute(int orientation)
	{
		CpDiagrammableLc first, closest, item;
		int distance = 0;

		first = getFirstSelected();
		Rectangle firstBounds = first.bounds();

		closest = (CpDiagrammableLc)selectedLcs.elementAt(1);

		// Find the closest one to the first
		for (int i = 1; i < selectedLcs.size(); i++) {
            item = (CpDiagrammableLc)selectedLcs.elementAt(i);
            int tempDistance;
            if (orientation == VERTICAL) {
                distance = (firstBounds.y - closest.bounds().y);
                tempDistance = (firstBounds.y - item.bounds().y);
            } else {
                distance = (firstBounds.x - closest.bounds().x);
                tempDistance =(firstBounds.x - item.bounds().x);
            }
		    if (Math.abs(distance) > Math.abs(tempDistance)) {
		        closest = item;
		        distance = tempDistance;
		    }
        }

    //    System.out.println("In distribute, distance = " + distance);

        enableDamageRepair(false);
        Point curPoint = new Point(firstBounds.x, firstBounds.y);
        for (int i = 0; i < selectedLcs.size(); i++) {
            item = (CpDiagrammableLc)selectedLcs.elementAt(i);
			if (item != first) {
                if (orientation == VERTICAL) {
                    curPoint = new Point(item.bounds().x, curPoint.y - distance);
                } else {
                    curPoint = new Point(curPoint.x - distance, item.bounds().y);
                }
                item.move(curPoint);
			}
       	}

        enableDamageRepair(true);
       	// I don't just damage the rects, that way I don't have to hide the sel dots
	    repaint();
	}

    public void menuMatchSize(int orientation)
	{
		CpDiagrammableLc first, item;

		first = getFirstSelected();
		Rectangle firstBounds = first.bounds();

        enableDamageRepair(false);
        for (int i = 0; i < selectedLcs.size(); i++) {
            item = (CpDiagrammableLc)selectedLcs.elementAt(i);
            Rectangle itemBounds = item.bounds();
			if (item != first) {
            //    System.out.println("Item bounds = " + itemBounds);
                if (orientation == HORIZONTAL) {
                    item.reshape(itemBounds.x, itemBounds.y, firstBounds.width, itemBounds.height);
                }
                if (orientation == VERTICAL) {
                    item.reshape(itemBounds.x, itemBounds.y, itemBounds.width, firstBounds.height);
                }
                if (orientation == BOTH) {
                    item.reshape(itemBounds.x, itemBounds.y, firstBounds.width, firstBounds.height);
                }
              //  System.out.println("After match size, item bounds = " + itemBounds);
			}
       	}
        // I don't just damage the rects, that way I don't have to hide the sel dots
        enableDamageRepair(true);
	    repaint();
	}

    public void unloadCursor()
    {
        if (rubberBandLc != null) {  // the loaded lc feedback lc in mouseMove
		    rubberBandLc.remove();
		    rubberBandLc = null;
		}
        loadedLcs.removeAllElements();
    }

    public void menuAdd(CpDiagrammableLc newLc)
    {
        loadedLcs.removeAllElements();
        loadedLcs.addElement(newLc);
        newLc.addObserver(this);
    }

    public void update(CpObservable o, int facet, Object arg)
    {
        switch (facet) {
            case CpEvent.COMMAND_DONE : {
                notifyObservers(CpEvent.COMMAND_DONE, arg);
                break;
            }
        }
    }

    public void menuAlign(int which)
	{
		CpDiagrammableLc first, item;
		int alignTo = 0;

        enableDamageRepair(false);
		first = (CpDiagrammableLc)selectedLcs.firstElement();
		Rectangle firstBounds = first.bounds();
		switch (which) {
		    case LEFT : { alignTo = firstBounds.x; break; }
		    case RIGHT : { alignTo = firstBounds.x + firstBounds.width; break; }
		    case CENTER_X : { alignTo = firstBounds.x + firstBounds.width / 2; break; }
		    case TOP : { alignTo = firstBounds.y; break; }
		    case BOTTOM : { alignTo = firstBounds.y + firstBounds.height; break; }
		    case CENTER_Y : { alignTo = firstBounds.y + firstBounds.height / 2; break; }
		}
        for (int i = 0; i < selectedLcs.size(); i++) {
            item = (CpDiagrammableLc)selectedLcs.elementAt(i);
			if (item != first) {
			    Point newPoint = null;
			    Rectangle itemBounds = item.bounds();
        	    switch (which) {
        		    case LEFT : { newPoint = new Point(alignTo, itemBounds.y); break; }
        		    case RIGHT : { newPoint = new Point(alignTo - itemBounds.width, itemBounds.y); break; }
        		    case CENTER_X : { newPoint = new Point(alignTo - itemBounds.width / 2, itemBounds.y); break; }
        		    case TOP : { newPoint = new Point(itemBounds.x, alignTo); break; }
        		    case BOTTOM : { newPoint = new Point(itemBounds.x, alignTo - itemBounds.height); break; }
        		    case CENTER_Y : { newPoint = new Point(itemBounds.x, alignTo - itemBounds.height / 2); break; }
        		}
                item.move(newPoint);
			}
       	}
        enableDamageRepair(true);
	    repaint();
	}

    public void toggleGrid()
    {
        showGrid = !showGrid;
        repaint();
    }

	public void scaleUp()
	{
	    scale(getScale() * 2);
	}

	public void scaleDown()
	{
	    scale(getScale() * 0.5);
	}

	public void scale(double newScale)
	{
	    CpDiagrammableLc item;

	    enableDamageRepair(false);
	 //   CpHandle.scale(newScale);
	    if (backgroundLc != null) {
	        backgroundLc.scale(newScale);
	    }
        for (int i = 0; i < lcs().size(); i++) {
            item = (CpDiagrammableLc)lcs().elementAt(i);
            item.scale(newScale);
        }
	    enableDamageRepair(true);

        super.scale(newScale);
    }

    void damageLcRect(CpDiagrammableLc lc)
    {

        Rectangle bounds = lc.boundsDiagram();
	    int handleDim = (int)(CpHandle.getBasicDimension() * getScale());
        damage(bounds.x - handleDim, bounds.y - handleDim, bounds.width + handleDim + handleDim, bounds.height + handleDim + handleDim);
    }

    void repaint(CpDiagrammableLc lc)
    {
        damageLcRect(lc);
        repairDamage();
    }

	void hideSelectionDots(boolean hide)
	{
	    if (hide) {
	        mode = HIDEDOTS;
	    } else {
	        mode = NORMAL;
	    }
	    if (!selectedLcs.isEmpty()) {
	        if (selectedLcs.size() > 1) {
    	        repaint();
    	    } else {
    	        repaint(getFirstSelected());
    	    }
    	}
	}

	public Vector getSelectedLcs()
	{
	    return selectedLcs;
	}

	public Vector lcs()
	{
	    if (hasChildren()) {
	        return getChildren();
	    } else {
	        return new Vector();
	    }
	}

    public void add(CpLightweightComponent kid)
    {
        super.add(kid);
    }

	public void add(Vector children)
	{
	    // Should recalculate the size here for scrolling purposes
	    super.add(children);
        for (int i = 0; i < children.size(); i++ ) {
            ((CpDiagrammableLc)children.elementAt(i)).setDiagram(this);
        }
	}

    public void remove(Vector children)
    {
        for (int i = 0; i < children.size(); i++ ) {
            damageLcRect((CpDiagrammableLc)children.elementAt(i));
        }
        super.remove(children);
    }

	public void selectLcs(Vector newSelection)
	{
	    selectedLcs = newSelection;
	    repaint();
	}

    public Rectangle boundsRectFor(Vector v)
	{
        if (v.isEmpty()) {
            return new Rectangle(0, 0, 0, 0);
        }

		CpDiagrammableLc item;
		Rectangle fullBounds = null;

        for (int i = 0; i < v.size(); i++) {
            item = (CpDiagrammableLc)v.elementAt(i);
            Rectangle itemBounds = item.bounds();
            if (fullBounds == null) {
                fullBounds = itemBounds;
            } else {
                fullBounds = fullBounds.union(itemBounds);
            }
        }
        return fullBounds;
	}

    public boolean mouseMove(Event evt, int x, int y)
    {
	    if (!loadedLcs.isEmpty() && rubberBandLc == null && loadedLc == null) {
            if (loadedLcs.size() > 1) {
    		    rubberBandLc = new CpRubberRectangleLc(x, y, 0, 0);
    		    Rectangle rect = boundsRectFor(loadedLcs);
                rubberBandLc.reshape(-500, -500, rect.width, rect.height);
        		add(rubberBandLc);
        		loadedLc = rubberBandLc;
        	} else {
        	    enableDamageRepair(false);
                loadedLc = (CpDiagrammableLc)loadedLcs.elementAt(0);
                add(loadedLc);
                loadedLc.scale(getScale());
        	    enableDamageRepair(true);
        	}
    	}
        if (loadedLc != null) {
            loadedLc.move(x - (loadedLc.size().width / 2), y - (loadedLc.size().height / 2));
        }
        return false;
    }
   // long time;
    public boolean mouseDown(Event evt, int x, int y)
    {
        CpDiagrammableLc lc;

        if (rubberBandLc != null) {  // the loaded lc feedback lc in mouseMove
		    rubberBandLc.remove();
		    rubberBandLc = null;
		}

        // TEMPORARY
        if (x < 5) {
        //    add(new cnp.ew.tedtest.CpPersonLc("Fred Hipster", 34, new Date(), false));
        }
/*
        for (int i = 0; i < selectedLcs.size(); i++) {
            lc = (CpDiagrammableLc)selectedLcs.elementAt(i);
            System.out.println(lc);
            System.out.println("rect: " + lc.bounds());
        }
*/
        if (evt.clickCount > 1) {
            // Temporary hack for ken's chart demo - need double clicks in the LC
            Point locationGlobal = locationGlobal();
            int globalX = x + locationGlobal.x;
            int globalY = y + locationGlobal.y;
            CpLightweightComponent tempLc = super.locateGlobal(globalX, globalY);
            // TED FIXING KEN's dangerously recursive HACK  :-)
            if (tempLc == lastHit) {
                locationGlobal = tempLc.locationGlobal();
              //  System.out.println("double clicking in diagram mouseDown: " + (globalX - locationGlobal.x) + ", " + (globalY - locationGlobal.y));
                tempLc.mouseDown(evt, globalX - locationGlobal.x, globalY - locationGlobal.y);
                return true;
            }
        }

        hideSelectionDots(true);

        // Is the cursor loaded?
		if (!loadedLcs.isEmpty()) {
    	    enableDamageRepair(false);
		    if (loadedLc != null) {
		        loadedLc = null;
		    } else {
        		Rectangle rect = boundsRectFor(loadedLcs);
        		for (int i = 0; i < loadedLcs.size(); i++) {
                    lc = (CpDiagrammableLc)loadedLcs.elementAt(i);
                    add(lc);
                    lc.scale(getScale());
                    lc.moveBy(x - (rect.width / 2), y - (rect.height / 2));
    			}
    		}
			selectedLcs = loadedLcs;
			loadedLcs = new Vector();
		    enableDamageRepair(true);
		    repaint();
		    notifyObservers(DIAGRAM_UNLOADED_CURSOR);
			return true;
        }

        // Check handles
		for (int i = 0; i < selectedLcs.size(); i++) {
            lc = (CpDiagrammableLc)selectedLcs.elementAt(i);
            Vector handles = lc.getHandles();
            for (int h = 0; h < handles.size(); h++) {
                CpHandle handle = (CpHandle)handles.elementAt(h);
                if (handle.inside(x, y)) {
				    mode = HANDLEDRAG;
				    currentHandle = handle;
                    handle.startDrag(x, y);
                    return true;
                }
			}
		}

    		if (backgroundLc != null) {
                CpHandle handle = (CpHandle)backgroundLc.getHandles().elementAt(0);
    		    if (handle.inside(x, y)) {
				    mode = HANDLEDRAG;
				    currentHandle = handle;
                    handle.startDrag(x, y);
                    return true;
                }
    		}

		// Check default handle
		for (int i = lcs().size() - 1; i >= 0; i--) {
            lc = (CpDiagrammableLc)lcs().elementAt(i);
			if (lc.insideDiagram(x, y)) {
				if (!selectedLcs.contains(lc) && !evt.shiftDown()) {
					selectedLcs.removeAllElements();
				}
				if (!selectedLcs.contains(lc) && lc.canBeSelected()) {
    				selectedLcs.addElement(lc);
    			}
				mode = HANDLEDRAG;
				currentHandle = lc.getDefaultHandle();
				currentHandle.startDrag(x, y);
				selectionChanged = true;
				lastHit = lc;
				return true;
			}
		}

		// Rubberband select...
	 	mode = RUBBERBAND;
        if (!evt.shiftDown()) {
			selectedLcs.removeAllElements();
		}
      //   time = System.currentTimeMillis();
		rubberBandLc = new CpRubberRectangleLc(x, y, 0, 0);
		add(rubberBandLc);
      //  System.out.println("For create rubber and add, delta = " + (System.currentTimeMillis() - time));
      //   time = System.currentTimeMillis();

		currentHandle = (CpHandle)rubberBandLc.getHandles().firstElement();
   		currentHandle.startDrag(x, y);
        return true;
    }

    public boolean mouseDrag(Event evt, int x, int y)
    {
        // Dust this
        // Ken Dusted 9/12
        //getScroller().makeVisible(x, y);

        if (mode == HANDLEDRAG || mode == RUBBERBAND) {
    		currentHandle.drag(x, y);
    	}
		return true;
    }

    public boolean mouseUp(Event evt, int x, int y)
    {
		Rectangle rubberRect;
		CpDiagrammableLc lc;

		if (mode == HANDLEDRAG) {
			currentHandle.endDrag(x, y);
			if (backgroundLc != null && (CpHandle)backgroundLc.getHandles().elementAt(0) == currentHandle) {
			    // Hack to redraw that ONE sizing handle.  Yuck.
			    repaint();
			}
			mode = NORMAL;
		}
		if (mode == RUBBERBAND) {
			currentHandle.endDrag(x, y);
    	    rubberRect = rubberBandLc.boundsGlobal();
		    rubberBandLc.remove();
		    rubberBandLc = null;
			for (int i = 0; i < lcs().size(); i++) {
           		lc = (CpDiagrammableLc)lcs().elementAt(i);
				if (lc.intersectsGlobal(rubberRect) && lc.canBeSelected() && !selectedLcs.contains(lc)) {
					selectedLcs.addElement(lc);
					selectionChanged = true;
				}
			}
			mode = NORMAL;
		}
		// Redraw the selection dots

      //  System.out.println("MOUSUP, delta = " + (System.currentTimeMillis() - time));

		hideSelectionDots(false);
	//	if (selectionChanged) {
	  /*  if (selectedLcs.isEmpty() && rootLc != null) {
	        Vector v = new Vector();
	        v.addElement(rootLc);
    		notifyObservers(CpEvent.DIAGRAM_SELECTION_CHANGED, v);
	    } else { */
    		notifyObservers(CpEvent.DIAGRAM_SELECTION_CHANGED, selectedLcs);
//    	}
    		selectionChanged = false;
    	//}
		return true;
    }

    public boolean keyDown(Event e, int key)
    {
        if (key == 8 || key == 127) {  // BS and DEL
            menuCut();
            return true;
        }
        Point delta = null;
        switch (key) {
            case (Event.RIGHT) : { delta = new Point(1, 0); break; }
            case (Event.LEFT) : { delta = new Point(-1, 0); break; }
            case (Event.UP) : { delta = new Point(0, -1); break; }
            case (Event.DOWN) : { delta = new Point(0, 1); break; }
            default : {}
        }
        if (delta != null) {
            hideSelectionDots(true);
		    for (int i = 0; i < getSelectedLcs().size(); i++) {
                CpDiagrammableLc lc = (CpDiagrammableLc)getSelectedLcs().elementAt(i);
                Rectangle lcBounds = lc.bounds();
                if (e.shiftDown()) {
                    if (delta.x < 0 || delta.y < 0) {
                        lc.reshape(lcBounds.x + delta.x, lcBounds.y + delta.y, lcBounds.width - delta.x, lcBounds.height - delta.y);
                    } else {
                        lc.reshape(lcBounds.x, lcBounds.y, lcBounds.width + delta.x, lcBounds.height + delta.y);
                    }
                } else {
                    lc.moveBy(delta.x, delta.y);
                }
            }
        }
        return true;
    }

    public boolean keyUp(Event e, int key)
    {
            // CREATE NUDGE COMMAND or maybe create the command on keyUp?  Pretty neat...
        if (mode == HIDEDOTS) {
            hideSelectionDots(false);
        }
        return false;
    }

    public boolean usesFocus()
    {
        return true;
    }

    public boolean wantsLeftAndRightArrows()
    {
        return true;
    }

    public void paint(Graphics g, Rectangle clipRect)
    {
        // Did we decide to make filling the background a switch?
        g.setColor(getBackground());
        g.fillRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);

        g.setColor(gridColor);

		if (showGrid){
		    Rectangle visRect = getVisibleRect();
		    int startX = (clipRect.x / gridSize.x) * gridSize.x - 1;  // Since lines draw outside their pixel
			for (int x = startX; x < visRect.x + visRect.width; /*clipRect.x + clipRect.width;*/ x += gridSize.x) {
				g.drawLine(x, clipRect.y, x, clipRect.y + clipRect.height);
			}
		    int startY = (clipRect.y / gridSize.y) * gridSize.y - 1;  // Since lines draw outside their pixel
			for (int y = startY; y < visRect.y + visRect.height; /*clipRect.y + clipRect.height;*/ y += gridSize.y) {
				g.drawLine(clipRect.x, y, clipRect.x + clipRect.width, y);
			}
		}
	}


    public CpLightweightComponent locateGlobal(int x, int y)
    {
        if (insideGlobal(x, y)) {
            return this;
        } else {
            return null;
        }
    }

    public void paintAfterChildren(Graphics g, Rectangle clipRect)
    {
        g.setColor(handleColor);

    		if (backgroundLc != null) {
                CpHandle handle = (CpHandle)backgroundLc.getHandles().elementAt(0);
    		    handle.paint(g, false);
    		}

        if (mode == NORMAL) {
    		drawHandles(g);
    	}
    }

    public String getFlyingTipText()
    {
        CpDiagrammableLc lc;
        Point mouseLoc = mouseLocation();

		for (int i = 0; i < lcs().size(); i++) {
            lc = (CpDiagrammableLc)lcs().elementAt(i);
			if (lc.insideDiagram(mouseLoc.x, mouseLoc.y)) {
			    return lc.getFlyingTipText();
			}
		}
		return null;
    }


    public void drawHandles(Graphics g)
	{
		boolean first = true;

        CpDiagrammableLc lc;
        for (int i = 0; i < selectedLcs.size(); i++) {
            lc = (CpDiagrammableLc)selectedLcs.elementAt(i);
            lc.drawHandles(g, first);
			first = false;
        }
	}

}

