/*
 * RadialTreeLayout.java
 *
 * Copyright (C) 2006-2014 Andrew Rambaut
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package figtree.treeviewer.treelayouts;

import jebl.evolution.graphs.Graph;
import jebl.evolution.graphs.Node;
import jebl.evolution.trees.RootedTree;

import java.awt.*;
import java.awt.geom.*;
import java.util.List;

/**
 * @author Andrew Rambaut
 * @version $Id$
 *
 * $HeadURL$
 *
 * $LastChangedBy$
 * $LastChangedDate$
 * $LastChangedRevision$
 */
public class RadialTreeLayout extends AbstractTreeLayout {

	private double spread = 0.0;

    public AxisType getXAxisType() {
        return AxisType.CONTINUOUS;
    }

    public AxisType getYAxisType() {
        return AxisType.CONTINUOUS;
    }

    public boolean isShowingRootBranch() {
        return false;
    }

	public double getTotalRootLength() {
		return 0.0;
	}

	public void setFishEye(double fishEye) {
		// do nothing
	}

	public void setPointOfInterest(double x, double y) {
		// do nothing
	}

    public boolean isShowingColouring() {
	    return (branchColouringAttribute != null);
    }

	public boolean maintainAspectRatio() {
        return true;
    }

    public double getHeightOfPoint(Point2D point) {
        throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
    }

    public Line2D getAxisLine(double height) {
        throw new UnsupportedOperationException("Method getHeightLine() is not supported in this TreeLayout");
    }

    public Shape getHeightArea(double height1, double height2) {
        throw new UnsupportedOperationException("Method getHeightArea() is not supported in this TreeLayout");
    }

    public double getSpread() {
        return spread;
    }

    public void setSpread(double spread) {
        this.spread = spread;
        fireTreeLayoutChanged();
    }

	public void layout(RootedTree tree, TreeLayoutCache cache) {
        cache.clear();

        try {
            final Node root = tree.getRootNode();

            constructNode(tree, root, 0.0, Math.PI * 2, 0.0, 0.0, 0.0, cache);

            // Haven't been able to make these look nice....
//            constructNodeAreas(tree, root, new Area(), cache);

        } catch (Graph.NoEdgeException e) {
            e.printStackTrace();
        }
    }

    private Point2D constructNode(RootedTree tree, Node node,
                                  double angleStart, double angleFinish, double xPosition,
                                  double yPosition, double length,
                                  TreeLayoutCache cache) throws Graph.NoEdgeException {

        final double branchAngle = (angleStart + angleFinish) / 2.0;

        final double directionX = Math.cos(branchAngle);
        final double directionY = Math.sin(branchAngle);
        Point2D nodePoint = new Point2D.Double(xPosition + (length * directionX), yPosition + (length * directionY));

        // System.out.println("Node: " + Utils.DEBUGsubTreeRep(tree, node) + " at " + nodePoint);

        if (!tree.isExternal(node)) {

// Not too clear how to do hilighting for radial trees so leave it out...
//            if (hilightAttributeName != null && node.getAttribute(hilightAttributeName) != null) {
//                constructHilight(tree, node, angleStart, angleFinish, xPosition, yPosition, length, cache);
//            }

            List<Node> children = tree.getChildren(node);
            int[] leafCounts = new int[children.size()];
            int sumLeafCount = 0;

            int i = 0;
            for (Node child : children) {
                leafCounts[i] = jebl.evolution.trees.Utils.getExternalNodeCount(tree, child);
                sumLeafCount += leafCounts[i];
                i++;
            }

            double span = (angleFinish - angleStart);

            if (!tree.isRoot(node)) {
                span *= 1.0 + (spread / 10.0);
                angleStart = branchAngle - (span / 2.0);
                angleFinish = branchAngle + (span / 2.0);
            }

            double a2 = angleStart;

	        boolean rotate = false;
	        if (node.getAttribute("!rotate") != null &&
			        ((Boolean)node.getAttribute("!rotate"))) {
		        rotate = true;
	        }
            for(i = 0; i < children.size(); ++i) {
	            int index = i;
	            if (rotate) {
		            index = children.size() - i - 1;
	            }

	            Node child = children.get(index);

                final double childLength = tree.getLength(child);
                double a1 = a2;
                a2 = a1 + (span * leafCounts[index] / sumLeafCount);

                Point2D childPoint = constructNode(tree, child, a1, a2,
		                nodePoint.getX(), nodePoint.getY(), childLength, cache);

	            Line2D branchLine = new Line2D.Double(
				            childPoint.getX(), childPoint.getY(),
			                nodePoint.getX(), nodePoint.getY()
				            );

	            Object[] colouring = null;
	            if (branchColouringAttribute != null) {
		            colouring = (Object[])child.getAttribute(branchColouringAttribute);
	            }
	            if (colouring != null) {
		            // If there is a colouring, then we break the path up into
		            // segments. This should allow use to iterate along the segments
		            // and colour them as we draw them.

		            float nodeHeight = (float) tree.getHeight(node);
		            float childHeight = (float) tree.getHeight(child);

		            float x1 = (float)childPoint.getX();
		            float y1 = (float)childPoint.getY();
		            float x0 = (float)nodePoint.getX();
		            float y0 = (float)nodePoint.getY();

		            GeneralPath branchPath = new GeneralPath();

		            // to help this, we are going to draw the branch backwards
		            branchPath.moveTo(x1, y1);
		            float interval = 0.0F;
		            for (int j = 0; j < colouring.length - 1; j+=2) {
//			            float height = ((Number)colouring[j+1]).floatValue();
//			            float p = (height - childHeight) / (nodeHeight - childHeight);
			            interval += ((Number)colouring[j+1]).floatValue();
			            float p = interval / (nodeHeight - childHeight);
			            float x = x1 + ((x0 - x1) * p);
			            float y = y1 + ((y0 - y1) * p);
			            branchPath.lineTo(x, y);
		            }
		            branchPath.lineTo(x0, y0);

		            // add the branchPath to the map of branch paths
		            cache.branchPaths.put(child, branchPath);

	            } else {
		            // add the branchLine to the map of branch paths
		            cache.branchPaths.put(child, branchLine);
	            }

                cache.branchLabelPaths.put(child, (Line2D)branchLine.clone());
            }

            Point2D nodeLabelPoint = new Point2D.Double(xPosition + ((length + 1.0) * directionX),
                    yPosition + ((length + 1.0) * directionY));

            Line2D nodeLabelPath = new Line2D.Double(nodePoint, nodeLabelPoint);
            cache.nodeLabelPaths.put(node, nodeLabelPath);

        } else {

            Point2D taxonPoint = new Point2D.Double(xPosition + ((length + 1.0) * directionX),
                    yPosition + ((length + 1.0) * directionY));

            Line2D taxonLabelPath = new Line2D.Double(nodePoint, taxonPoint);
            cache.tipLabelPaths.put(node, taxonLabelPath);

        }

        Point2D nodeShapePoint = new Point2D.Double(xPosition + ((length - 1.0) * directionX),
                yPosition + ((length - 1.0) * directionY));
        Line2D nodeShapePath = new Line2D.Double(nodePoint, nodeShapePoint);
        cache.nodeShapePaths.put(node, nodeShapePath);

        // add the node point to the map of node points
        cache.nodePoints.put(node, nodePoint);

        return nodePoint;
    }

    private void constructNodeAreas(final RootedTree tree, final Node node, final Area parentNodeArea, TreeLayoutCache cache) {

        if (!tree.isExternal(node)) {

            List<Node> children = tree.getChildren(node);

            boolean rotate = false;
            if (node.getAttribute("!rotate") != null &&
                    ((Boolean)node.getAttribute("!rotate"))) {
                rotate = true;
            }

            int index = (rotate ? children.size() - 1 : 0);
            Node child1 = children.get(index);
            Area childArea1 = new Area();

            constructNodeAreas(tree, child1, childArea1, cache);

            index = (rotate ? 0 : children.size() - 1);
            Node child2 = children.get(index);
            Area childArea2 = new Area();
            constructNodeAreas(tree, child2, childArea2, cache);

            GeneralPath nodePath = new GeneralPath();
            Line2D line1 = (Line2D)cache.getBranchPath(child1);
            Line2D line2 = (Line2D)cache.getBranchPath(child2);

            nodePath.moveTo(line2.getX1(), line2.getY1());
            nodePath.lineTo(line2.getX2(), line2.getY2());
            nodePath.lineTo(line1.getX1(), line1.getY1());

            nodePath.closePath();

            Area nodeArea = new Area(nodePath);

            parentNodeArea.add(nodeArea);
            parentNodeArea.add(childArea1);
            parentNodeArea.add(childArea2);

            nodeArea.subtract(childArea1);
            nodeArea.subtract(childArea2);

            cache.nodeAreas.put(node, nodeArea);
        }
    }

    private void constructHilight(RootedTree tree, Node node, double angleStart, double angleFinish, double xPosition,
                                  double yPosition, double length, TreeLayoutCache cache) {

        Object[] values = (Object[])node.getAttribute(hilightAttributeName);
        int tipCount = (Integer)values[0];
        double tipHeight = (Double)values[1];
        double height = tree.getHeight(node);

        GeneralPath hilightShape = new GeneralPath();

        final double branchAngle = (angleStart + angleFinish) / 2.0;

        float x0 = (float)(xPosition + (0.5 * length * Math.cos(branchAngle)));
        float y0 = (float)(yPosition + (0.5 * length * Math.sin(branchAngle)));

        float x1 = (float)(x0 + (tipHeight * Math.cos(angleStart)));
        float y1 = (float)(y0 + (tipHeight * Math.sin(angleStart)));

        float x2 = (float)(x0 + (tipHeight * Math.cos(angleFinish)));
        float y2 = (float)(y0 + (tipHeight * Math.sin(angleFinish)));

        hilightShape.moveTo(x0, y0);
        hilightShape.lineTo(x1, y1);
        hilightShape.lineTo(x2, y2);
        hilightShape.lineTo(x0, y0);

        hilightShape.closePath();

        // add the collapsedShape to the map of branch paths
        cache.hilightNodes.add(node);
        cache.hilightShapes.put(node, hilightShape);
    }

}