/*
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
** Copyright (C) 2011 Silicon Graphics, Inc.
** All Rights Reserved.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
** of the Software, and to permit persons to whom the Software is furnished to do so,
** subject to the following conditions:
**
** The above copyright notice including the dates of first publication and either this
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC.
** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
** OR OTHER DEALINGS IN THE SOFTWARE.
**
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
** be used in advertising or otherwise to promote the sale, use or other dealings in
** this Software without prior written authorization from Silicon Graphics, Inc.
*/
/*
** Original Author: Eric Veach, July 1994.
** libtess2: Mikko Mononen, http://code.google.com/p/libtess2/.
** LibTessDotNet: Remi Gillig, https://github.com/speps/LibTessDotNet
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine.Scripting.APIUpdating;

namespace UnityEngine.Rendering.Universal
{
    using Real = System.Single;
    namespace LibTessDotNet
    {
        internal struct Vec3
        {
            public readonly static Vec3 Zero = new Vec3();

            public Real X, Y, Z;

            public Real this[int index]
            {
                get
                {
                    if (index == 0) return X;
                    if (index == 1) return Y;
                    if (index == 2) return Z;
                    throw new IndexOutOfRangeException();
                }
                set
                {
                    if (index == 0) X = value;
                    else if (index == 1) Y = value;
                    else if (index == 2) Z = value;
                    else throw new IndexOutOfRangeException();
                }
            }

            public static void Sub(ref Vec3 lhs, ref Vec3 rhs, out Vec3 result)
            {
                result.X = lhs.X - rhs.X;
                result.Y = lhs.Y - rhs.Y;
                result.Z = lhs.Z - rhs.Z;
            }

            public static void Neg(ref Vec3 v)
            {
                v.X = -v.X;
                v.Y = -v.Y;
                v.Z = -v.Z;
            }

            public static void Dot(ref Vec3 u, ref Vec3 v, out Real dot)
            {
                dot = u.X * v.X + u.Y * v.Y + u.Z * v.Z;
            }

            public static void Normalize(ref Vec3 v)
            {
                var len = v.X * v.X + v.Y * v.Y + v.Z * v.Z;
                Debug.Assert(len >= 0.0f);
                len = 1.0f / (Real)Math.Sqrt(len);
                v.X *= len;
                v.Y *= len;
                v.Z *= len;
            }

            public static int LongAxis(ref Vec3 v)
            {
                int i = 0;
                if (Math.Abs(v.Y) > Math.Abs(v.X)) i = 1;
                if (Math.Abs(v.Z) > Math.Abs(i == 0 ? v.X : v.Y)) i = 2;
                return i;
            }

            public override string ToString()
            {
                return string.Format("{0}, {1}, {2}", X, Y, Z);
            }
        }

        internal static class MeshUtils
        {
            public const int Undef = ~0;

            public abstract class Pooled<T> where T : Pooled<T>, new()
            {
                private static Stack<T> _stack;
                public abstract void Reset();
                public virtual void OnFree() { }

                public static T Create()
                {
                    if (_stack != null && _stack.Count > 0)
                    {
                        return _stack.Pop();
                    }
                    return new T();
                }

                public void Free()
                {
                    OnFree();
                    Reset();
                    if (_stack == null)
                    {
                        _stack = new Stack<T>();
                    }
                    _stack.Push((T)this);
                }
            }

            public class Vertex : Pooled<Vertex>
            {
                internal Vertex _prev, _next;
                internal Edge _anEdge;

                internal Vec3 _coords;
                internal Real _s, _t;
                internal PQHandle _pqHandle;
                internal int _n;
                internal object _data;

                public override void Reset()
                {
                    _prev = _next = null;
                    _anEdge = null;
                    _coords = Vec3.Zero;
                    _s = 0;
                    _t = 0;
                    _pqHandle = new PQHandle();
                    _n = 0;
                    _data = null;
                }
            }

            public class Face : Pooled<Face>
            {
                internal Face _prev, _next;
                internal Edge _anEdge;

                internal Face _trail;
                internal int _n;
                internal bool _marked, _inside;

                internal int VertsCount
                {
                    get
                    {
                        int n = 0;
                        var eCur = _anEdge;
                        do
                        {
                            n++;
                            eCur = eCur._Lnext;
                        }
                        while (eCur != _anEdge);
                        return n;
                    }
                }

                public override void Reset()
                {
                    _prev = _next = null;
                    _anEdge = null;
                    _trail = null;
                    _n = 0;
                    _marked = false;
                    _inside = false;
                }
            }

            public struct EdgePair
            {
                internal Edge _e, _eSym;

                public static EdgePair Create()
                {
                    var pair = new MeshUtils.EdgePair();
                    pair._e = MeshUtils.Edge.Create();
                    pair._e._pair = pair;
                    pair._eSym = MeshUtils.Edge.Create();
                    pair._eSym._pair = pair;
                    return pair;
                }

                public void Reset()
                {
                    _e = _eSym = null;
                }
            }

            public class Edge : Pooled<Edge>
            {
                internal EdgePair _pair;
                internal Edge _next, _Sym, _Onext, _Lnext;
                internal Vertex _Org;
                internal Face _Lface;
                internal Tess.ActiveRegion _activeRegion;
                internal int _winding;

                internal Face _Rface { get { return _Sym._Lface; } set { _Sym._Lface = value; } }
                internal Vertex _Dst { get { return _Sym._Org; } set { _Sym._Org = value; } }

                internal Edge _Oprev { get { return _Sym._Lnext; } set { _Sym._Lnext = value; } }
                internal Edge _Lprev { get { return _Onext._Sym; } set { _Onext._Sym = value; } }
                internal Edge _Dprev { get { return _Lnext._Sym; } set { _Lnext._Sym = value; } }
                internal Edge _Rprev { get { return _Sym._Onext; } set { _Sym._Onext = value; } }
                internal Edge _Dnext { get { return _Rprev._Sym; } set { _Rprev._Sym = value; } }
                internal Edge _Rnext { get { return _Oprev._Sym; } set { _Oprev._Sym = value; } }

                internal static void EnsureFirst(ref Edge e)
                {
                    if (e == e._pair._eSym)
                    {
                        e = e._Sym;
                    }
                }

                public override void Reset()
                {
                    _pair.Reset();
                    _next = _Sym = _Onext = _Lnext = null;
                    _Org = null;
                    _Lface = null;
                    _activeRegion = null;
                    _winding = 0;
                }
            }

            /// <summary>
            /// MakeEdge creates a new pair of half-edges which form their own loop.
            /// No vertex or face structures are allocated, but these must be assigned
            /// before the current edge operation is completed.
            /// </summary>
            public static Edge MakeEdge(Edge eNext)
            {
                Debug.Assert(eNext != null);

                var pair = EdgePair.Create();
                var e = pair._e;
                var eSym = pair._eSym;

                // Make sure eNext points to the first edge of the edge pair
                Edge.EnsureFirst(ref eNext);

                // Insert in circular doubly-linked list before eNext.
                // Note that the prev pointer is stored in Sym->next.
                var ePrev = eNext._Sym._next;
                eSym._next = ePrev;
                ePrev._Sym._next = e;
                e._next = eNext;
                eNext._Sym._next = eSym;

                e._Sym = eSym;
                e._Onext = e;
                e._Lnext = eSym;
                e._Org = null;
                e._Lface = null;
                e._winding = 0;
                e._activeRegion = null;

                eSym._Sym = e;
                eSym._Onext = eSym;
                eSym._Lnext = e;
                eSym._Org = null;
                eSym._Lface = null;
                eSym._winding = 0;
                eSym._activeRegion = null;

                return e;
            }

            /// <summary>
            /// Splice( a, b ) is best described by the Guibas/Stolfi paper or the
            /// CS348a notes (see Mesh.cs). Basically it modifies the mesh so that
            /// a->Onext and b->Onext are exchanged. This can have various effects
            /// depending on whether a and b belong to different face or vertex rings.
            /// For more explanation see Mesh.Splice().
            /// </summary>
            public static void Splice(Edge a, Edge b)
            {
                var aOnext = a._Onext;
                var bOnext = b._Onext;

                aOnext._Sym._Lnext = b;
                bOnext._Sym._Lnext = a;
                a._Onext = bOnext;
                b._Onext = aOnext;
            }

            /// <summary>
            /// MakeVertex( eOrig, vNext ) attaches a new vertex and makes it the
            /// origin of all edges in the vertex loop to which eOrig belongs. "vNext" gives
            /// a place to insert the new vertex in the global vertex list. We insert
            /// the new vertex *before* vNext so that algorithms which walk the vertex
            /// list will not see the newly created vertices.
            /// </summary>
            public static void MakeVertex(Edge eOrig, Vertex vNext)
            {
                var vNew = MeshUtils.Vertex.Create();

                // insert in circular doubly-linked list before vNext
                var vPrev = vNext._prev;
                vNew._prev = vPrev;
                vPrev._next = vNew;
                vNew._next = vNext;
                vNext._prev = vNew;

                vNew._anEdge = eOrig;
                // leave coords, s, t undefined

                // fix other edges on this vertex loop
                var e = eOrig;
                do
                {
                    e._Org = vNew;
                    e = e._Onext;
                }
                while (e != eOrig);
            }

            /// <summary>
            /// MakeFace( eOrig, fNext ) attaches a new face and makes it the left
            /// face of all edges in the face loop to which eOrig belongs. "fNext" gives
            /// a place to insert the new face in the global face list. We insert
            /// the new face *before* fNext so that algorithms which walk the face
            /// list will not see the newly created faces.
            /// </summary>
            public static void MakeFace(Edge eOrig, Face fNext)
            {
                var fNew = MeshUtils.Face.Create();

                // insert in circular doubly-linked list before fNext
                var fPrev = fNext._prev;
                fNew._prev = fPrev;
                fPrev._next = fNew;
                fNew._next = fNext;
                fNext._prev = fNew;

                fNew._anEdge = eOrig;
                fNew._trail = null;
                fNew._marked = false;

                // The new face is marked "inside" if the old one was. This is a
                // convenience for the common case where a face has been split in two.
                fNew._inside = fNext._inside;

                // fix other edges on this face loop
                var e = eOrig;
                do
                {
                    e._Lface = fNew;
                    e = e._Lnext;
                }
                while (e != eOrig);
            }

            /// <summary>
            /// KillEdge( eDel ) destroys an edge (the half-edges eDel and eDel->Sym),
            /// and removes from the global edge list.
            /// </summary>
            public static void KillEdge(Edge eDel)
            {
                // Half-edges are allocated in pairs, see EdgePair above
                Edge.EnsureFirst(ref eDel);

                // delete from circular doubly-linked list
                var eNext = eDel._next;
                var ePrev = eDel._Sym._next;
                eNext._Sym._next = ePrev;
                ePrev._Sym._next = eNext;

                eDel.Free();
            }

            /// <summary>
            /// KillVertex( vDel ) destroys a vertex and removes it from the global
            /// vertex list. It updates the vertex loop to point to a given new vertex.
            /// </summary>
            public static void KillVertex(Vertex vDel, Vertex newOrg)
            {
                var eStart = vDel._anEdge;

                // change the origin of all affected edges
                var e = eStart;
                do
                {
                    e._Org = newOrg;
                    e = e._Onext;
                }
                while (e != eStart);

                // delete from circular doubly-linked list
                var vPrev = vDel._prev;
                var vNext = vDel._next;
                vNext._prev = vPrev;
                vPrev._next = vNext;

                vDel.Free();
            }

            /// <summary>
            /// KillFace( fDel ) destroys a face and removes it from the global face
            /// list. It updates the face loop to point to a given new face.
            /// </summary>
            public static void KillFace(Face fDel, Face newLFace)
            {
                var eStart = fDel._anEdge;

                // change the left face of all affected edges
                var e = eStart;
                do
                {
                    e._Lface = newLFace;
                    e = e._Lnext;
                }
                while (e != eStart);

                // delete from circular doubly-linked list
                var fPrev = fDel._prev;
                var fNext = fDel._next;
                fNext._prev = fPrev;
                fPrev._next = fNext;

                fDel.Free();
            }

            /// <summary>
            /// Return signed area of face.
            /// </summary>
            public static Real FaceArea(Face f)
            {
                Real area = 0;
                var e = f._anEdge;
                do
                {
                    area += (e._Org._s - e._Dst._s) * (e._Org._t + e._Dst._t);
                    e = e._Lnext;
                }
                while (e != f._anEdge);
                return area;
            }
        }
    }
}