package de.wwwu.awolf.model;

/**
 * Implementierung verschiedener Algorithmen zur Berechnung von Ausgleichsgeraden.
 *
 * @Author: Armin Wolf
 * @Email: a_wolf28@uni-muenster.de
 * @Date: 12.06.2017.
 */
public class Line implements Comparable<Line>{

    private final Double MAX = 9999d;
    private final Double MIN = -9999d;


    private Double m;
    private Double b;

    private Double x1;
    private Double x2;
    private Double y1;
    private Double y2;

    private String id;

    /**
     * Konstruktor
     *
     * @param m  Steigung
     * @param b  y-Achsenabschnitt
     * @param id id
     */
    public Line(double m, double b, String id) {
        this.m = m;
        this.b = b;

        this.x1 = MIN;
        this.y1 = (MIN * m) + b;
        this.x2 = MAX * 0.5;
        this.y2 = ((MAX * 0.5) * m) + b;
        this.id = id;
    }

    /**
     * Konstruktor
     *
     * @param m Steigung
     * @param b y-Achsenabschnitt
     */
    public Line(double m, double b) {
        this.m = m;
        this.b = b;

        this.x1 = calculateX1(MIN);
        this.y1 = calculateY1(MIN);
        this.x2 = calculateX2(MAX * 0.5);
        this.y2 = calculateY2(MAX * 0.5);
    }

    /**
     * Konstruktor
     *
     * @param x1 x-Koordiante des Startpunkts
     * @param x2 x-Koordinate des Endpunkts
     * @param y1 y-Koordinate des Startpunkts
     * @param y2 y-Koordinate des Endpunkts
     */
    public Line(double x1, double x2, double y1, double y2) {
        this.x1 = x1;
        this.x2 = x2;
        this.y1 = y1;
        this.y2 = y2;

        this.m = (y2 - y1) / (x2 - x1);
        this.b = y2 - (x2 * m);

    }

    private Double calculateX1(Double min) {
        return min;
    }

    private Double calculateY1(Double min) {
        return (min * m) + b;
    }

    private Double calculateX2(Double max) {
        return max;
    }

    private Double calculateY2(Double max) {
        return (max * m) + b;
    }

    /**
     * @return Steigung der Gerade
     */
    public Double getM() {
        return m;
    }

    /**
     * @param m Steigung der Gerade
     */
    public void setM(double m) {
        this.m = m;
    }

    /**
     * @return y-Achsenabschnitt der Gerade
     */
    public Double getB() {
        return b;
    }

    /**
     * @param b y-Achsenabschnitt der Gerade
     */
    public void setB(double b) {
        this.b = b;
    }

    /**
     * @return id der Gerade
     */
    public String getId() {
        return id;
    }

    /**
     * @param id id der Gerade
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * @return x-Koordiante des Startpunkts
     */
    public Double getX1() {
        return x1;
    }

    /**
     * @return x-Koordiante des Endpunkts
     */
    public Double getX2() {
        return x2;
    }

    /**
     * @return y-Koordiante des Startpunkts
     */
    public Double getY1() {
        return y1;
    }

    /**
     * @return y-Koordiante des Endpunkts
     */
    public Double getY2() {
        return y2;
    }

    /**
     * Setzt die Koordianten des Segments. Aus dem Segment wird eine Gerade berechnet.
     *
     * @param x1 x-Koordiante des Startpunkts
     * @param y1 y-Koordiante des Endpunkts
     * @param x2 x-Koordinate des Startpunkts
     * @param y2 y-Koordinte des Endpunkts
     */
    public void setEndPoints(double x1, double y1, double x2, double y2) {
        this.x1 = x1;
        this.x2 = x2;
        this.y1 = y1;
        this.y2 = y2;
        this.m = (y2 - y1) / (x2 - x1);
        this.b = y2 - (x2 * m);
    }

    /**
     * Vergleich einzelner Geradern
     *
     * @param obj zu vergleichende Gerade
     * @return <code>true</code> falls die Geraden Gleich sind
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Line) {
            Line other = (Line) obj;
            return other.getM().equals(this.getM()) && other.getB().equals(this.getB());
        } else {
            return super.equals(obj);
        }
    }

    @Override
    public int hashCode() {
        return super.hashCode() + this.getM().hashCode() + this.getB().hashCode();
    }

    @Override
    public String toString() {
        return "Line m: " + this.getM() + ", b: " + this.getB();
    }


    public Point intersect(Line line) {
        double x = (line.b - this.b) / (this.m - line.m);
        double y = this.m * x + this.b;

        return new Point(x, y);
    }

    // Given three colinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
    public boolean onSegment(Point p, Point q, Point r) {
        return q.getX() <= Math.max(p.getX(), r.getX()) && q.getX() >= Math.min(p.getX(), r.getX()) &&
                q.getY() <= Math.max(p.getY(), r.getY()) && q.getY() >= Math.min(p.getY(), r.getY());
    }

    // To find orientation of ordered triplet (p, q, r).
    // The function returns following values
    // 0 --> p, q and r are colinear
    // 1 --> Clockwise
    // 2 --> Counterclockwise
    public int orientation(Point p, Point q, Point r) {
        // See https://www.geeksforgeeks.org/orientation-3-ordered-points/
        // for details of below formula.
        double val = (q.getY() - p.getY()) * (r.getX() - q.getX()) -
                (q.getX() - p.getX()) * (r.getY() - q.getY());

        if (val == 0) return 0; // colinear

        return (val > 0) ? 1 : 2; // clock or counterclock wise
    }

    // The main function that returns true if line segment 'p1q1'
    // and 'p2q2' intersect.
    // Line A: y = mx + b -->
    public boolean doIntersect(Line line, double lower, double upper) {
        //this
        Point p1 = new Point(calculateX1(lower), calculateY1(lower));
        Point q1 = new Point(calculateX2(upper), calculateY2(upper));
        Point p2 = new Point(line.calculateX1(lower), line.calculateY1(lower));
        Point q2 = new Point(line.calculateX2(upper), line.calculateY2(upper));
        // Find the four orientations needed for general and
        // special cases
        int o1 = orientation(p1, q1, p2);
        int o2 = orientation(p1, q1, q2);
        int o3 = orientation(p2, q2, p1);
        int o4 = orientation(p2, q2, q1);

        // General case
        if (o1 != o2 && o3 != o4)
            return true;

        // Special Cases
        // p1, q1 and p2 are colinear and p2 lies on segment p1q1
        if (o1 == 0 && onSegment(p1, p2, q1)) return true;

        // p1, q1 and q2 are colinear and q2 lies on segment p1q1
        if (o2 == 0 && onSegment(p1, q2, q1)) return true;

        // p2, q2 and p1 are colinear and p1 lies on segment p2q2
        if (o3 == 0 && onSegment(p2, p1, q2)) return true;

        // p2, q2 and q1 are colinear and q1 lies on segment p2q2
        return o4 == 0 && onSegment(p2, q1, q2);// Doesn't fall in any of the above cases
    }

    @Override
    public int compareTo(Line line) {
        if (this.getM().equals(line.getM())) {
            if (this.getB() <= line.getB()) {
                return -1;
            } else {
                return 1;
            }
        } else if (this.getM() < line.getM()) {
            return -1;
        } else {
            return 1;
        }
    }
}
