/*-
 *  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.math;

/**
 * This class represents a line from a point to another point, it can be used to
 * calculate the intersection of two edges
 * 
 * @author Matthias Betz
 *
 */
public class Segment2d {

	private static final double EPSILON = 0.01;
	private Vector2d p1;
	private Vector2d p2;

	public Segment2d(Vector2d p1, Vector2d p2) {
		this.p1 = p1;
		this.p2 = p2;
	}

	public boolean intersects(Segment2d other) {
		double x1 = p1.getX();
		double x2 = p2.getX();
		double x3 = other.p1.getX();
		double x4 = other.p2.getX();

		double y1 = p1.getY();
		double y2 = p2.getY();
		double y3 = other.getP1().getY();
		double y4 = other.getP2().getY();

		double denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);

		double numeratora = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
		double numeratorb = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
		
		if (Math.abs(denominator) < EPSILON) {
			denominator = 0.0;
		}

		if (denominator == 0) {
			// both numerators == 0 means, lines are coincident, otherwise parallel
			if (numeratora == 0 && numeratorb == 0) {
				// check if they are colinear
				return containsPoint(other.p1) || containsPoint(other.p2);
			} else {
				// parallel
				return false;
			}
		} else {
			double ua = numeratora / denominator;
			double ub = numeratorb / denominator;
			return ua > EPSILON && ua < (1 - EPSILON) && ub > EPSILON && ub < (1 - EPSILON);
		}
	}
	
	public boolean intersectsWithoutColinearity(Segment2d other) {
		double x1 = p1.getX();
		double x2 = p2.getX();
		double x3 = other.p1.getX();
		double x4 = other.p2.getX();

		double y1 = p1.getY();
		double y2 = p2.getY();
		double y3 = other.getP1().getY();
		double y4 = other.getP2().getY();

		double denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);


		if (Math.abs(denominator) < EPSILON) {
			denominator = 0.0;
		}
		
		if (denominator == 0) {
			// both numerators == 0 means, lines are coincident, otherwise parallel
			// parallel lines are not intersecting
			return false;
		} else {
			double numeratora = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
			double numeratorb = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
			double ua = numeratora / denominator;
			double ub = numeratorb / denominator;
			return ua > EPSILON && ua < (1 - EPSILON) && ub > EPSILON && ub < (1 - EPSILON);
		}

	}
	
	public Vector2d intersectionPoint(Segment2d other) {
		IntersectionPoint2d inter = calculateIntersection(other);
		if (inter == null) {
			return null;
		}
		return inter.getIntersectionPoint();
	}
	
	public IntersectionPoint2d calculateIntersection(Segment2d other) {
		double x1 = p1.getX();
		double x2 = p2.getX();
		double x3 = other.p1.getX();
		double x4 = other.p2.getX();

		double y1 = p1.getY();
		double y2 = p2.getY();
		double y3 = other.getP1().getY();
		double y4 = other.getP2().getY();
		
		double a1 = x2 - x1;
		double b1 = x4 - x3;
		double c1 = x3 - x1;
		double a2 = y2 - y1;
		double b2 = y4 - y3;
		double c2 = y3 - y1;
		
		double detA = b1 * a2 - a1 * b2;
		double detA1 = b1 * c2 - c1 * b2;
		double detA2 = a1 * c2 - c1 * a2;
		if (detA == 0) {
			// lines parallel or coincident
			if (detA1 == 0 && detA2 == 0) {
				// check if they are colinear
				if (containsPoint(other.p1)) {
					return new IntersectionPoint2d(new Line2d(other.p1, new Vector2d()), 0);
				}
				if (containsPoint(other.p2)) {
					return new IntersectionPoint2d(new Line2d(other.p2, new Vector2d()), 0);
				}
				return null;
			} else {
				// parallel
				return null;
			}
		}
		double s = detA1 / detA;
		double t = detA2 / detA;
		
		if (s < 0 || s > 1 || t < 0 || t > 1) {
			// intersection on line but not on segment
			return null;
		}
		Vector2d dir = new Vector2d(a1, a2);
		Vector2d start = new Vector2d(x1, y1);
		return new IntersectionPoint2d(new Line2d(start, dir), s);
	}
	
	public boolean containsPoint(Vector2d p) {
		double xDir = p2.getX() - p1.getX();
		if (xDir == 0) {
			double yDir = p2.getY() - p1.getY();
			if (yDir == 0) {
				// line is one point
				return p.equals(p1);
			}
			double a = (p.getY() - p1.getY()) / yDir;
			return a < (1 - EPSILON) && a > EPSILON;
		}
		double a = (p.getX() - p1.getX()) / xDir;
		if (a < EPSILON || a > (1 - EPSILON)) {
			return false;
		}
		// direction in y
		double yDir = p2.getY() - p1.getY();
		// check for colinearity
		return p1.getY() + a * yDir == p.getY();
	}

	public double distance(Vector2d p) {
		double x1 = p1.getX();
		double x2 = p2.getX();
		double x3 = p.getX();

		double y1 = p1.getY();
		double y2 = p2.getY();
		double y3 = p.getY();

		Vector2d v = p2.minus(p1);
		double denominator = v.lengthSquare();
		if (denominator == 0) {
			// segment has no length, distance is distance to end/begin point
			v = p.minus(p1);
			return v.length();
		}
		double numerator = (x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1);
		double u = numerator / denominator;
		if (u < 0) {
			u = 0;
		} else if (u > 1) {
			u = 1;
		}
		Vector2d distanceVec = p.minus(p1.plus(v.mult(u)));
		return distanceVec.length();
	}

	public Vector2d getP1() {
		return p1;
	}

	public Vector2d getP2() {
		return p2;
	}

}
