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.Shape;
import java.awt.Stroke;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
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.undo.DOMUndoManager;
import kiyut.sketsa.util.ColorConvertion;
import kiyut.sketsa.util.DOMUtilities;
import kiyut.sketsa.util.GeomUtilities;
import kiyut.sketsa.util.SVGConstants;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGElement;
import org.w3c.dom.svg.SVGPathElement;
import org.w3c.dom.svg.SVGPathSegClosePath;
import org.w3c.dom.svg.SVGPathSegCurvetoCubicAbs;
import org.w3c.dom.svg.SVGPathSegLinetoAbs;
import org.w3c.dom.svg.SVGPathSegMovetoAbs;
import org.w3c.dom.svg.SVGStylable;

/**
 * Implementation of Spiral Tool
 *
 * @author Kiyut
 */
public class SpiralTool extends AbstractTool {
    private VectorCanvas canvas;
    private Shape spiral;
    private Shape outline;
    private Rectangle2D bounds;
    
    private Paint strokePaint;
    private Paint fillPaint;
    private Stroke stroke;
    private float strokeWidth;
    
    private int newX;
    private int newY;
    private int startX;
    private int startY;
    
    /** Creates a new instance of SpiralTool */
    public SpiralTool() {
        bounds = new Rectangle2D.Double();
        
        strokeWidth = 1;
        stroke = new BasicStroke(strokeWidth);
        strokePaint = Color.BLACK;
        fillPaint = null;
    }
    
    @Override
    public void paint(Graphics2D g2d) {
        if (outline == null) { return; }
        
        Paint oldPaint = g2d.getPaint();
        Stroke oldStroke = g2d.getStroke();
        
        if (fillPaint != null) {
            g2d.setPaint(fillPaint);
            g2d.fill(outline);
        }
        
        g2d.setPaint(strokePaint);
        g2d.setStroke(stroke);
        g2d.draw(outline);
        
        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 startTool() {
        spiral = null;
        outline = null;
        canvas.getCanvasSelection().setEnabledVisible(false,true,false);
        canvas.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
        fireMessageReceived(MessageEvent.INFORMATION_MESSAGE, "Press and drag mouse to draw spiral");
    }
    
    @Override
    public void endTool() {
        super.endTool();
        spiral = null;
        outline = null;
    }
    
    @Override
    public void mousePressed(MouseEvent evt) {
        if (!SwingUtilities.isLeftMouseButton(evt)) { return; }
        
        startX = evt.getX();
        startY = evt.getY();
        
        if (canvas.isSnapToGrid() == true) {
            int gridSize = canvas.getCanvasGrid().getGridSize();
            startX = gridSize * Math.round((float)startX / gridSize);
            startY = gridSize * Math.round((float)startY / gridSize);
        }
        
        newX = startX;
        newY = startY;
        
        spiral = createShape(startX,startY);
        
        canvas.repaint();
    }
    
    @Override
    public void mouseDragged(MouseEvent evt) {
        if (!SwingUtilities.isLeftMouseButton(evt)) { return; }
        
        newX = evt.getX();
        newY = evt.getY();
        
        if ((evt.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) == InputEvent.SHIFT_DOWN_MASK) {
            int dx = newX-startX;
            int dy = newY-startY;
            int d = Math.max(Math.abs(dx),Math.abs(dy));
            if (dx < 0) {
                newX = startX + (d * -1);
            } else {
                newX = startX + d;
            }
            
            if (dy < 0) {
                newY = startY + (d * -1);
            } else {
                newY = startY + d;
            }
        }
        
        if (canvas.isSnapToGrid() == true) {
            int gridSize = canvas.getCanvasGrid().getGridSize();
            newX = gridSize * Math.round((float)newX / gridSize);
            newY = gridSize * Math.round((float)newY / gridSize);
        }
        
        if (outline != null) {
            bounds.setRect(outline.getBounds());
        }
        
        updateOutline();
        
        bounds = bounds.createUnion(outline.getBounds());
        canvas.repaint((int)bounds.getX()-1,(int)bounds.getY()-1,(int)bounds.getWidth()+2,(int)bounds.getHeight()+2);
        
    }
    
    @Override
    public void mouseReleased(MouseEvent evt) {
        if (!SwingUtilities.isLeftMouseButton(evt)) { return; }
        
        updateOutline();
        
        if (outline.getBounds2D().isEmpty()) { return; }
        
        SVGElement element = createSVGElement();
        CanvasModel model = canvas.getModel();
        
        DOMUndoManager undoManager = canvas.getUndoManager();
        undoManager.start("Insert Spiral");
        model.appendChild(element);
        undoManager.end();
        
        java.util.List<SVGElement> selectionList = new java.util.ArrayList<SVGElement>(1);
        selectionList.add(element);
        canvas.getCanvasSelection().setSelectionList(selectionList);
        
        startTool();
        
        canvas.refresh();
    }
    
    /** Return the SVGElement
     *@return the SVGElement
     */
    protected SVGElement createSVGElement() {
        CanvasModel model = canvas.getModel();
        SVGDocument svgDocument = model.getSVGDocument();
        String svgNS = SVGConstants.SVG_NAMESPACE_URI;
        
        AffineTransform at = canvas.getTransform(true);
        try {
            at = at.createInverse();
        } catch (NoninvertibleTransformException ex) { /* do nothing */ }
        
        PathIterator pi = outline.getPathIterator(at);
        
        SVGPathElement element = (SVGPathElement)svgDocument.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_PATH_TAG);
        
        // adding the path seg
        float[] coords = new float[6];
        while (!pi.isDone()) {
            int segment = pi.currentSegment(coords);
            if (segment == PathIterator.SEG_MOVETO) {
                SVGPathSegMovetoAbs svgPathSeg = element.createSVGPathSegMovetoAbs(coords[0],coords[1]);
                element.getPathSegList().appendItem(svgPathSeg);
            } else if (segment == PathIterator.SEG_CLOSE) {
                SVGPathSegClosePath svgPathSeg = element.createSVGPathSegClosePath();
                element.getPathSegList().appendItem(svgPathSeg);
            } else if (segment == PathIterator.SEG_LINETO) {
                SVGPathSegLinetoAbs svgPathSeg = element.createSVGPathSegLinetoAbs(coords[0],coords[1]);
                element.getPathSegList().appendItem(svgPathSeg);
            } else if (segment == PathIterator.SEG_CUBICTO) {
                SVGPathSegCurvetoCubicAbs svgPathSeg = element.createSVGPathSegCurvetoCubicAbs(coords[4],coords[5],coords[0],coords[1],coords[2],coords[3]);
                element.getPathSegList().appendItem(svgPathSeg);
            }
            
            pi.next();
        }
        
        SVGStylable stylable = (SVGStylable)element;
        
        String str;
        
        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);
        }
        
        return element;
    }
    
    /** Return shape that will be used to draw into the canvas at the location
     * @param x x coord
     * @param y y coord
     * @return Shape
     */
    protected Shape createShape(int x, int y) {
        /* Create Shape */
        String pathString = "M 23.526352 27.19297 C 24.383507 26.27047 25.089615 27.997765 24.93245 " +
                "28.746468 C 24.506508 30.77539 22.117805 31.084969 20.677063 30.259483 C 18.0999 28.782898 " +
                "17.819717 24.994354 19.308083 22.532494 C 21.492317 18.91961 26.177742 18.616196 29.22493 " +
                "21.059944 C 33.286377 24.317062 33.559647 30.787315 30.556808 34.960426 C 26.778015 40.21187 " +
                "19.58062 40.502785 14.9784775 36.392517 C 9.404663 31.414436 9.145134 22.17929 13.683731 " +
                "16.318527 C 19.032303 9.411808 28.777931 9.137165 34.923523 14.92691 C 42.018845 21.611397 " +
                "42.262787 33.63356 36.181168 41.1744 C 29.270813 49.742783 16.964348 50.0 9.279892 42.52555 " +
                "C 0.65909576 34.140343 0.43136597 19.321999 8.059364 10.104553 C 16.527893 -0.12855148 " +
                "31.401077 -0.36790466 40.6221 8.793877 C 49.72838 17.841637 51.0 33.25872 44.099747 " +
                "44.242897 ";
        
        Shape shape = GeomUtilities.createShape(pathString);
        
        /* Set the location */
        Rectangle2D bounds = shape.getBounds2D();
        double tx = x - bounds.getX();
        double ty = y - bounds.getY();
        AffineTransform at = AffineTransform.getTranslateInstance(tx, ty);
        shape = at.createTransformedShape(shape);
        
        return shape;
    }
    
    /**
     * Update outline shape according the current mouse event
     */
    protected void updateOutline() {
        Rectangle2D rect = spiral.getBounds2D();
        
        double tx = rect.getX();
        double ty = rect.getY();
        double w = rect.getWidth();
        double h = rect.getHeight();
        
        // avoid div by zero
        if (w == 0) { return; }
        if (h == 0) { return; }
        
        double wx = tx + w;     // width x coord
        double hy = ty + h;     // height y coord
        
        double sx = (w+(newX - wx))/w;
        double sy = (h+(newY - hy))/h;
        
        AffineTransform at = new AffineTransform();
        at.translate(tx,ty);
        at.scale(sx,sy);
        at.translate(-tx,-ty);
        
        outline = at.createTransformedShape(spiral);
    }
}
