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.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
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.SVGConstants;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGElement;
import org.w3c.dom.svg.SVGStylable;


/**
 * Implementation of Insert Round Rectangle Tool
 *
 * @author  Kiyut
 */
public class RoundRectangleTool extends AbstractTool {
    private VectorCanvas canvas;
    private RoundRectangle2D rect;
    private Rectangle2D bounds;
    private int rx;
    private int ry;
    
    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 RoundRectangleTool */
    public RoundRectangleTool() {
        rect = new RoundRectangle2D.Float();
        bounds = new Rectangle2D.Double();
        
        rx = 20;
        ry = rx;
        strokeWidth = 1;
        stroke = new BasicStroke(strokeWidth);
        strokePaint = Color.BLACK;
        fillPaint = null;
    }
    
    @Override
    public void paint(Graphics2D g2d) {
        Paint oldPaint = g2d.getPaint();
        Stroke oldStroke = g2d.getStroke();
        
        if (fillPaint != null) {
            g2d.setPaint(fillPaint);
            g2d.fill(rect);
        }
        
        g2d.setPaint(strokePaint);
        g2d.setStroke(stroke);
        g2d.draw(rect);
        
        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() {
        canvas.getCanvasSelection().setEnabledVisible(false,true,false);
        rect.setRoundRect(-1,-1,-1,-1,rx,ry);
        canvas.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
        fireMessageReceived(MessageEvent.INFORMATION_MESSAGE, "Press and drag mouse to draw round rectangle");
    }
    
    @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);
        }
        
        bounds.setRect(rect.getBounds());
        
        updateRect();
        
        bounds = bounds.createUnion(rect.getBounds());
        canvas.repaint((int)bounds.getX()-1,(int)bounds.getY()-1,(int)bounds.getWidth()+2,(int)bounds.getHeight()+2);
        
    }
    
    @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;
        
        rect.setRoundRect(startX,startY,0,0,rx,ry);
        canvas.repaint();
    }
    
    @Override
    public void mouseReleased(MouseEvent evt) {
        if (!SwingUtilities.isLeftMouseButton(evt)) { return; }
        
        updateRect();
        
        if (rect.isEmpty()) { return; }
        
        SVGElement element = createSVGElement();
        CanvasModel model = canvas.getModel();
        
        DOMUndoManager undoManager = canvas.getUndoManager();
        undoManager.start("Insert Round Rectangle");
        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();
    }
    
    private SVGElement createSVGElement() {
        CanvasModel model = canvas.getModel();
        SVGDocument svgDocument = model.getSVGDocument();
        String svgNS = SVGConstants.SVG_NAMESPACE_URI;
        SVGElement element = (SVGElement)svgDocument.createElementNS(svgNS, SVGConstants.SVG_RECT_TAG);
        
        double[] srcPts = new double[4];
        double[] dstPts = new double[6];
        srcPts[0] = rect.getX();
        srcPts[1] = rect.getY();
        srcPts[2] = rect.getWidth();
        srcPts[3] = rect.getHeight();
        
        AffineTransform at = canvas.getTransform(true);
        try {
            at = at.createInverse();
        } catch (NoninvertibleTransformException e) {}
        at.transform(srcPts,0,dstPts,0,2);
        dstPts[4] = rect.getArcWidth();
        dstPts[5] = rect.getArcHeight();
        
        element.setAttributeNS(null, SVGConstants.SVG_X_ATTRIBUTE, Float.toString((float)dstPts[0]));
        element.setAttributeNS(null, SVGConstants.SVG_Y_ATTRIBUTE, Float.toString((float)dstPts[1]));
        element.setAttributeNS(null, SVGConstants.SVG_WIDTH_ATTRIBUTE, Float.toString((float)dstPts[2]));
        element.setAttributeNS(null, SVGConstants.SVG_HEIGHT_ATTRIBUTE, Float.toString((float)dstPts[3]));
        element.setAttributeNS(null, SVGConstants.SVG_RX_ATTRIBUTE, Float.toString((float)dstPts[4]));
        element.setAttributeNS(null, SVGConstants.SVG_RY_ATTRIBUTE, Float.toString((float)dstPts[5]));
        
        //SVGStylable svgStylable = (SVGStylable)element;
        //CSSStyleDeclaration cssStyle = svgStylable.getStyle();
        
        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;
    }
    
    /** update the rect based on mouse event */
    protected void updateRect() {
        double rX;
        double rY;
        double rW;
        double rH;
        
        if (newX >= startX) {
            rX = startX;
            rW = newX - startX;
        } else {
            rX = newX;
            rW = startX - newX;
        }
        
        if (newY >= startY) {
            rY = startY;
            rH = newY - startY;
        } else {
            rY = newY;
            rH = startY - newY;
        }
        
        rect.setRoundRect(rX, rY, rW, rH, this.rx, this.ry);
    }
}
