/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2006, GeoTools Project Managment Committee (PMC)
 *        
 *    This library 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;
 *    version 2.1 of the License.
 *
 *    This library 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.
 */
package org.geotools.graph.util.delaunay;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import org.geotools.graph.structure.Edge;
import org.geotools.graph.structure.Node;
import org.geotools.graph.structure.line.XYNode;

/**
 *
 * @author jfc173
 */
public class Triangle {
    
    Edge edge1, edge2, edge3;
    Node node1, node2, node3;
    GeometryFactory fact = new GeometryFactory();    
    
    public final static int OUTSIDE = 0;
    public final static int INSIDE = 1;
    public final static int ON_EDGE = 2;
    private final static double TOLERANCE = 0.0001;    
    
    /** Creates a new instance of Triangle */
    public Triangle(Edge e1, Edge e2, Edge e3) {
        Node n1a = e1.getNodeA();
        Node n1b = e1.getNodeB();
        Node n2a = e2.getNodeA();
        Node n2b = e2.getNodeB();
        Node n3a = e3.getNodeA();
        Node n3b = e3.getNodeB();
        
        Node temp1, temp2, temp3;
        if (n1a.equals(n2a)){
            temp1 = n1a; //==n2a
            temp2 = n1b;
            temp3 = n2b;
            if (n3a.equals(temp2)){
                if (n3b.equals(temp3)){
                    assign(e1, e2, e3, temp1, temp2, temp3);
                } else {
                    whinge(e1, e2, e3);
                }
            } else if (n3a.equals(temp3)){
                if (n3b.equals(temp2)){
                    assign(e1, e2, e3, temp1, temp2, temp3);
                } else {
                    whinge(e1, e2, e3);
                }
            } else {
                whinge(e1, e2, e3);
            }
        } else if (n1a.equals(n2b)){
            temp1 = n1a; //==n2b
            temp2 = n1b;
            temp3 = n2a;
            if (n3a.equals(temp2)){
                if (n3b.equals(temp3)){
                    assign(e1, e2, e3, temp1, temp2, temp3);
                } else {
                    whinge(e1, e2, e3);
                }
            } else if (n3a.equals(temp3)){
                if (n3b.equals(temp2)){
                    assign(e1, e2, e3, temp1, temp2, temp3);
                } else {
                    whinge(e1, e2, e3);
                }
            } else {
                whinge(e1, e2, e3);
            }            
        } else if (n1b.equals(n2a)){
            temp1 = n1a;
            temp2 = n1b; //==n2a
            temp3 = n2b;
            if (n3a.equals(temp1)){
                if (n3b.equals(temp3)){
                    assign(e1, e2, e3, temp1, temp2, temp3);
                } else {
                    whinge(e1, e2, e3);
                }
            } else if (n3a.equals(temp3)){
                if (n3b.equals(temp1)){
                    assign(e1, e2, e3, temp1, temp2, temp3);
                } else {
                    whinge(e1, e2, e3);
                }
            } else {
                whinge(e1, e2, e3);
            }             
        } else if (n1b.equals(n2b)){
            temp1 = n1a;
            temp2 = n1b; //==n2b
            temp3 = n2a;
            if (n3a.equals(temp1)){
                if (n3b.equals(temp3)){
                    assign(e1, e2, e3, temp1, temp2, temp3);
                } else {
                    whinge(e1, e2, e3);
                }
            } else if (n3a.equals(temp3)){
                if (n3b.equals(temp1)){
                    assign(e1, e2, e3, temp1, temp2, temp3);
                } else {
                    whinge(e1, e2, e3);
                }
            } else {
                whinge(e1, e2, e3);
            }               
        } else {
            whinge(e1, e2, e3);
        }
        
    }
    
    private void assign(Edge e1, Edge e2, Edge e3, Node n1, Node n2, Node n3){
        edge1 = e1;
        edge2 = e2;
        edge3 = e3;
        node1 = n1;
        node2 = n2;
        node3 = n3;          
    }
    
    private void whinge(Edge e1, Edge e2, Edge e3){
        throw new RuntimeException("You didn't give me a proper triangle.  " + e1 + ", " + e2 + ", " + e3);
    }
    
    public int relate(XYNode n){
        int ret;
        if ((!(node1 instanceof XYNode)) ||
            (!(node2 instanceof XYNode)) ||
            (!(node3 instanceof XYNode))){
            throw new RuntimeException("I can't perform a relate function on a non-spatial triangle");
        }
        LinearRing lr = fact.createLinearRing(new Coordinate[]{((XYNode)node1).getCoordinate(), ((XYNode)node2).getCoordinate(), ((XYNode)node3).getCoordinate(), ((XYNode)node1).getCoordinate()});
        Polygon poly = fact.createPolygon(lr, null);
        Point nPoint = fact.createPoint(n.getCoordinate());
        
        Line2D.Double line12 = new Line2D.Double(((XYNode)node1).getCoordinate().x, ((XYNode)node1).getCoordinate().y, ((XYNode)node2).getCoordinate().x, ((XYNode)node2).getCoordinate().y);
        Line2D.Double line13 = new Line2D.Double(((XYNode)node1).getCoordinate().x, ((XYNode)node1).getCoordinate().y, ((XYNode)node3).getCoordinate().x, ((XYNode)node3).getCoordinate().y);
        Line2D.Double line23 = new Line2D.Double(((XYNode)node2).getCoordinate().x, ((XYNode)node2).getCoordinate().y, ((XYNode)node3).getCoordinate().x, ((XYNode)node3).getCoordinate().y);
        Point2D.Double point2D = new Point2D.Double(n.getCoordinate().x, n.getCoordinate().y);
        if ((line12.ptSegDist(point2D) <= TOLERANCE) ||
            (line13.ptSegDist(point2D) <= TOLERANCE) ||
            (line23.ptSegDist(point2D) <= TOLERANCE)){
            ret = ON_EDGE;
        } else if (poly.contains(nPoint)){
            ret = INSIDE;
        } else {
            ret = OUTSIDE;
        }
        return ret;
    }            
    
    public Edge getBoundaryEdge(XYNode n){
        if (!(relate(n) == ON_EDGE)){
            throw new RuntimeException("Can't get the boundary edge for a point that isn't on an edge.");
        }
        Point2D.Double point = new Point2D.Double(n.getCoordinate().x, n.getCoordinate().y);
        Line2D.Double line1 = new Line2D.Double(((XYNode) edge1.getNodeA()).getCoordinate().x, ((XYNode) edge1.getNodeA()).getCoordinate().y,
                                                ((XYNode) edge1.getNodeB()).getCoordinate().x, ((XYNode) edge1.getNodeB()).getCoordinate().y);
        Line2D.Double line2 = new Line2D.Double(((XYNode) edge2.getNodeA()).getCoordinate().x, ((XYNode) edge2.getNodeA()).getCoordinate().y,
                                                ((XYNode) edge2.getNodeB()).getCoordinate().x, ((XYNode) edge2.getNodeB()).getCoordinate().y);
        Line2D.Double line3 = new Line2D.Double(((XYNode) edge3.getNodeA()).getCoordinate().x, ((XYNode) edge3.getNodeA()).getCoordinate().y,
                                                ((XYNode) edge3.getNodeB()).getCoordinate().x, ((XYNode) edge3.getNodeB()).getCoordinate().y);   
        Edge ret = null;
        if (line1.ptSegDist(point) <= TOLERANCE){
            ret = edge1;
        } else if (line2.ptSegDist(point) <= TOLERANCE){
            ret = edge2;
        } else if (line3.ptSegDist(point) <= TOLERANCE){
            ret = edge3;
        } else {
            throw new RuntimeException("So...  node " + n + " is on an edge of " + this.toString() + " but isn't on any of its edges: " + edge1 + ", " + edge2 + ", or " + edge3);
        }
        return ret;
    }
    
    public Node getThirdNode(Edge e){
        if (e.getNodeA().equals(node1)){            
            if (e.getNodeB().equals(node2)){
                return node3;
            } else if (e.getNodeB().equals(node3)){
                return node2;
            } else {
                throw new RuntimeException("Edge e must be in this triangle for Triangle.getThirdNode to work!");
            }
        } else if (e.getNodeA().equals(node2)){
            if(e.getNodeB().equals(node1)){
                return node3;
            } else if (e.getNodeB().equals(node3)){
                return node1;
            } else {
                throw new RuntimeException("Edge e must be in this triangle for Triangle.getThirdNode to work!");               
            }
        } else if (e.getNodeA().equals(node3)){
            if (e.getNodeB().equals(node2)){
                return node1;
            } else if (e.getNodeB().equals(node1)){
                return node2;
            } else {
                throw new RuntimeException("Edge e must be in this triangle for Triangle.getThirdNode to work!");
            }
        } else {
            throw new RuntimeException("Edge " + e + " must be in this triangle " + this.toString() + " for Triangle.getThirdNode to work!");
        }
    }
    
    public Edge getOppositeEdge(Node n){
        if ((edge1.getNodeA().equals(n) || (edge1.getNodeB().equals(n)))){
            if ((edge2.getNodeA().equals(n) || (edge2.getNodeB().equals(n)))){
                return edge3;
            } else if ((edge3.getNodeA().equals(n) || (edge3.getNodeB().equals(n)))){
                return edge2;
            } else {
                throw new RuntimeException("Node n must be in this triangle for Triangle.getOppositeEdge to work!");
            }
        } else if ((edge2.getNodeA().equals(n) || (edge2.getNodeB().equals(n)))){
            if ((edge3.getNodeA().equals(n) || (edge3.getNodeB().equals(n)))){
                return edge1;
            } else {
                throw new RuntimeException("Node n must be in this triangle for Triangle.getOppositeEdge to work!");
            }
        } else {
            throw new RuntimeException("Node n must be in this triangle for Triangle.getOppositeEdge to work!");
        }
    }
    
    public Edge getSharedEdge(Triangle t){
        Edge[] tEdges = t.getEdges();
        Edge shared = null;
        for (int i = 0; i < 3; i++){
            if (tEdges[i].equals(edge1)){
                shared = edge1;
            } else if (tEdges[i].equals(edge2)){
                shared = edge2;
            } else if (tEdges[i].equals(edge3)){
                shared = edge3;
            }
        }
        return shared;            
    }
    
    public Edge[] getEdges(){
        return new Edge[]{edge1, edge2, edge3};
    }
    
    public Node[] getNodes(){
        return new Node[]{node1, node2, node3};
    }
    
    public double getArea(){
        if ((node1 instanceof XYNode) ||
            (node2 instanceof XYNode) ||
            (node3 instanceof XYNode)){
            double x1 = ((XYNode) node1).getCoordinate().x;
            double y1 = ((XYNode) node1).getCoordinate().y;
            double x2 = ((XYNode) node2).getCoordinate().x;
            double y2 = ((XYNode) node2).getCoordinate().y;
            double x3 = ((XYNode) node3).getCoordinate().x;
            double y3 = ((XYNode) node3).getCoordinate().y;
            
            double length1_2 = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
            double length1_3 = Math.sqrt((x1-x3)*(x1-x3) + (y1-y3)*(y1-y3));
            double length2_3 = Math.sqrt((x2-x3)*(x2-x3) + (y2-y3)*(y2-y3));
            double s = (length1_2 + length1_3 + length2_3)/2;
            
            return Math.sqrt((s) * (s - length1_2) * (s - length1_3) * (s - length2_3));                        
        } else {
            throw new RuntimeException("I can't calculate the area if the triangle doesn't have XY coordinates.");
        }
    }
    
    public boolean containsEdge(Edge e){
        return ((edge1.equals(e)) || (edge2.equals(e)) || (edge3.equals(e)));
    }
    
    public boolean equals(Object o){
        boolean ret;
        if (o instanceof Triangle){
            ret = ((this.containsEdge(((Triangle) o).getEdges()[0])) &&
                   (this.containsEdge(((Triangle) o).getEdges()[1])) &&
                   (this.containsEdge(((Triangle) o).getEdges()[2])));
        } else {
            ret = false;
        }
        return ret;
    }
    
    public String toString(){
        return ("{" + node1.toString() + ", " + node2.toString() + ", " + node3.toString() + "}");
    }
    
}
