using ClipperLib;
using System.Collections.Generic;
using UnityEngine;

public class SlicedPolygon {
    private readonly List<CheesePolygon> cheesePolygons;
	private List<Segment> segments;

    public SlicedPolygon() : this(new Contour()) {
    }

    public SlicedPolygon(Triangle triangle) : this(new Contour(triangle)) {
    }

    public SlicedPolygon(Contour contour) : this(new CheesePolygon(contour)) {
    }

    public SlicedPolygon(CheesePolygon cheesePolygon) {
        this.cheesePolygons = new List<CheesePolygon>();
        this.cheesePolygons.Add(cheesePolygon);
    }

    public SlicedPolygon(List<CheesePolygon> cheesePolygons) {
        this.cheesePolygons = new List<CheesePolygon>(cheesePolygons);
    }

    public SlicedPolygon(List<Triangle> triangles) : this(getPathsFromTriangles(triangles)) { }

    private SlicedPolygon(List<List<IntPoint>> paths) {
        paths = Clipper.CleanPolygons(Clipper.SimplifyPolygons(paths));

        List<Contour> contours = new List<Contour>();
        List<CheesePolygonBuilder> cheesePolygonBuilders = new List<CheesePolygonBuilder>();
        List<bool> willBeBuilt = new List<bool>();
        List<List<int>> isInside = new List<List<int>>();

        foreach (List<IntPoint> path in paths) {
            Contour contour = new ContourBuilder(path).build();
            if (contour.isValid()) {
                contours.Add(contour);
                cheesePolygonBuilders.Add(new CheesePolygonBuilder(contour));
                willBeBuilt.Add(true);
                isInside.Add(new List<int>());
            }
        }

        for (int i = 0; i < contours.Count; i++) {

            for (int j = i + 1; j < contours.Count; j++) {
                if (contours[i].isContourInside(contours[j])) {
                    isInside[j].Add(i);
                } else if (contours[j].isContourInside(contours[i])) {
                    isInside[i].Add(j);
                }
            }
        }

        for (int i = 0; i < contours.Count; i++) {
            if (isInside[i].Count % 2 == 1) {
                willBeBuilt[i] = false;

                int indexOuterContour = 0;
                foreach(int index in isInside[i]) {
                    if (isInside[index].Count == isInside[i].Count - 1) {
                        indexOuterContour = index;
                    }
                }

                cheesePolygonBuilders[indexOuterContour].addHole(contours[i]);
            }
        }

        /*for (int i = 0; i < contours.Count; i++) {

            for (int j = i+1; j < contours.Count; j++) {
                if (contours[i].isContourInside(contours[j])) {
                    
                    willBeBuilt[j] = false;
                } 
                else if (contours[j].isContourInside(contours[i])) {
                    cheesePolygonBuilders[j].addHole(contours[i]);
                    willBeBuilt[i] = false;
                }
            }
        }*/

        this.cheesePolygons = new List<CheesePolygon>();
        for (int i = 0; i < contours.Count; i++) {
            if (willBeBuilt[i]) {
                cheesePolygons.Add(cheesePolygonBuilders[i].build());
            }
        }
    }

    //Returns true iff the vertex is inside the slicedPolygon
    public bool isPointInside(GameVertex vertex) {
        foreach (CheesePolygon cheesePolygon in cheesePolygons) {
            if (cheesePolygon.isPointInside(vertex)) { //is in cheesePolygon
                return true;
            }
        }

        return false;
    }

    public List<CheesePolygon> getDisjointCheesePolygons() {
        return new List<CheesePolygon>(cheesePolygons);
    }

    private List<List<IntPoint>> getPaths() {
        List<List<IntPoint>> paths = new List<List<IntPoint>>();

        foreach (CheesePolygon cheesePolygon in cheesePolygons) {
            paths.AddRange(cheesePolygon.getPaths());
        }

        return paths;
    }

    public SlicedPolygon cutLongEdges() {
        List<CheesePolygon> newCheesePolygons = new List<CheesePolygon>();

        foreach (CheesePolygon cheese in cheesePolygons) {
            newCheesePolygons.Add(cheese.cutLongEdges());
        }

        return new SlicedPolygon(newCheesePolygons);
    }

    public SlicedPolygon updateVertex(GameVertex oldVertex, GameVertex newVertex) {
        List<CheesePolygon> newCheesePolygons = new List<CheesePolygon>();

        foreach (CheesePolygon cheesePolygon in cheesePolygons) {
            newCheesePolygons.Add(cheesePolygon.updateVertex(oldVertex, newVertex));
        }

        return new SlicedPolygon(newCheesePolygons);
    }

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

        foreach (CheesePolygon cheesePolygon in cheesePolygons) {
            vertices.addRange(cheesePolygon.getVertices());
        }

        return vertices;
    }

	public List<Segment> getSegments() {
		if (segments != null) {
			return segments;
		}

		segments = new List<Segment>();

		foreach (CheesePolygon cheesePolygon in cheesePolygons) {
			segments.AddRange(cheesePolygon.getSegments());
		}

		return segments;
	}

    public Set<Contour> getContours() {
        Set<Contour> contours = new Set<Contour>();

        foreach (CheesePolygon cheesePolygon in cheesePolygons) {
            contours.addRange(cheesePolygon.getContours());
        }

        return contours;
    }

    public bool isSelfIntersecting() {
        List<Segment> segments = getSegments();

        foreach(Segment seg1 in segments) {
            foreach(Segment seg2 in segments) {
                if (seg1 != seg2 && seg1.intersect(seg2) && seg1.vertexShared(seg2) == null) {
                    return true;
                }
            }
        }

        return false;
    }

	public bool intersect(Segment toBeTested) {
		getSegments();

		foreach(Segment seg in segments) {
			if (seg.intersect(toBeTested)) {
				return true;
			}
		}

		return false;
	}

	public Set<GameVertex> getIntersectionPoints(Segment toBeTested) {
		getSegments();

		Set<GameVertex> intersectionPoints = new Set<GameVertex>();

		foreach(Segment seg in segments) {
			if (seg.intersect(toBeTested)) {
				intersectionPoints.add(seg.closestPointTo(toBeTested));
			}
		}

		return intersectionPoints;
	}

    public SlicedPolygon union(Contour other) {
        return this.union(new SlicedPolygon(other));
    }

    public SlicedPolygon union(SlicedPolygon other) {
        List<List<IntPoint>> solution = new List<List<IntPoint>>();
        Clipper clipper = new Clipper();
        clipper.AddPaths(this.getPaths(), PolyType.ptSubject, true);
        clipper.AddPaths(other.getPaths(), PolyType.ptClip, true);
        clipper.Execute(ClipType.ctUnion, solution);

        return new SlicedPolygon(solution);
    }

    public SlicedPolygon union(List<SlicedPolygon> others) {
        List<List<IntPoint>> remain = getPaths();

        for (int i = 0; i < others.Count; i++) {
            List<List<IntPoint>> solution = new List<List<IntPoint>>();
            Clipper clipper = new Clipper();
            clipper.AddPaths(remain, PolyType.ptSubject, true);
            clipper.AddPaths(others[i].getPaths(), PolyType.ptClip, true);
            clipper.Execute(ClipType.ctUnion, solution);

            remain = solution;
        }

        return new SlicedPolygon(remain);
    }

    public SlicedPolygon intersection(SlicedPolygon other) {
        List<List<IntPoint>> solution = new List<List<IntPoint>>();
        Clipper clipper = new Clipper();
        clipper.AddPaths(this.getPaths(), PolyType.ptSubject, true);
        clipper.AddPaths(other.getPaths(), PolyType.ptClip, true);
        clipper.Execute(ClipType.ctIntersection, solution);

        return new SlicedPolygon(solution);
    }

    public SlicedPolygon difference(SlicedPolygon other) {
        List<List<IntPoint>> solution = new List<List<IntPoint>>();
        Clipper clipper = new Clipper();
        clipper.AddPaths(this.getPaths(), PolyType.ptSubject, true);
        clipper.AddPaths(other.getPaths(), PolyType.ptClip, true);
        clipper.Execute(ClipType.ctDifference, solution);

        return new SlicedPolygon(solution);
    }

    public SlicedPolygon difference(List<SlicedPolygon> others) {
        return difference(0, others);
    }

    public SlicedPolygon difference(int offset, List<SlicedPolygon> others) {
        List<List<IntPoint>> remain = getPaths();

        for (int i = offset; i < others.Count; i++) {
            List<List<IntPoint>> solution = new List<List<IntPoint>>();
            Clipper clipper = new Clipper();
            clipper.AddPaths(remain, PolyType.ptSubject, true);
            clipper.AddPaths(others[i].getPaths(), PolyType.ptClip, true);
            clipper.Execute(ClipType.ctDifference, solution);

            remain = solution;
        }

        return new SlicedPolygon(remain);
    }

	public float distanceTo(GameVertex v) {
		float minDist = float.MaxValue;

		foreach(CheesePolygon cheesePoly in cheesePolygons) {
			float dist = cheesePoly.distanceTo(v);
			if (minDist > dist) {
				minDist = dist;
			}
		}

		return minDist;
	} 

    private static List<List<IntPoint>> getPathsFromTriangles(List<Triangle> triangles) {
        List<List<IntPoint>> solution = new List<List<IntPoint>>();

        foreach (Triangle triangle in triangles) {
            Clipper clipper = new Clipper();
            clipper.AddPaths(solution, PolyType.ptSubject, true);
            clipper.AddPaths(new SlicedPolygon(triangle.getContour()).getPaths(), PolyType.ptClip, true);
            clipper.Execute(ClipType.ctUnion, solution);
        }

        return solution;
    }

    private static PolyTree getPolyTreeFromPaths(List<List<IntPoint>> paths) {
        Clipper clipper = new Clipper();
        PolyTree tree = new PolyTree();
        clipper.AddPaths(paths, PolyType.ptSubject, true);
        clipper.Execute(ClipType.ctUnion, tree);

        return tree;
    }
}

public class SlicedPolygonBuilder {
    List<CheesePolygon> cheesePolygons = new List<CheesePolygon>();

    public SlicedPolygonBuilder() { }

    public void addCheesePolygon(CheesePolygon cheesePolygon) {
        cheesePolygons.Add(cheesePolygon);
    }

    public SlicedPolygon build() {
        return new SlicedPolygon(cheesePolygons);
    }
}
