using UnityEngine;
using ClipperLib;
using System;

/*
 * This class represents a segment, ie a line bounded by two points 
*/
public class Segment {
    protected GameVertex firstPoint;
    protected GameVertex secondPoint;

    public Segment(GameVertex firstPoint, GameVertex secondPoint) {
        this.firstPoint = firstPoint;
        this.secondPoint = secondPoint;
    }

    public GameVertex getFirstPoint() {
        return firstPoint;
    }

    public GameVertex getSecondPoint() {
        return secondPoint;
    }

    public void addToGame() {
        Debug.DrawLine(new Vector3(firstPoint.x(), firstPoint.y(), -2.1f), new Vector3(secondPoint.x(), secondPoint.y(), -2.1f), Color.black, 10000);
    }

    //Outputs the closest point on this segment to the other segment
    public GameVertex closestPointTo(Segment other) {
        return new GameVertex(closestPointAndDistance(this.firstPoint.getVector(), this.secondPoint.getVector(), other.firstPoint.getVector(), other.secondPoint.getVector())._1);
    }

    //Outputs the closest point on this segment to the given point
    public GameVertex closestPointTo(GameVertex point) {
        return new GameVertex(closestPoint(this.firstPoint.getVector(), this.secondPoint.getVector(), point.getVector())._1);
    }

    //Outputs the minimal distance between the two segments
    public float distanceTo(Segment other) {
        return closestPointAndDistance(this.firstPoint.getVector(), this.secondPoint.getVector(), other.firstPoint.getVector(), other.secondPoint.getVector())._2;
    }

    //Outputs the minimal distance between the segment and the point
    public float distanceTo(GameVertex point) {
        return closestPoint(this.firstPoint.getVector(), this.secondPoint.getVector(), point.getVector())._2;
    }

    //Returns true iff the two segment intersect
    public bool intersect(Segment other) {
        return distanceTo(other) < 1E-4;
    }

    public GameVertex vertexShared(Segment other) {
        if (other.firstPoint == firstPoint || other.secondPoint == firstPoint) {
            return firstPoint;
        }
        else if (other.firstPoint == secondPoint || other.secondPoint == secondPoint) {
            return secondPoint;
        }
        return null;
    }

    //Returns true iff the point is on the segment
    public bool pointOnSegment(GameVertex point) {
        return distanceTo(point) < 1E-4;
    }

    public override bool Equals(object obj) {
        Segment other = obj as Segment;

        if (other == null) {
            return false;
        }

        return (other.firstPoint == firstPoint && other.secondPoint == secondPoint) || (other.secondPoint == firstPoint && other.firstPoint == secondPoint);
    }

    public override int GetHashCode() {
        return firstPoint.GetHashCode() + secondPoint.GetHashCode();
    }

    public override string ToString() {
        return firstPoint.ToString() + " ; " + secondPoint.ToString();
    }

    //Outputs the point p in [p1, p2], that is closest to [p3, p4], and the distance p <-> [p3, p4]
    private static Tuple<Vector2, float> closestPointAndDistance(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4) {
        // See 
        // http://geomalgorithms.com/a07-_distance.html
        Vector2 u = p2 - p1;
        Vector2 v = p4 - p3;
        Vector2 w = p1 - p3;
        float a = dot(u, u);         // always >= 0
        float b = dot(u, v);
        float c = dot(v, v);         // always >= 0
        float d = dot(u, w);
        float e = dot(v, w);
        float D = a * c - b * b;        // always >= 0
        float sc, sN, sD = D;       // sc = sN / sD, default sD = D >= 0
        float tc, tN, tD = D;       // tc = tN / tD, default tD = D >= 0

        // compute the line parameters of the two closest points
        if (D < float.Epsilon) { // the lines are almost parallel
            sN = 0.0f;         // force using point P0 on segment S1
            sD = 1.0f;         // to prevent possible division by 0.0 later
            tN = e;
            tD = c;
        } else {                 // get the closest points on the infinite lines
            sN = (b * e - c * d);
            tN = (a * e - b * d);
            if (sN < 0.0f) {        // sc < 0 => the s=0 edge is visible
                sN = 0.0f;
                tN = e;
                tD = c;
            } else if (sN > sD) {  // sc > 1  => the s=1 edge is visible
                sN = sD;
                tN = e + b;
                tD = c;
            }
        }

        if (tN < 0.0f) {            // tc < 0 => the t=0 edge is visible
            tN = 0.0f;
            // recompute sc for this edge
            if (-d < 0.0f)
                sN = 0.0f;
            else if (-d > a)
                sN = sD;
            else {
                sN = -d;
                sD = a;
            }
        } else if (tN > tD) {      // tc > 1  => the t=1 edge is visible
            tN = tD;
            // recompute sc for this edge
            if ((-d + b) < 0.0f)
                sN = 0;
            else if ((-d + b) > a)
                sN = sD;
            else {
                sN = (-d + b);
                sD = a;
            }
        }
        // finally do the division to get sc and tc
        sc = (Math.Abs(sN) <= 1E-5 ? 0.0f : sN / sD);
        tc = (Math.Abs(tN) <= 1E-5 ? 0.0f : tN / tD);

        return new Tuple<Vector2, float>(p1 + (sc * u), (w + (sc * u) - (tc * v)).sqrMagnitude);   // return the point and the distance
    }

    //Outputs the point r in [p1, p2], that is closest to p, and the distance r <-> p
    private static Tuple<Vector2, float> closestPoint(Vector2 a, Vector2 b, Vector2 p) {
        Vector2 v = (b - a);
        Vector2 diff = (p - a);

        float coeff = dot(v, diff) / v.sqrMagnitude;

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

        //Debug.Log(a + s + " ; " + dist + " ; " + s + " ; " + diff);

        return new Tuple<Vector2, float>(a + v * coeff, (a + v * coeff - p).sqrMagnitude);
    }

    public static float dot(Vector2 a, Vector2 b) {
        return a.x * b.x + a.y * b.y;
    }

    public static float cross(Vector2 a, Vector2 b) {
        return a.x * b.y - a.y * b.x;
    }

    public static float angle(Vector2 a, Vector2 b) {
        return Mathf.Acos(dot(a.normalized, b.normalized));
    }
}