/*-
 *  Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
 * 
 *  This file is part of CityDoctor2.
 *
 *  CityDoctor2 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  CityDoctor2 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 CityDoctor2.  If not, see <https://www.gnu.org/licenses/>.
 */
package de.hft.stuttgart.citydoctor2.edge;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

public class IntersectPolygonAndStraight2d {

	/**
	 * @param pcPolygon                 A polygon
	 * 
	 * @param rcIntersectingStraight    The straight which have been intersected
	 *                                  with the given polygon
	 * 
	 * @param rIntersectionValues       The calculated parameters of the
	 *                                  intersection
	 * 
	 * @param rIntersectedPolygonPoints A set of values, containing parameter values
	 *                                  where the straight intersects the polygon in
	 *                                  a corner point.
	 * 
	 * @return rIntersectionIntervals A list of computed intersection intervals of
	 *         the straight with the polygon
	 * 
	 *         This methods computes the intervals of the intersection between the
	 *         given straight and polygon. It gets only parameters where the
	 *         straight hits the polygon.
	 * 
	 *         The additional set of intersected polygon points is neccessary,
	 *         because each intersection of a corner point with the straight results
	 *         in the computation of the same intersection parameter twice, because
	 *         at every corner point of the polygon two edges are joining. These
	 *         points has to be treated special.
	 * 
	 */
	public static List<Interval> intersect(Polygon2d mcpPolygon, GmBoundedStraight2d mIntStraight, double eps) {

		List<Interval> mIntersectionIntervals = new ArrayList<>();
		TreeSet<Double> intersectionValues = new TreeSet<>(Global.getDoubleTolCompare(eps));
		TreeSet<Double> intersectedPolygonPoints = new TreeSet<>(Global.getDoubleTolCompare(eps));

		List<HalfEdge2d> halfEdges = mcpPolygon.getHalfEdges();
		for (HalfEdge2d heIt : halfEdges) {
			GmBoundedStraight2d hEStraight = heIt.getStraight();
			GmStraight2dIntersectionResult res = hEStraight.intersect(mIntStraight);
			if (res.areParallel()) {
				Vector2d dir = mIntStraight.getDirection();
				Vector2d diffVec = mIntStraight.getOrigin().minus(hEStraight.getOrigin());
				// calculate the do product to check if the vector connecting the
				// origin points of the straight is orthogonal to the direction
				// if so ==> the parallel straights overlap
				double diffVecDotPerpDir = diffVec.dot(dir.getPerpendicularVector());

				if (Math.abs(diffVecDotPerpDir) < Global.getHighAccuracyTolerance()) {
					// Straights are identical
					Point2d p1 = hEStraight.getOrigin();
					Point2d p2 = hEStraight.getTarget();
					Point2d orig = mIntStraight.getOrigin();

					double[] params = new double[2];

					if (Math.abs(dir.getX()) > Global.getHighAccuracyTolerance()) {
						params[0] = (p1.getX() - orig.getX()) / dir.getX();
						params[1] = (p2.getX() - orig.getX()) / dir.getX();
					} else if (Math.abs(dir.getY()) > Global.getHighAccuracyTolerance()) {
						params[0] = (p1.getY() - orig.getY()) / dir.getY();
						params[1] = (p2.getY() - orig.getY()) / dir.getY();
					} else {
						throw new IllegalArgumentException(
								"Directional vector of identical straights is equal to the zero vector");
					}

					// Point2d pnt = mIntStraight.evaluate( params[0] );
					// cout << "got ( " << pnt.getU() << ", " << pnt.getV() << " ) as int point with
					// param " << params[0] << endl;
					// pnt = mIntStraight.evaluate( params[1] );
					// cout << "got ( " << pnt.getU() << ", " << pnt.getV() << " ) as int point with
					// param " << params[1] << endl;

					assignParameterToCorrectList(params[0], intersectionValues, intersectedPolygonPoints);
					assignParameterToCorrectList(params[1], intersectionValues, intersectedPolygonPoints);
				}
			} else {
				if (hEStraight.isWithinBoundaries(res.getParamHE())) {
					// Point2d pnt = mIntStraight.evaluate( paramInt );
					// cout << "got ( " << pnt.getU() << ", " << pnt.getV() << " ) as int point with
					// param " << paramInt << endl;
					assignParameterToCorrectList(res.getParamInt(), intersectionValues, intersectedPolygonPoints);
				}
			}
		}

		processIntersectionIntervals(mcpPolygon, mIntStraight, intersectionValues, intersectedPolygonPoints,
				mIntersectionIntervals);

		return mIntersectionIntervals;
	}

	/**
	 * 
	 * This methods computes the intervals of the intersection between the given
	 * straight and polygon. It gets only parameters where the straight hits the
	 * polygon.
	 * 
	 * <br>
	 * The additional set of intersected polygon points is necessary, because each
	 * intersection of a corner point with the straight results in the computation
	 * of the same intersection parameter twice, because at every corner point of
	 * the polygon two edges are joining. These points has to be treated special.
	 * 
	 * @param crIntersectionValues       The calculated parameters of the
	 *                                   intersection
	 * 
	 * @param crIntersectedPolygonPoints A set of values, containing parameter
	 *                                   values where the straight intersects the
	 *                                   polygon in a corner point.
	 * 
	 * 
	 */
	private static void processIntersectionIntervals(Polygon2d mcpPolygon, GmStraight2d mIntStraight,
			TreeSet<Double> crIntersectionValues, TreeSet<Double> crIntersectedPolygonPoints,
			List<Interval> mIntersectionIntervals) {
		// create intervals
		if (!crIntersectionValues.isEmpty()) {
//		if (crIntersectionValues.size() > 1) {
			Iterator<Double> it1 = crIntersectionValues.iterator();
			double vit1 = it1.next();
			while (it1.hasNext()) {
				double vit2 = it1.next();
				// check if the point between the values (midpoint) is inside of the
				// polygon; this don't need to be true for concave polygons
				// if the midpoint is inside, than we have an interval, if it's outside
				// it's not. i can
				Point2d pnt = mIntStraight.evaluate((vit1 + vit2) / 2);
				if (mcpPolygon.isPointInsidePolygon(pnt)) {
					Interval intIntv = new Interval(vit1, vit2);
					mIntersectionIntervals.add(intIntv);
				}
				vit1 = vit2;
			}
		}

		// insert points intervals, if they are no part of an already existing
		// interval
		for (Double d : crIntersectedPolygonPoints) {
			boolean pntIsPartOfExistingInterval = false;
			for (Interval interval : mIntersectionIntervals) {
				if (pntIsPartOfExistingInterval) {
					break;
				}
				if (interval.getStart() == d || interval.getEnd() == d) {
					pntIsPartOfExistingInterval = true;
				}
			}

			// the current value is not part of an existing interval, so insert it
			if (!pntIsPartOfExistingInterval) {
				mIntersectionIntervals.add(new Interval(d, d));
			}
		}
	}

	/**
	 * 
	 * 
	 * Util method. Checks if the given parameter has been already inserted as
	 * intersection parameter. If this is true, the parameter will be added as
	 * intersected polygon corner point. Otherwise it's just a normal intersection
	 * parameter till now.
	 * 
	 * Just a util method to purge the code
	 * 
	 * @param param                      Calculated intersection parameter
	 * 
	 * @param orIntersectionValues       Set of intersection parameters
	 * 
	 * @param orIntersectedPolygonPoints Set of intersected polygon corner points
	 * 
	 */
	private static void assignParameterToCorrectList(double param, TreeSet<Double> orIntersectionValues,
			TreeSet<Double> orIntersectedPolygonPoints) {
		if (!orIntersectionValues.contains(param)) {
			orIntersectionValues.add(param);
		} else {
			// value is already present ==> this must be a point of the polygon
			// note, it's assumed, that the polygon do not intersect itself
			orIntersectedPolygonPoints.add(param);
		}
	}

}
