package com.baselet.control.basics.geom;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class GeometricFunctions {

	public static double distanceBetweenTwoPoints(PointDouble p1, PointDouble p2) {
		return GeometricFunctions.distanceBetweenTwoPoints(p1.x, p1.y, p2.x, p2.y);
	}

	public static double getDistanceBetweenLineAndPoint(PointDouble start, PointDouble end, PointDouble pointToCheck) {
		return getDistanceBetweenLineAndPoint(start.x, start.y, end.x, end.y, pointToCheck.x, pointToCheck.y);
	}

	private static double distanceBetweenTwoPoints(double x1, double y1, double x2, double y2) {
		double xDist = x1 - x2;
		double yDist = y1 - y2;
		return Math.sqrt(xDist * xDist + yDist * yDist);
	}

	/**
	 * implementation is based on http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment/2233538#2233538
	 */
	private static double getDistanceBetweenLineAndPoint(double x1, double y1, double x2, double y2, double checkX, double checkY) {
		double px = x2 - x1;
		double py = y2 - y1;
		if (px == 0.0 && py == 0.0) { // if both ends of the line are on the same point, it's just the distance between the location of the 0-length line and the check point
			return distanceBetweenTwoPoints(x1, y1, checkX, checkY);
		}

		double mult = px * px + py * py;
		double u = ((checkX - x1) * px + (checkY - y1) * py) / mult;

		if (u > 1) {
			u = 1;
		}
		else if (u < 0) {
			u = 0;
		}

		double x = x1 + u * px;
		double y = y1 + u * py;

		return GeometricFunctions.distanceBetweenTwoPoints(x, y, checkX, checkY);
	}

	/**
	 * for math information see http://www.mathisfunforum.com/viewtopic.php?id=9657
	 */
	public static PointDouble getPointOnLineWithDistanceFromStart(PointDouble start, PointDouble end, double distance) {
		double xDiff = end.getX() - start.getX();
		double yDiff = end.getY() - start.getY();
		double length = distanceBetweenTwoPoints(start, end);
		double distanceToGo = distance / length;
		return new PointDouble(start.getX() + xDiff * distanceToGo, start.getY() + yDiff * distanceToGo);
	}

	/**
	 * from https://stackoverflow.com/questions/15594424/line-crosses-rectangle-how-to-find-the-cross-points/15594751#15594751
	 */
	public static PointDouble getIntersectionPoint(Line lineA, Line lineB) {
		double x1 = lineA.getStart().getX();
		double y1 = lineA.getStart().getY();
		double x2 = lineA.getEnd().getX();
		double y2 = lineA.getEnd().getY();

		double x3 = lineB.getStart().getX();
		double y3 = lineB.getStart().getY();
		double x4 = lineB.getEnd().getX();
		double y4 = lineB.getEnd().getY();

		PointDouble p = null;

		double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
		if (d != 0) {
			double xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
			double yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;

			p = new PointDouble(xi, yi);

			// remove intersections which are not on the line (use 1 instead of 0 distance for tolerance)
			if (lineA.getDistanceToPoint(p) > 1 || lineB.getDistanceToPoint(p) > 1) {
				p = null;
			}
		}

		return p;
	}

	public static List<PointDouble> getIntersectionPoints(Line line, Rectangle rectangle) {
		List<PointDouble> list = Arrays.asList(
				// TOP
				getIntersectionPoint(line,
						new Line(new PointDouble(rectangle.getX(), rectangle.getY()),
								new PointDouble(rectangle.getX() + rectangle.getWidth(), rectangle.getY()))),
				// BOTTOM
				getIntersectionPoint(line,
						new Line(new PointDouble(rectangle.getX(), rectangle.getY() + rectangle.getHeight()),
								new PointDouble(rectangle.getX() + rectangle.getWidth(), rectangle.getY() + rectangle.getHeight()))),
				// LEFT
				getIntersectionPoint(line,
						new Line(new PointDouble(rectangle.getX(), rectangle.getY()),
								new PointDouble(rectangle.getX(), rectangle.getY() + rectangle.getHeight()))),
				// RIGHT
				getIntersectionPoint(line,
						new Line(new PointDouble(rectangle.getX() + rectangle.getWidth(), rectangle.getY()),
								new PointDouble(rectangle.getX() + rectangle.getWidth(), rectangle.getY() + rectangle.getHeight()))));

		// remove nulls
		List<PointDouble> returnList = new ArrayList<PointDouble>();
		for (PointDouble p : list) {
			if (p != null) {
				returnList.add(p);
			}
		}
		return returnList;
	}
}
