package com.baselet.control.basics.geom;

import java.util.List;

import org.apache.log4j.Logger;

import com.baselet.control.enums.Direction;

public class Line {

	private static final Logger log = Logger.getLogger(Line.class);

	private final PointDouble start;
	private final PointDouble end;

	public Line(PointDouble start, PointDouble end) {
		super();
		this.start = start;
		this.end = end;
	}

	public Line(Point start, Point end) {
		super();
		this.start = new PointDouble(start);
		this.end = new PointDouble(end);
	}

	public Line(IntLine intPointLine) {
		super();
		start = new PointDouble(intPointLine.getStart());
		end = new PointDouble(intPointLine.getEnd());
	}

	public PointDouble getStart() {
		return start;
	}

	public PointDouble getEnd() {
		return end;
	}

	public PointDouble getPoint(boolean start) {
		return start ? getStart() : getEnd();
	}

	public PointDouble getCenter() {
		return new PointDouble((start.getX() + end.getX()) / 2, (start.getY() + end.getY()) / 2);
	}

	public double getLength() {
		return GeometricFunctions.distanceBetweenTwoPoints(start, end);
	}

	public PointDouble getPointOnLineWithDistanceFrom(boolean fromStart, double distance) {
		if (fromStart) {
			return GeometricFunctions.getPointOnLineWithDistanceFromStart(start, end, distance);
		}
		else { // from end
			return GeometricFunctions.getPointOnLineWithDistanceFromStart(end, start, distance);
		}
	}

	public double getAngleOfSlope() {
		double radius = getLength();
		double rad = Math.acos((start.x - end.x) / radius);
		double radDeg = Math.toDegrees(rad);
		if (start.y < end.y) {
			radDeg = 360 - radDeg;
		}
		return radDeg;
	}

	/**
	 * Checks the distance between this line and a specific point
	 * @param pointToCheck point to check the distance
	 * @return minimal distance from point to line as double value
	 */
	public double getDistanceToPoint(PointDouble pointToCheck) {
		double dist = GeometricFunctions.getDistanceBetweenLineAndPoint(start, end, pointToCheck);
		log.trace("Minimal distance between " + this + " and " + pointToCheck + " is " + dist);
		return dist;
	}

	public PointDouble[] toPoints() {
		return new PointDouble[] { start, end };
	}

	public Direction getDirectionOfLine(boolean directionOfStart) {
		double angleOfSlope = getAngleOfSlope();
		Direction direction;
		if (angleOfSlope > 315 || angleOfSlope < 45) {
			direction = Direction.RIGHT;
		}
		else if (angleOfSlope < 135) {
			direction = Direction.DOWN;
		}
		else if (angleOfSlope < 225) {
			direction = Direction.LEFT;
		}
		else {
			direction = Direction.UP;
		}
		if (!directionOfStart) {
			direction = direction.invert();
		}
		return direction;
	}

	public List<PointDouble> getIntersectionPoints(Rectangle rectangle) {
		return GeometricFunctions.getIntersectionPoints(this, rectangle);
	}

	public Line getShorterVersion(boolean fromStart, double shortenBy) {
		PointDouble shortenedPoint = getPointOnLineWithDistanceFrom(fromStart, shortenBy);
		if (fromStart) {
			return new Line(shortenedPoint, end);
		}
		else {
			return new Line(start, shortenedPoint);
		}
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (end == null ? 0 : end.hashCode());
		result = prime * result + (start == null ? 0 : start.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		Line other = (Line) obj;
		if (end == null) {
			if (other.end != null) {
				return false;
			}
		}
		else if (!end.equals(other.end)) {
			return false;
		}
		if (start == null) {
			if (other.start != null) {
				return false;
			}
		}
		else if (!start.equals(other.start)) {
			return false;
		}
		return true;
	}

	@Override
	public String toString() {
		return "Line [start=" + start + ", end=" + end + "]";
	}

}
