namespace TriangleNet.Smoothing
{
    using System;
    using TriangleNet.Topology.DCEL;
    using TriangleNet.Voronoi;
    /// 
    /// Factory which re-uses objects in the smoothing loop to enhance performance.
    /// 
    /// 
    /// See .
    /// 
    class VoronoiFactory : IVoronoiFactory
    {
        ObjectPool vertices;
        ObjectPool edges;
        ObjectPool faces;
        public VoronoiFactory()
        {
            vertices = new ObjectPool();
            edges = new ObjectPool();
            faces = new ObjectPool();
        }
        public void Initialize(int vertexCount, int edgeCount, int faceCount)
        {
            vertices.Capacity = vertexCount;
            edges.Capacity = edgeCount;
            faces.Capacity = faceCount;
            for (int i = vertices.Count; i < vertexCount; i++)
            {
                vertices.Put(new Vertex(0, 0));
            }
            for (int i = edges.Count; i < edgeCount; i++)
            {
                edges.Put(new HalfEdge(null));
            }
            for (int i = faces.Count; i < faceCount; i++)
            {
                faces.Put(new Face(null));
            }
            Reset();
        }
        public void Reset()
        {
            vertices.Release();
            edges.Release();
            faces.Release();
        }
        public Vertex CreateVertex(double x, double y)
        {
            Vertex vertex;
            if (vertices.TryGet(out vertex))
            {
                vertex.x = x;
                vertex.y = y;
                vertex.leaving = null;
                return vertex;
            }
            vertex = new Vertex(x, y);
            vertices.Put(vertex);
            return vertex;
        }
        public HalfEdge CreateHalfEdge(Vertex origin, Face face)
        {
            HalfEdge edge;
            if (edges.TryGet(out edge))
            {
                edge.origin = origin;
                edge.face = face;
                edge.next = null;
                edge.twin = null;
                if (face != null && face.edge == null)
                {
                    face.edge = edge;
                }
                return edge;
            }
            edge = new HalfEdge(origin, face);
            edges.Put(edge);
            return edge;
        }
        public Face CreateFace(Geometry.Vertex vertex)
        {
            Face face;
            if (faces.TryGet(out face))
            {
                face.id = vertex.id;
                face.generator = vertex;
                face.edge = null;
                return face;
            }
            face = new Face(vertex);
            faces.Put(face);
            return face;
        }
        class ObjectPool where T : class
        {
            int index, count;
            T[] pool;
            public int Count
            {
                get { return count; }
            }
            public int Capacity
            {
                get { return this.pool.Length; }
                set { Resize(value); }
            }
            public ObjectPool(int capacity = 3)
            {
                this.index = 0;
                this.count = 0;
                this.pool = new T[capacity];
            }
            public ObjectPool(T[] pool)
            {
                this.index = 0;
                this.count = 0;
                this.pool = pool;
            }
            public bool TryGet(out T obj)
            {
                if (this.index < this.count)
                {
                    obj = this.pool[this.index++];
                    return true;
                }
                obj = null;
                return false;
            }
            public void Put(T obj)
            {
                var capacity = this.pool.Length;
                if (capacity <= this.count)
                {
                    Resize(2 * capacity);
                }
                this.pool[this.count++] = obj;
                this.index++;
            }
            public void Release()
            {
                this.index = 0;
            }
            private void Resize(int size)
            {
                if (size > this.count)
                {
                    Array.Resize(ref this.pool, size);
                }
            }
        }
    }
}