
package kiyut.sketsa.modules.extratool;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import javax.swing.SwingUtilities;
import kiyut.sketsa.canvas.CanvasModel;
import kiyut.sketsa.canvas.VectorCanvas;
import kiyut.sketsa.canvas.tool.AbstractTool;
import kiyut.sketsa.event.MessageEvent;
import kiyut.sketsa.options.CanvasOptions;
import kiyut.sketsa.undo.DOMUndoManager;
import kiyut.sketsa.util.ColorConvertion;
import kiyut.sketsa.util.DOMUtilities;
import kiyut.sketsa.util.SVGConstants;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGElement;
import org.w3c.dom.svg.SVGPoint;
import org.w3c.dom.svg.SVGPointList;
import org.w3c.dom.svg.SVGPolygonElement;
import org.w3c.dom.svg.SVGPolylineElement;
import org.w3c.dom.svg.SVGSVGElement;
import org.w3c.dom.svg.SVGStylable;

/**
 * Implementation of Draw Arrow Conn Tool
 *
 * @author  KIYUT
 */
public class Arrow02Tool extends AbstractTool {
    protected VectorCanvas canvas;
    protected Rectangle2D bounds;
    protected GeneralPath path;

    protected Paint strokePaint;
    protected Paint fillPaint;
    protected Stroke stroke;
    protected float strokeWidth;

    private int x;
    private int y;
    private int lastX;
    private int lastY;

    protected Rectangle2D initHandle;

    /** Creates a new instance of ArrowTool */
    public Arrow02Tool() {
        bounds = new Rectangle2D.Double();

        strokeWidth = 1;
        stroke = new BasicStroke(strokeWidth);
        strokePaint = Color.BLACK;
        fillPaint = null;

        path = new GeneralPath();

        reset();
    }

    @Override
    public void paint(Graphics2D g2d) {
        Paint oldPaint = g2d.getPaint();
        Stroke oldStroke = g2d.getStroke();

        g2d.setPaint(strokePaint);
        g2d.setStroke(stroke);

        if (initHandle != null) {
            g2d.fill(initHandle);
        }

        if (path != null) {
            g2d.draw(path);
        }

        g2d.setPaint(oldPaint);
        g2d.setStroke(oldStroke);
        
    }

    @Override
    public void setVectorCanvas(VectorCanvas canvas) {
        this.canvas = canvas;
    }

    public void setStrokePaint(Paint strokePaint) {
        this.strokePaint = strokePaint;
    }

    public void setFillPaint(Paint fillPaint) {
        this.fillPaint = fillPaint;
    }

    public void setStroke(Stroke stroke) {
        this.stroke = stroke;
    }

    @Override
    public void endTool() {
        //addArrowHead();
        reset();
    }

    @Override
    public void startTool() {
        reset();
        canvas.getCanvasSelection().setEnabledVisible(false,true,false);
        canvas.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
        fireMessageReceived(MessageEvent.INFORMATION_MESSAGE, "Click and drag to draw arrow.");
    }

    private void reset() {
        initHandle = null;
        path.reset();
    }

    @Override
    public void mousePressed(MouseEvent evt) {
        if (!SwingUtilities.isLeftMouseButton(evt)) { return; }

        x = evt.getX();
        y = evt.getY();

        if (canvas.isSnapToGrid() == true) {
            int gridSize = canvas.getCanvasGrid().getGridSize();
            x = gridSize * Math.round((float)x / gridSize);
            y = gridSize * Math.round((float)y / gridSize);
        }

        if (initHandle == null) {
            CanvasOptions prefs = CanvasOptions.getInstance();
            int handleSize = prefs.getHandleSize() / 2;

            initHandle = new Rectangle2D.Float();
            initHandle.setFrameFromCenter(x,y,x-handleSize,y-handleSize);
        }

        canvas.repaint();
     }

    @Override
    public void mouseReleased(MouseEvent evt) {
        if (!SwingUtilities.isLeftMouseButton(evt)) { return; }

        lastX = evt.getX();
        lastY = evt.getY();

        // constraint
        if ((evt.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) == InputEvent.SHIFT_DOWN_MASK) {
            int size = 8;
            if (x >= lastX - size && x <= lastX + size) {
                lastX = x;

            }
            if (y >= lastY - size && y <= lastY + size) {
                lastY = y;
            }
        }

        if (canvas.isSnapToGrid() == true) {
            int gridSize = canvas.getCanvasGrid().getGridSize();
            lastX = gridSize * Math.round((float)lastX / gridSize);
            lastY = gridSize * Math.round((float)lastY / gridSize);
        }

        if (x == lastX && y == lastY) {
            return;
        }

        createShape();

        SVGElement element = createSVGElement();
        CanvasModel model = canvas.getModel();

        DOMUndoManager undoManager = canvas.getUndoManager();
        undoManager.start("Draw Arrow");
        try {
            model.appendChild(element);
        } finally {
            undoManager.end();
        }

        java.util.List<SVGElement> selectionList = new java.util.ArrayList<SVGElement>(1);
        selectionList.add(element);
        canvas.getCanvasSelection().setSelectionList(selectionList);

        startTool();

        canvas.refresh();
    }

    @Override
    public void mouseDragged(MouseEvent evt) {
        if (!SwingUtilities.isLeftMouseButton(evt)) { return; }

        lastX = evt.getX();
        lastY = evt.getY();

        // constraint
        if ((evt.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) == InputEvent.SHIFT_DOWN_MASK) {
            int size = 8;
            if (x >= lastX - size && x <= lastX + size) {
                lastX = x;

            }
            if (y >= lastY - size && y <= lastY + size) {
                lastY = y;
            }
        }

        if (canvas.isSnapToGrid() == true) {
            int gridSize = canvas.getCanvasGrid().getGridSize();
            lastX = gridSize * Math.round((float)lastX / gridSize);
            lastY = gridSize * Math.round((float)lastY / gridSize);
        }

        if (x == lastX && y == lastY) {
            return;
        }

        bounds.setRect(path.getBounds());

        createShape();

        bounds = bounds.createUnion(path.getBounds());
        canvas.repaint((int)bounds.getX()-1,(int)bounds.getY()-1,(int)bounds.getWidth()+2,(int)bounds.getHeight()+2);

    }

    protected void createShape() {
        path.reset();

        path.moveTo(x, y);

        /* Try to draw the following shape
         * 
         * |
         * |________
         *          |
         *          |
         *
         * /

        /* draw first vert line */
        int tX = x;
        int tY = (int)((y + lastY) / 2d);
        path.lineTo(tX, tY);

        /* draw first horiz line */
        tX = lastX;
        path.lineTo(tX, tY);

        /* draw second vert line */
        path.lineTo(lastX, lastY);

    }

    protected SVGElement createSVGElement() {
        CanvasModel model = canvas.getModel();
        SVGDocument svgDocument = model.getSVGDocument();
        SVGSVGElement rootElement = svgDocument.getRootElement();
        String svgNS = SVGConstants.SVG_NAMESPACE_URI;

        SVGPointList svgPointList;
        SVGPoint svgPoint;
        SVGStylable stylable;
        String str;

        AffineTransform at = canvas.getTransform(true);
        try {
            at = at.createInverse();
        } catch (NoninvertibleTransformException e) {}

        //////////////////////////
        // poly or arrow body
        SVGPolylineElement arrowBody = (SVGPolylineElement)svgDocument.createElementNS(svgNS, SVGConstants.SVG_POLYLINE_TAG);
        svgPointList = arrowBody.getPoints();

        float[] coords = new float[6];
        PathIterator pi = path.getPathIterator(at);
        while (!pi.isDone()) {
            pi.currentSegment(coords);
            svgPoint = rootElement.createSVGPoint();
            svgPoint.setX(coords[0]);
            svgPoint.setY(coords[1]);
            svgPointList.appendItem(svgPoint);
            pi.next();
        }

        stylable = (SVGStylable)arrowBody;

        if (strokePaint != null) {
            if (strokePaint instanceof Color) {
                str = ColorConvertion.toHexString((Color) strokePaint);
                DOMUtilities.updateProperty(stylable, null, SVGConstants.SVG_STROKE_ATTRIBUTE, str);
            }
        } else {
            DOMUtilities.updateProperty(stylable, null, SVGConstants.SVG_STROKE_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE);
        }

        if (fillPaint != null) {
            if (fillPaint instanceof Color) {
                str = ColorConvertion.toHexString((Color) fillPaint);
                DOMUtilities.updateProperty(stylable, null, SVGConstants.SVG_FILL_ATTRIBUTE, str);
            }
        } else {
            DOMUtilities.updateProperty(stylable, null, SVGConstants.SVG_FILL_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE);
        }

        ///////////////////
        // arrow head
        SVGPolygonElement arrowHead = (SVGPolygonElement)svgDocument.createElementNS(svgNS, SVGConstants.SVG_POLYGON_TAG);
        int size = 8;
        float[] pts = new float[6];

        // arrow head triangle
        pts[0] = lastX;
        pts[1] = lastY;

        if (Math.max(x,lastX) == lastX) {
            //if (y >= lastY - size && y <= lastY + size ) {
            if (y == lastY) {
                // arrow right
                pts[2] = lastX-size;
                pts[3] = lastY+size;
                pts[4] = lastX-size;
                pts[5] = lastY-size;
            } else if (Math.max(y, lastY) == lastY) {
                // arrow down
                pts[2] = lastX-size;
                pts[3] = lastY-size;
                pts[4] = lastX+size;
                pts[5] = lastY-size;
            } else {
                // arrow up
                pts[2] = lastX+size;
                pts[3] = lastY+size;
                pts[4] = lastX-size;
                pts[5] = lastY+size;
            }
        } else {
            //if (y >= lastY - size && y <= lastY + size ) {
            if (y == lastY) {
                // arrow left
                pts[2] = lastX+size;
                pts[3] = lastY+size;
                pts[4] = lastX+size;
                pts[5] = lastY-size;
            } else if (Math.max(y, lastY) == lastY) {
                // arrow down
                pts[2] = lastX-size;
                pts[3] = lastY-size;
                pts[4] = lastX+size;
                pts[5] = lastY-size;
            
            } else {
                // arrow up
                pts[2] = lastX+size;
                pts[3] = lastY+size;
                pts[4] = lastX-size;
                pts[5] = lastY+size;
            }
        }

        svgPointList = arrowHead.getPoints();
        for (int i=2;i<=6; i=i+2) {
            svgPoint = rootElement.createSVGPoint();
            svgPoint.setX(pts[i-2]);
            svgPoint.setY(pts[i-1]);
            svgPointList.appendItem(svgPoint);
        }

        stylable = (SVGStylable)arrowHead;

        if (strokePaint != null) {
            if (strokePaint instanceof Color) {
                str = ColorConvertion.toHexString((Color) strokePaint);
                DOMUtilities.updateProperty(stylable, null, SVGConstants.SVG_STROKE_ATTRIBUTE, str);
                DOMUtilities.updateProperty(stylable, null, SVGConstants.SVG_FILL_ATTRIBUTE, str);
            }
        } else {
            DOMUtilities.updateProperty(stylable, null, SVGConstants.SVG_STROKE_ATTRIBUTE, SVGConstants.SVG_NONE_VALUE);
        }

        ////////////////////////
        // Group body and head
        SVGElement arrow = (SVGElement)svgDocument.createElementNS(svgNS, SVGConstants.SVG_G_TAG);
        arrow.setId("arrow" + System.currentTimeMillis());
        arrow.appendChild(arrowBody);
        arrow.appendChild(arrowHead);

        return arrow;
    }

}
