/* Poly2Tri
 * Copyright (c) 2009-2010, Poly2Tri Contributors
 * http://code.google.com/p/poly2tri/
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of Poly2Tri nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without specific
 *   prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/// Changes from the Java version
///   Polygon constructors sprused up, checks for 3+ polys
///   Naming of everything
///   getTriangulationMode() -> TriangulationMode { get; }
///   Exceptions replaced
/// Future possibilities
///   We have a lot of Add/Clear methods -- we may prefer to just expose the container
///   Some self-explanitory methods may deserve commenting anyways

using System;
using System.Collections.Generic;
using System.Linq;

namespace UnityEngine.ProBuilder.Poly2Tri {
	class Polygon : Triangulatable {
		protected List<TriangulationPoint> _points = new List<TriangulationPoint>();
		protected List<TriangulationPoint> _steinerPoints;
		protected List<Polygon> _holes;
		protected List<DelaunayTriangle> _triangles;
		protected PolygonPoint _last;

		/// <summary>
		/// Create a polygon from a list of at least 3 points with no duplicates.
		/// </summary>
		/// <param name="points">A list of unique points</param>
		public Polygon( IList<PolygonPoint> points ) {
			if (points.Count < 3) throw new ArgumentException("List has fewer than 3 points", "points");

			// Lets do one sanity check that first and last point hasn't got same position
			// Its something that often happen when importing polygon data from other formats
			if (points[0].Equals(points[points.Count - 1])) points.RemoveAt(points.Count - 1);

			_points.AddRange(points.Cast<TriangulationPoint>());
		}

		/// <summary>
		/// Create a polygon from a list of at least 3 points with no duplicates.
		/// </summary>
		/// <param name="points">A list of unique points.</param>
		public Polygon( IEnumerable<PolygonPoint> points ): this( (points as IList<PolygonPoint>) ?? points.ToArray() ) {}

		/// <summary>
		/// Create a polygon from a list of at least 3 points with no duplicates.
		/// </summary>
		/// <param name="points">A list of unique points.</param>
		public Polygon( params PolygonPoint[] points ) : this((IList<PolygonPoint>)points) { }

		public TriangulationMode TriangulationMode { get { return TriangulationMode.Polygon; } }

		public void AddSteinerPoint( TriangulationPoint point ) {
			if (_steinerPoints == null) _steinerPoints = new List<TriangulationPoint>();
			_steinerPoints.Add(point);
		}

		public void AddSteinerPoints( List<TriangulationPoint> points ) {
			if (_steinerPoints == null) _steinerPoints = new List<TriangulationPoint>();
			_steinerPoints.AddRange(points);
		}

		public void ClearSteinerPoints() {
			if (_steinerPoints != null) _steinerPoints.Clear();
		}

		/// <summary>
		/// Add a hole to the polygon.
		/// </summary>
		/// <param name="poly">A subtraction polygon fully contained inside this polygon.</param>
		public void AddHole( Polygon poly ) {
			if (_holes == null) _holes = new List<Polygon>();
			_holes.Add(poly);
			// XXX: tests could be made here to be sure it is fully inside
			//        addSubtraction( poly.getPoints() );
		}

		/// <summary>
		/// Inserts newPoint after point.
		/// </summary>
		/// <param name="point">The point to insert after in the polygon</param>
		/// <param name="newPoint">The point to insert into the polygon</param>
		public void InsertPointAfter( PolygonPoint point, PolygonPoint newPoint ) {
			// Validate that
			int index = _points.IndexOf(point);
			if (index == -1) throw new ArgumentException("Tried to insert a point into a Polygon after a point not belonging to the Polygon", "point");
			newPoint.Next = point.Next;
			newPoint.Previous = point;
			point.Next.Previous = newPoint;
			point.Next = newPoint;
			_points.Insert(index + 1, newPoint);
		}

		/// <summary>
		/// Inserts list (after last point in polygon?)
		/// </summary>
		/// <param name="list"></param>
		public void AddPoints( IEnumerable<PolygonPoint> list ) {
			PolygonPoint first;
			foreach (PolygonPoint p in list) {
				p.Previous = _last;
				if (_last != null) {
					p.Next = _last.Next;
					_last.Next = p;
				}
				_last = p;
				_points.Add(p);
			}
			first = (PolygonPoint)_points[0];
			_last.Next = first;
			first.Previous = _last;
		}

		/// <summary>
		/// Adds a point after the last in the polygon.
		/// </summary>
		/// <param name="p">The point to add</param>
		public void AddPoint( PolygonPoint p ) {
			p.Previous = _last;
			p.Next = _last.Next;
			_last.Next = p;
			_points.Add(p);
		}

		/// <summary>
		/// Removes a point from the polygon.
		/// </summary>
		/// <param name="p"></param>
		public void RemovePoint( PolygonPoint p ) {
			PolygonPoint next, prev;

			next = p.Next;
			prev = p.Previous;
			prev.Next = next;
			next.Previous = prev;
			_points.Remove(p);
		}

		public IList<TriangulationPoint> Points { get { return _points; } }
		public IList<DelaunayTriangle> Triangles { get { return _triangles; } }
		public IList<Polygon> Holes { get { return _holes; }}

		public void AddTriangle( DelaunayTriangle t ) {
			_triangles.Add(t);
		}

		public void AddTriangles( IEnumerable<DelaunayTriangle> list ) {
			_triangles.AddRange(list);
		}

		public void ClearTriangles() {
			if (_triangles != null) _triangles.Clear();
		}

		/// <summary>
		/// Creates constraints and populates the context with points
		/// </summary>
		/// <param name="tcx">The context</param>
		public void Prepare( TriangulationContext tcx ) {
			if (_triangles == null) {
				_triangles = new List<DelaunayTriangle>(_points.Count);
			} else {
				_triangles.Clear();
			}

			// Outer constraints
			for (int i = 0; i < _points.Count - 1; i++) tcx.NewConstraint(_points[i], _points[i + 1]);
			tcx.NewConstraint(_points[0], _points[_points.Count - 1]);
			tcx.Points.AddRange(_points);

			// Hole constraints
			if (_holes != null) {
				foreach (Polygon p in _holes) {
					for (int i = 0; i < p._points.Count - 1; i++) tcx.NewConstraint(p._points[i], p._points[i + 1]);
					tcx.NewConstraint(p._points[0], p._points[p._points.Count - 1]);
					tcx.Points.AddRange(p._points);
				}
			}

			if (_steinerPoints != null) {
				tcx.Points.AddRange(_steinerPoints);
			}
		}

	}
}
