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

/*
 * This class represents a finite list of points, which represents the path of a unit. 
*/
public class Path {

    private LinkedList<Waypoint> path;
	private List<Waypoint> endWaypoints;

	public Path() {
		this.path = new LinkedList<Waypoint>();
	}

	public Path(Vertex v) {
		this.path = new LinkedList<Waypoint>();
		path.AddLast(new StaticWaypoint(v));
    }

	public Path(List<GameVertex> vertices, DeployedUnit unit) {
		this.path = new LinkedList<Waypoint>();

		foreach(Tuple<GameVertex, float> tuple in getFinalPoints(vertices, unit)) {
			path.AddLast(new StaticWaypoint(Converter.gameCooToMeters(tuple._1, tuple._2 + unit.getCruiseAltitude())));
		}
	}

	private List<Tuple<GameVertex, float>> getFinalPoints(List<GameVertex> vertices, DeployedUnit unit) {
		List<Tuple<GameVertex, float>> ret = new List<Tuple<GameVertex, float>>();

		for(int i = 0; i < vertices.Count; i++) {
			ret.Add(new Tuple<GameVertex, float>(vertices[i], GameController.altitudeAt(vertices[i], unit.getClass().getPathConstraints())));

			if (i < vertices.Count - 1) {
				Segment seg = new Segment(vertices[i], vertices[i+1]);

				ret.AddRange(getIntermediaryPoints(seg, unit));
			}
		}

		ret.RemoveAt(0); //remove first point

		return ret;
	}

	private List<Tuple<GameVertex, float>> getIntermediaryPoints(Segment seg, DeployedUnit unit) {
		PathConstraints constraints = unit.getClass().getPathConstraints();

		Set<Tuple<GameVertex, float>> intersections = new Set<Tuple<GameVertex, float>>();
		foreach(MapPolygon mapPoly in GameController.getMap()) {
			if (constraints.canGoThrough(mapPoly.getTerrain())) { // no need to test uncrossable polygons
				Set<GameVertex> inter = mapPoly.getSlicedPolygon().getIntersectionPoints(seg);

				foreach(GameVertex vertex in inter) {
					intersections.add(new Tuple<GameVertex, float>(vertex, mapPoly.getTerrain().getAltitude()));
				}
			}
		}

		List<Tuple<GameVertex, float>> ret = new List<Tuple<GameVertex, float>>();


		while(intersections.count() > 0) {
			Set<Tuple<GameVertex, float>> toBeRemoved = new Set<Tuple<GameVertex, float>>();

			float minDistance = float.MaxValue;
			Tuple<GameVertex, float> closest = null;

			foreach (Tuple<GameVertex, float> tuple in intersections) {
				if (tuple._1.distance(seg.getFirstPoint()) < minDistance) {
					minDistance = tuple._1.distance(seg.getFirstPoint());
					closest = tuple;
				}
			}

			foreach (Tuple<GameVertex, float> tuple in intersections) {
				if (tuple._1.distance(closest._1) < Constants.DISTANCE_REMOVE_DUPLICATE_VERTEX) {
					toBeRemoved.add(tuple);

					if (closest._2 < tuple._2) {
						closest = tuple;
					}
				}
			}

			foreach(Tuple<GameVertex, float> removed in toBeRemoved) {
				intersections.remove(removed);
			}

			ret.Add(new Tuple<GameVertex, float>(closest._1, closest._2));
		}

		return ret;
	}

	public void addStartWaypoints(List<Waypoint> waypoints) {
		waypoints.Reverse();
		foreach (Waypoint waypoint in waypoints) {
			path.AddFirst(waypoint);
		}
	}

	public void addEndWaypoint(Waypoint waypoint) {
		if (endWaypoints == null) {
			path.RemoveLast();
			path.AddLast(waypoint);

			endWaypoints = new List<Waypoint>();
			endWaypoints.Add(waypoint);
		}
		else {
			path.AddLast(waypoint);
			endWaypoints.Add(waypoint);
		}
	}

	public void addEndWaypoints(List<Waypoint> waypoints) {
		foreach(Waypoint waypoint in waypoints) {
			addEndWaypoint(waypoint);
		}
	}

	public void addWaypoint(Waypoint waypoint) {
		path.AddLast(waypoint);
	}

	public Waypoint getNextWaypoint() {
		return path.First.Value;
	}

	public Vertex getDestination() {
		return path.Last.Value.getVertex();
	}

	public void removeFirstWaypoint() {
		path.RemoveFirst();
	}

	public int size() {
		return path.Count;
	}

	public List<Waypoint> getEndWaypoints() {
		return endWaypoints;
	}

	public Path addPath(Path other) {
		foreach(Waypoint way in other.path) {
			addWaypoint(way);
		}

		return this;
	}

	public bool isOutdated() {
		foreach (Waypoint waypoint in path) {
			if (waypoint.isTooFar()) {
				return true;
			}
		}

		return false;
	}

	public bool isValid() {
		foreach (Waypoint waypoint in path) {
			if (!waypoint.isValid()) {
				return false;
			}
		}

		return true;
	}

    //Adds a red line in the game, representing the path in the map
    public void debugDisplay() {
		Waypoint last = null;
        bool first = true;
		foreach (Waypoint pt in path) {
            if (!first) {
				Debug.DrawLine(last.getVertex().getVector(), pt.getVertex().getVector(), Color.red, 2);
            }
            first = false;
            last = pt;
        }
    }

	public List<Waypoint> toList() {
		List<Waypoint> list = new List<Waypoint>();
		foreach (Waypoint pt in path) {
            list.Add(pt);
        }

        return list;
    }
}
