using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Xml.Serialization;

namespace VisiMath
{
    #region Indexers (damn c# for not allowing indexed properties)


    public class VertexIndexerVec3
    {
        IVertexArray Parent;
        VertexElementUsage ElementType;

        protected VertexIndexerVec3()
        {
        }

        public VertexIndexerVec3(IVertexArray parent, VertexElementUsage elem)
        {
            Parent = parent;
            ElementType = elem;
        }

        public Vector3 this[int index]
        {
            get
            {
                switch (ElementType)
                {
                    case VertexElementUsage.Position:
                        return Parent[index].Position;
                }

                return new Vector3();
            }

            set
            {
                switch (ElementType)
                {
                    case VertexElementUsage.Position:
                        var tmp = Parent[index];
                        tmp.Position = value;
                        Parent[index] = tmp;
                        break;
                }
            }
        }
    }

    public class VertexIndexerVec2
    {
        IVertexArray Parent;
        VertexElementUsage ElementType;

        protected VertexIndexerVec2()
        {
        }

        public VertexIndexerVec2(IVertexArray parent, VertexElementUsage elem)
        {
            Parent = parent;
            ElementType = elem;
        }

        public Vector2 this[int index]
        {
            get
            {
                switch (ElementType)
                {
                    case VertexElementUsage.TexCoord:
                        return Parent[index].TexCoord2;
                }

                return new Vector2();
            }

            set
            {
                switch (ElementType)
                {
                    case VertexElementUsage.TexCoord:
                        var tmp = Parent[index];
                        tmp.TexCoord2 = value;
                        Parent[index] = tmp;
                        break;
                }
            }
        }
    }

    public class VertexIndexerColorByte
    {
        IVertexArray Parent;
        VertexElementUsage ElementType;

        protected VertexIndexerColorByte()
        {
        }

        public VertexIndexerColorByte(IVertexArray parent, VertexElementUsage elem)
        {
            Parent = parent;
            ElementType = elem;
        }

        public ColorByte this[int index]
        {
            get
            {
                switch (ElementType)
                {
                    case VertexElementUsage.Color:
                        return Parent[index].Color;
                }

                return ColorByte.White;
            }

            set
            {
                switch (ElementType)
                {
                    case VertexElementUsage.Color:
                        var tmp = Parent[index];
                        tmp.Color = value;
                        Parent[index] = tmp;
                        break;
                }
            }
        }
    }


    #endregion

    
    /// <summary>
    /// Interface for accessing vertex arrays and list with brackets
    /// </summary>
    public interface IVertexArray : IEnumerable<IVertex>, IXmlSerializable
    {
        IVertex this[int index] { get; set; }
        int Count { get; }
        VertexIndexerVec3 Normals { get; }
        VertexIndexerVec3 Positions { get; }
        VertexIndexerColorByte Colors { get; }
        VertexIndexerVec2 TexCoords { get; }

        VertexDescriptor Descriptor { get; }

        /// <summary>
        /// Total size of the array
        /// </summary>
        int GetSize();
        /// <summary>
        /// Size of one vertex
        /// </summary>
        int GetVertexSize();
        
        /// <summary>
        /// Returns true if it's possible to pin vertices and use it in unmanaged code with GetPointer
        /// Dynamic VertexLists returns false (it's still possible to use GetPointer, but new vertexarray will be created!)
        /// </summary>
        bool IsPinnable { get; }

        /// <summary>
        /// Returns pointer to pinned array which can be used in unmanaged code. Must be freed again with freepointer!
        /// If VertexArray contains Vertex which is not structure, this function will throw exception!
        /// Dynamic VertexArray (VertexList and other) have to convert itself to VertexArray (DO NOT use for rendering every frame)
        /// </summary>
        IntPtr GetPointer();
        /// <summary>
        /// Frees previously pinned pointer to array
        /// </summary>
        void FreePointer();

        /// <summary>
        /// Returns copy of the vertex array
        /// </summary>
        /// <returns></returns>
        IVertexArray GetCopy();
    }


    /// <summary>
    /// Interface for accessing vertex arrays and list with brackets
    /// </summary>
    /// <typeparam name="T">Vertex structure, must implement IVertex information, not just vertex attributes</typeparam>
    public interface IVertexArray<T> : IVertexArray
        where T : IVertex
    {
    }

    public class VertexArray<T> : IVertexArray<T>,  IDisposable
        where T : IVertex
    {
        public T[] Vertices;

        VertexIndexerVec3 positions;
        VertexIndexerVec3 normals;
        VertexIndexerVec2 texcoords;
        VertexIndexerColorByte colors;

        GCHandle gch;
        bool pinned = false;

        /// <summary>
        /// Default constructor
        /// </summary>
        private VertexArray()
        {
        }

        /// <summary>
        /// Constructs vertexlist with specified capacity
        /// </summary>
        /// <param name="capacity">Required capacity</param>
        public VertexArray(int capacity)
        {
            Vertices = new T[capacity];
            ConstructIndexers();
        }

        /// <summary>
        /// Constructs vertex list from the vertex collection
        /// </summary>
        /// <param name="verarray">Source Vertex array</param>
        public VertexArray(IEnumerable<T> verarray)
        {
            Vertices = verarray.ToArray();
            ConstructIndexers();
        }

        protected void ConstructIndexers()
        {
            positions = new VertexIndexerVec3(this, VertexElementUsage.Position);
            normals = new VertexIndexerVec3(this, VertexElementUsage.Normal);
            texcoords = new VertexIndexerVec2(this, VertexElementUsage.TexCoord);
            colors = new VertexIndexerColorByte(this, VertexElementUsage.Color);
        }


        /// <summary>
        /// Quick accessor to position vectors
        /// </summary>
        public VertexIndexerVec3 Positions
        {
            get
            {
                return positions;
            }
        }

        /// <summary>
        /// Quick acessor to normal vectors
        /// </summary>
        public VertexIndexerVec3 Normals
        {
            get
            {
                return normals;
            }
        }

        /// <summary>
        /// Quick acessors to texture coordinates
        /// </summary>
        public VertexIndexerVec2 TexCoords
        {
            get
            {
                return texcoords;
            }
        }

        /// <summary>
        /// Quick accessor to color informations
        /// </summary>
        public VertexIndexerColorByte Colors
        {
            get
            {
                return colors;
            }
        }

        /// <summary>
        /// Returns true if it's possible to pin vertices and use it in unmanaged code with GetPointer
        /// Dynamic VertexLists returns false (it's still possible to use GetPointer, but new vertexarray will be created!)
        /// </summary>
        public bool IsPinnable { get { return true; } }



        #region IEnumerable<T> Members


        public class VAEnumerator : IEnumerator<IVertex>
        {
            private int index = 0;
            VertexArray<T> parent;

            private VAEnumerator() { }
            public VAEnumerator(VertexArray<T> par)
            {
                parent = par;
            }


            #region IEnumerator<T> Members

            public IVertex Current
            {
                get { return (IVertex)parent.Vertices[index]; }
            }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
                
            }

            #endregion

            #region IEnumerator Members

            object System.Collections.IEnumerator.Current
            {
                get { return (object)parent.Vertices[index]; }
            }

            public bool MoveNext()
            {
                index++;
                if (index >= parent.Vertices.Length)
                    return false;
                return true;
            }

            public void Reset()
            {
                index = 0;
            }

            #endregion
        }

        public IEnumerator<IVertex> GetEnumerator()
        {
            return new VAEnumerator(this);
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Vertices.GetEnumerator();
        }

        #endregion

        #region IVertexArray<T> Members

        public IVertex this[int index]
        {
            get { return Vertices[index]; }
            set { Vertices[index] = (T)value; } //TODO!!! - use vertex copy for non same type!
        }

        public int Count
        {
            get { return Vertices.Length; }
        }

        public VertexDescriptor Descriptor
        {
            get
            {
                return VertexFactory.GetDescriptor(typeof(T));
            }
        }


        public IntPtr GetPointer()
        {
            if (!typeof(T).IsValueType)
            {
                // Vertex is NOT a structure, we can't pin the data, this shouldn't happen!
                throw new ArgumentException("Vertex in VertexArray is not a structure, can't get native pointer!");
            }
            if (pinned)
                return gch.AddrOfPinnedObject();
            gch = GCHandle.Alloc(this.Vertices, GCHandleType.Pinned);
            pinned = true;
            return gch.AddrOfPinnedObject();
        }

        public void FreePointer()
        {
            if (pinned)
            {
                gch.Free();
                pinned = false;
            }
        }

        public int GetSize()
        {
            return Marshal.SizeOf(Vertices[0]) * Vertices.Length;
        }

        public int GetVertexSize()
        {
            return Marshal.SizeOf(Vertices[0]);
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            FreePointer();
        }

        #endregion

        #region IVertexArray Members


        public IVertexArray GetCopy()
        {
            VertexArray<T> copy = new VertexArray<T>(this.Count);

            for (int i = 0; i < Count; i++)
            {
                copy[i] = this[i];
            }

            return copy;
        }

        #endregion

        #region IXmlSerializable Members

        /// <summary>
        /// Reqired by IXMLserializable
        /// </summary>
        /// <returns>Null</returns>
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Deserializes object from XML
        /// </summary>
        /// <param name="reader">XML reader</param>
        public void ReadXml(System.Xml.XmlReader reader)
        {
            if (reader["VertexFormat"] != typeof(T).Name)
                throw new ArgumentException("VertexArray: Cannot read different type of vertex from xml!");

            List<T> vertices = new List<T>(100);    //guessing size

            while (reader.Read())
            {
                if (reader.IsStartElement("Vertex"))
                {
                    var vertex = VertexFactory.CreateNewVertex(typeof(T));
                    vertex.ReadXml(reader);
                    vertices.Add((T)vertex);
                }
                else break;
            }

            Vertices = vertices.ToArray();
        }

        /// <summary>
        /// Serializes object to XML
        /// </summary>
        /// <param name="writer">XML writer to save values</param>
        public void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteAttributeString("VertexFormat", typeof(T).Name);    //todo - use vertex factory to save description?

            for (int i = 0; i < Vertices.Length; i++)
            {
                Vertices[i].WriteXml(writer, "Vertex");
            }
        }

        #endregion
    }




}
