/*
* File Arrow.java
*
* Copyright (C) 2010 Remco Bouckaert remco@cs.auckland.ac.nz
*
* This file is part of BEAST2.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
*  BEAST is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA  02110-1301  USA
*/
package beast.app.draw;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.GeneralPath;
import java.io.PrintStream;
import java.util.List;

import javax.swing.JPanel;

import org.w3c.dom.Node;

import beast.core.util.Log;

public class Arrow extends Shape {
    String m_sHeadID;
    String m_sTailID;
    public BEASTObjectShape m_tailShape;
    public InputShape m_headShape;
//	String m_sPenStyle;

    /* c'tor for creating arrow while parsing XDL format XML **/
    public Arrow(Node node, Document doc, boolean reconstructBEASTObjects) {
        parse(node, doc, reconstructBEASTObjects);
    }

    /* c'tor for creating arrow when starting to draw new one **/
    public Arrow(BEASTObjectShape tailShape, int x, int y) {
        m_sTailID = tailShape.getID();
        m_x = x;
        m_y = y;
        m_w = 1;
        m_h = 1;
        m_tailShape = tailShape;
    }

    /* c'tor for creating arrow with all fields set properly
      * Used when arrows are created by Document.recalcArrows */
    public Arrow(BEASTObjectShape tailShape, BEASTObjectShape headShape, String inputName) {
	        m_sTailID = tailShape.getID();
	        m_tailShape = tailShape;
	        InputShape input = headShape.getInputShape(inputName);
	        if (input == null) {
	        	Log.warning.println("Arrow from " + tailShape.m_beastObject.getID() + " to " + headShape.m_beastObject.getID() + "." + inputName + " skipped");	        	
	        }
	        m_sHeadID = input.getID();
	        m_headShape = input;
	        //m_sHeadID = headShape.m_id;
	        m_x = 0;
	        m_y = 0;
	        m_w = 1;
	        m_h = 1;
	        m_pencolor = Color.gray;
    }

    @Override
    public void draw(Graphics2D g, JPanel panel) {
        g.setStroke(new BasicStroke(m_nPenWidth));
        g.setColor(m_pencolor);
        g.setColor(Color.gray);
        GeneralPath path = new GeneralPath();
        path.moveTo(m_x, m_y);
        path.curveTo(m_x + 20, m_y, m_x + m_w - 40, m_y + m_h, m_x + m_w, m_y + m_h);
        g.draw(path);
        drawLabel(g);
    }

    /* change head position while dragging by mouse */
    public void setHead(int w, int h) {
        m_w = w;
        m_h = h;
    }

    /* set all parameters properly at end of dragging when mouse is released */
    public boolean setHead(InputShape shape, List<Shape> objects, Document doc) {
        m_sHeadID = shape.getID();
        m_headShape = shape;
        adjustCoordinates();
        String inputName = m_headShape.getInputName();
        m_headShape.getBEASTObject().setInputValue(inputName, m_tailShape.m_beastObject);
        return true;//setFunctionInput(objects, doc);
    }

    /* parse arrow in XDL format XML **/
    @Override
    void parse(Node node, Document doc, boolean reconstructBEASTObjects) {
        super.parse(node, doc, reconstructBEASTObjects);
        if (node.getAttributes().getNamedItem("headid") != null) {
            m_sHeadID = node.getAttributes().getNamedItem("headid").getNodeValue();
        }
        if (node.getAttributes().getNamedItem("tailid") != null) {
            m_sTailID = node.getAttributes().getNamedItem("tailid").getNodeValue();
        }
//		if (node.getAttributes().getNamedItem("penstyle") != null) {
//			m_sPenStyle = node.getAttributes().getNamedItem("penstyle").getNodeValue();
//		}
    }

    @Override
    String getAtts() {
        return
                " headid='" + m_sHeadID + "'" +
                        " tailid='" + m_sTailID + "'" +
//		" penstyle='" + m_sPenStyle + "'" +
                        super.getAtts();
    }

    @Override
    public String getXML() {
        return "<" + Document.ARROW_ELEMENT + getAtts() + "/>";
    }

    void adjustCoordinates() {
        Point tailCenter = new Point((m_tailShape.getX() + m_tailShape.getX2()) / 2, (m_tailShape.getY() + m_tailShape.getY2()) / 2);
        Point headCenter = new Point((m_headShape.getX() + m_headShape.getX2()) / 2, (m_headShape.getY() + m_headShape.getY2()) / 2);
        Shape rect = m_tailShape;
        Point roundness = new Point(0, 0);
        if (rect instanceof InputShape) {
            roundness.x = rect.m_w;
            roundness.y = rect.m_h;
        }
        Point tailPoint = CalcIntersectionLineAndNode(
                tailCenter, headCenter, rect, roundness);

        rect = m_headShape;
        roundness = new Point(0, 0);
        if (rect instanceof InputShape) {
            roundness.x = rect.m_w;
            roundness.y = rect.m_h;
        }

        Point headPoint = CalcIntersectionLineAndNode(
                headCenter, tailCenter, rect, roundness);
        m_x = tailPoint.x;
        m_y = tailPoint.y;
        m_w = headPoint.x - m_x;
        m_h = headPoint.y - m_y;
    }

    Point CalcIntersectionLineAndNode(Point p0, Point p1,
                                      Shape position, Point roundness) {
//	 Note: a rounded rectangle is a rectangle in which the corners are quarter elipses
//		.p0			   .
//					   .
//					   .
//					   |
//		............=-	 <- partly elipse
        //
        Point pt = new Point();
        int w, h, a, b, c; // width, height, elipse width, elipse height, cut position
        w = Math.abs((position.getX() - position.getX2()) / 2);
        h = Math.abs((position.getY() - position.getY2()) / 2);
        a = Math.abs(roundness.x / 2);
        b = Math.abs(roundness.y / 2);

        // try intersection with horizontal line
        if (p1.y != p0.y) { // Don't try if Line is horizontal
            if (p1.y > p0.y) {
                c = p0.y + h;
            } else {
                c = p0.y - h;
            }
            pt.y = c;
            pt.x = p0.x + (p1.x - p0.x) * (c - p0.y) / (p1.y - p0.y);
            if ((pt.x >= p0.x - w + a) && (pt.x <= p0.x + w - a))
                return pt;
        }

        // try intersection with vertical line
        if (p1.x != p0.x) { // Don't try if Line is vertical
            if (p1.x > p0.x) {
                c = p0.x + w;
            } else {
                c = p0.x - w;
            }
            pt.x = c;
            pt.y = p0.y + (p1.y - p0.y) * (c - p0.x) / (p1.x - p0.x);
            if ((pt.y >= p0.y - h + b) && (pt.y <= p0.y + h - b))
                return pt;
        }

        // finally try intersection with one of the elips-shaped corners
        double ar, br, ga, gb, A, B, C, p, q;

        if (p1.x > p0.x)
            p = p0.x + w - a;
        else
            p = p0.x - w + a;
        if (p1.y > p0.y)
            q = p0.y + h - b;
        else
            q = p0.y - h + b;

        ar = a;
        br = b;
        if (p1.x == p0.x) // cheat to prevent divsion by zero
        {
            ga = (double) (p1.y - p0.y) / 1;
        } else {
            ga = (double) (p1.y - p0.y) / (double) (p1.x - p0.x);
        }
        gb = p0.y - ga * p0.x;
        A = 1 / (ar * ar) + (ga * ga) / (br * br);
        B = -2.0 * p / (ar * ar) + 2.0 * ga * gb / (br * br) - 2.0 * ga * q / (br * br);
        C = p * p / (ar * ar) + gb * gb / (br * br) - 2.0 * gb * q / (br * br) + q * q / (br * br) - 1.0;

        if (p1.x > p0.x) {
            pt.x = (int) ((-B + Math.sqrt(B * B - 4.0 * A * C)) / (2.0 * A));
        } else {
            pt.x = (int) ((-B - Math.sqrt(B * B - 4.0 * A * C)) / (2.0 * A));
        }
        pt.y = (int) (ga * pt.x + gb);
        return pt;

    }

    String m_sID = null;

    @Override
    public String getID() {
        return m_sID;
    }

    public void setID(String id) {
        m_sID = id;
    }

    @Override
	public String toString() {
        return m_sTailID + "-->" + m_sHeadID;
    }

    @Override
    void toSVG(PrintStream out) {
        out.println("<path d='M " + m_x + " " + m_y +
                " C " + (m_x + 20) + " " + m_y + " " + (m_x + m_w - 40) + " " + (m_y + m_h) + " " + (m_x + m_w) + " " + (m_y + m_h) + "'" +
//	    		" q 20 0 " + (m_w-40) + " " + (m_h) + " T 40 0 '" +
                " stroke='rgb(" + m_pencolor.getRed() + "," + m_pencolor.getGreen() + "," + m_pencolor.getBlue() + ")'" +
                " stroke-width='" + m_nPenWidth + "' fill='none'/>");
    }
} // class Arrow
