package org.locationtech.jtslab.edgeray;

import org.locationtech.jts.algorithm.LineIntersector;
import org.locationtech.jts.algorithm.Orientation;
import org.locationtech.jts.algorithm.RobustLineIntersector;
import org.locationtech.jts.algorithm.locate.IndexedPointInAreaLocator;
import org.locationtech.jts.algorithm.locate.SimplePointInAreaLocator;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Location;
import org.locationtech.jts.geom.Polygon;

public class EdgeRayIntersectionArea {
  
  public static double area(Geometry geom0, Geometry geom1) {
    EdgeRayIntersectionArea area = new EdgeRayIntersectionArea(geom0, geom1);
    return area.getArea();
  }
  
  private Geometry geomA;
  private Geometry geomB;
  double area = 0;

  public EdgeRayIntersectionArea(Geometry geom0, Geometry geom1) {
    this.geomA = geom0;
    this.geomB = geom1;
  }
  
  public double getArea() {
    // TODO: for now assume poly is CW and has no holes
    
    addIntersections();
    addResultVertices(geomA, geomB);
    addResultVertices(geomB, geomA);
    return area;
  }

  private void addIntersections() {
    CoordinateSequence seqA = getVertices(geomA);
    boolean[] isIntersected0 = new boolean[seqA.size()-1];
    CoordinateSequence seqB = getVertices(geomB);
    boolean[] isIntersected1 = new boolean[seqB.size()-1];
    
    boolean isCCWA = Orientation.isCCW(seqA);
    boolean isCCWB = Orientation.isCCW(seqB);
    
    // Compute rays for all intersections
    LineIntersector li = new RobustLineIntersector();
    
    for (int i = 0; i < seqA.size()-1; i++) {
      Coordinate a0 = seqA.getCoordinate(i);
      Coordinate a1 = seqA.getCoordinate(i+1);
      
      if (isCCWA) {
        // flip segment orientation
        Coordinate temp = a0; a0 = a1; a1 = temp;
      }
      
      for (int j = 0; j < seqB.size()-1; j++) {
        Coordinate b0 = seqB.getCoordinate(j);
        Coordinate b1 = seqB.getCoordinate(j+1);
        
        if (isCCWB) {
          // flip segment orientation
          Coordinate temp = b0; b0 = b1; b1 = temp;
        }
        
        li.computeIntersection(a0, a1, b0, b1);
        if (li.hasIntersection()) {
          isIntersected0[i] = true;
          isIntersected1[j] = true;
          
          /**
           * With both rings oriented CW (effectively)
           * There are two situations for segment intersections:
           * 
           * 1) A entering B, B exiting A => rays are IP-A1:R, IP-B0:L
           * 2) A exiting B, B entering A => rays are IP-A0:L, IP-B1:R
           * (where :L/R indicates result is to the Left or Right).
           * 
           * Use full edge to compute direction, for accuracy.
           */
          Coordinate intPt = li.getIntersection(0);
          
          boolean isAenteringB = Orientation.COUNTERCLOCKWISE == Orientation.index(a0, a1, b1);
          
          if ( isAenteringB ) {
            area += EdgeRay.areaTerm(intPt, a0, a1, true);
            area += EdgeRay.areaTerm(intPt, b1, b0, false);
          }
          else {
            area += EdgeRay.areaTerm(intPt, a1, a0, false);
            area += EdgeRay.areaTerm(intPt, b0, b1, true);
          }
        }
      }
    }
  }
    
  private void addResultVertices(Geometry geom0, Geometry geom1) {
    /**
     * Compute rays originating at vertices inside the resultant
     * (i.e. A vertices inside B, and B vertices inside A)
     */
    IndexedPointInAreaLocator locator = new IndexedPointInAreaLocator(geom1);
    CoordinateSequence seq = getVertices(geom0);
    boolean isCW = ! Orientation.isCCW(seq);
    for (int i = 0; i < seq.size()-1; i++) {
      Coordinate vPrev = i == 0 ? seq.getCoordinate(seq.size()-2) : seq.getCoordinate(i-1);
      Coordinate v = seq.getCoordinate(i);
      Coordinate vNext = seq.getCoordinate(i+1);
      if (Location.INTERIOR == locator.locate(v)) {
        area += EdgeRay.areaTerm(v, vPrev, ! isCW);
        area += EdgeRay.areaTerm(v, vNext, isCW);
      }
    }
  }
  
  private CoordinateSequence getVertices(Geometry geom) {
    Polygon poly = (Polygon) geom;
    CoordinateSequence seq = poly.getExteriorRing().getCoordinateSequence();
    return seq;
  }
}
