using System.Collections.Generic;

using NetPoly = TriangleNet.Geometry.Polygon;
using NetVertex = TriangleNet.Geometry.Vertex;
using NetContour = TriangleNet.Geometry.Contour;
using NetTriangle = TriangleNet.Geometry.ITriangle;
using NetMesh = TriangleNet.Meshing.IMesh;
using ClipperLib;
using UnityEngine;

public class Contour {
    private List<GameVertex> vertices;
	private List<Segment> segments = null;

    public Contour() {
        vertices = new List<GameVertex>();
    }

    public Contour(Triangle triangle) {
        vertices = new List<GameVertex>();
        
        for (int i = 0; i < 3; i++) {
            vertices.Add(triangle[i]);
        }
    }

    public Contour(List<GameVertex> list) {
        vertices = new List<GameVertex>(list);
    }

    public bool isValid() {
        return vertices.Count >= 3;// && Mathf.Abs((float)Clipper.Area(getPath())) >= Constants.MINIMUM_AREA;
    }

    //Returns true iff the point is in the contour
    public bool isPointInside(GameVertex point) {
        int j = vertices.Count - 1;
        bool inside = false;
        for (int i = 0; i < vertices.Count; j = i++) {
            if (((vertices[i].y() <= point.y() && point.y() < vertices[j].y()) || (vertices[j].y() <= point.y() && point.y() < vertices[i].y())) &&
               point.x() < (vertices[j].x() - (double)vertices[i].x()) * ((double)point.y() - vertices[i].y()) / (vertices[j].y() - (double)vertices[i].y()) + vertices[i].x())
                inside = !inside;
        }
        return inside;
    }

    public bool isContourInside(Contour other) {
        foreach (GameVertex vOther in other.vertices) {
            if (!isPointInside(vOther)) {
                return false;
            }
        }

        return true;
    }

    public NetContour toNetContour() {
        List<NetVertex> netVertices = new List<NetVertex>();

        for (int i = 0; i < vertices.Count; i++) {
            netVertices.Add(new NetVertex(vertices[i].getIntPoint().X, vertices[i].getIntPoint().Y));
        }

        return new NetContour(netVertices);
    }

    //This method should only be called by Cheese and Sliced Polygons
    public List<IntPoint> getPath() {
        List<IntPoint> path = new List<IntPoint>();

        foreach (GameVertex v in vertices) {
            path.Add(v.getIntPoint());
        }

        return path;
    }

    public int count() {
        return vertices.Count;
    }

    public GameVertex this[int index] {
        get {
            if (index >= 0 && index < vertices.Count) {
                return vertices[index];
            } else {
                return null;
            }
        }
        set { //Cannot modify the vertices of a contour
            return;
        }
    }

    public Set<GameVertex> getVertices() {
        return new Set<GameVertex>(vertices);
    }

    public List<Segment> getSegments() {
		constructSegments();

        return segments;
    }

    public Contour cutLongEdges() {
        List<GameVertex> newVertices = new List<GameVertex>(vertices);

        bool done = false;

        do {
            done = true;

            for (int i = 0; i < newVertices.Count; i++) {
                int next = (i + 1) % newVertices.Count;

                if (newVertices[i].distance(newVertices[next]) > Constants.MAX_DISTANCE_IN_CONTOUR) {
                    newVertices.Insert(i + 1, (newVertices[i] + newVertices[next]) / 2);

                    done = false;
                    break;
                }
            }

        } while (!done);


        return new Contour(newVertices);
    }

    public Contour updateVertex(GameVertex oldVertex, GameVertex newVertex) {
        List<GameVertex> newVertices = new List<GameVertex>();

        foreach (GameVertex vertex in vertices) {
            if (vertex.Equals(oldVertex)) {
                newVertices.Add(newVertex);
            } else {
                newVertices.Add(vertex);
            }
        }

        return new Contour(newVertices);
    }

	public float distanceTo(GameVertex v) {
		constructSegments();

		float minDist = float.MaxValue;

		foreach(Segment segment in segments) {
			float dist = segment.distanceTo(v);
			if (minDist > dist) {
				minDist = dist;
			}
		}

		return minDist;
	}

	private void constructSegments() {
		if (segments != null) {
			return;
		}

		segments = new List<Segment>();

		for (int i = 0; i < vertices.Count; i++) {
			segments.Add(new Segment(vertices [i], vertices [(i + 1) % vertices.Count]));
		}
	}
}

public class ContourBuilder {
    private List<GameVertex> vertices = new List<GameVertex>();

    public ContourBuilder() {}

    //This constructor should only be called in SlicedPolygon
    public ContourBuilder(List<IntPoint> path) {
        vertices = new List<GameVertex>();

        for (int i = 0; i < path.Count; i++) {
            vertices.Add(new GameVertex(path[i]));

            /*int nextNext = (i + 2) % path.Count;
            if (path[nextNext].X / 100 == path[i].X / 100 && path[nextNext].Y / 100 == path[i].Y / 100) {
                path[nextNext] = path[i];
                i++;
            }*/
        }
    }

    public void addVertex(GameVertex v) {
        vertices.Add(v);
    }

    public int count() {
        return vertices.Count;
    }

    public GameVertex pointAt(int i) {
        return vertices[i];
    }

    public Contour build() {
        return new Contour(vertices);
    }
}
