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


namespace VisiMath
{
    #region Vertex Elements



    /// <summary>
    /// Format of the Vertex element
    /// </summary>
    public enum VertexElementFormat { 
        /// <summary>
        /// Unused element
        /// </summary>
        Unused, 
        /// <summary>
        /// Single float value
        /// </summary>
        Single,
        /// <summary>
        /// Vector2 (two float values)
        /// </summary>
        Vector2,
        /// <summary>
        /// Vector3 (three float values)
        /// </summary>
        Vector3,
        /// <summary>
        /// VEctor4 (four float values)
        /// </summary>
        Vector4,
        /// <summary>
        /// Color (4 bytes) in ARGB format
        /// </summary>
        Color
    };

    /// <summary>
    /// Usage of the vertex element
    /// </summary>
    public enum VertexElementUsage {
        /// <summary>
        /// Unused element
        /// </summary>
        Unused, 
        /// <summary>
        /// Element is used to specify position
        /// </summary>
        Position,
        /// <summary>
        /// Element is used to specify normal
        /// </summary>
        Normal,
        /// <summary>
        /// Element is used to specify color
        /// </summary>
        Color,
        /// <summary>
        /// Element is used to specify texture coordinate
        /// </summary>
        TexCoord 
    };

    /// <summary>
    /// Vertex element descriptor
    /// </summary>
    public struct VertexElement
    {
        /// <summary>
        /// Offset in the data block
        /// </summary>
        public int Offset;
        /// <summary>
        /// Numerical format of the element
        /// </summary>
        public VertexElementFormat Format;
        /// <summary>
        /// Intended use of the element
        /// </summary>
        public VertexElementUsage Usage;
        /// <summary>
        /// Use index (allows using more same element types, for example more texturecoordinates)
        /// </summary>
        public int UseIndex;

        public VertexElement(int of, VertexElementFormat form, VertexElementUsage use, int useidx)
        {
            Offset = of;
            Format = form;
            Usage = use;
            UseIndex = useidx;
        }
    }
    
    
    /// <summary>
    /// Structure used for the description of the vertex, containing list of the vertex elements
    /// </summary>
    public struct VertexDescriptor
    {
        /// <summary>
        /// All elements of the vertex
        /// </summary>
        public VertexElement[] Elements;

        /// <summary>
        /// Size of the vertex structure in bytes
        /// </summary>
        public int SizeInBytes;
        /// <summary>
        /// Size of the padding
        /// </summary>
        public int PaddingSize;

        public VertexDescriptor(VertexElement[] elements, int bytesize, int padding)
        {
            Elements = elements;
            SizeInBytes = bytesize;
            PaddingSize = padding;
        }

        /// <summary>
        /// Checks if vertex contains element with specified usage
        /// </summary>
        /// <param name="vu">Type of the usage</param>
        /// <returns>True if vertex has specified element</returns>
        public bool HasUsage(VertexElementUsage vu)
        {
            foreach (var element in Elements)
            {
                if (element.Usage == vu)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Checks if vertex has normal
        /// </summary>
        /// <returns></returns>
        public bool HasNormal()
        {
            return HasUsage(VertexElementUsage.Normal);
        }

        /// <summary>
        /// Checks if vertex has color information
        /// </summary>
        /// <returns></returns>
        public bool HasColor()
        {
            return HasUsage(VertexElementUsage.Color);
        }

        /// <summary>
        /// Checks if vertex has texturecoordinations
        /// </summary>
        /// <returns></returns>
        public bool HasTexCoord()
        {
            return HasUsage(VertexElementUsage.TexCoord);
        }

        /// <summary>
        /// Returns offset for first element of specified element type
        /// </summary>
        /// <param name="type">required element type</param>
        /// <returns>Offset in the vertex or -1 if not present</returns>
        public int GetOffset(VertexElementUsage type)
        {
            foreach (var element in Elements)
            {
                if (element.Usage == type)
                {
                    return element.Offset;
                }
            }
            return -1;
        }


        /// <summary>
        /// Returns offset for the specified element type and information about the format
        /// </summary>
        /// <param name="type">requested element type</param>
        /// <param name="format">will be filled with used format or VertexElementFormat.Unused if not found</param>
        /// <param name="useindex">requested use index. if set to -1, will be replaced with use index of first encountered element</param>
        /// <returns>Offset in the vertex or -1 if not present</returns>
        public int GetOffset(VertexElementUsage type, out VertexElementFormat format, ref int useindex)
        {
            foreach (var element in Elements)
            {
                if (element.Usage == type)
                    if ((useindex == -1) || (useindex == element.UseIndex))
                    {
                        format = element.Format;
                        useindex = element.UseIndex;
                        return element.Offset;
                    }
            }
            format = VertexElementFormat.Unused;
            return -1;
        }

        /// <summary>
        /// Returns offset for first element of specified element type and information about the format
        /// </summary>
        /// <param name="type">required element type</param>
        /// <param name="format">will be filled with used format or VertexElementFormat.Unused if not found</param>
        /// <returns>Offset in the vertex or -1 if not present</returns>
        public int GetOffset(VertexElementUsage type, out VertexElementFormat format)
        {
            foreach (var element in Elements)
            {
                if (element.Usage == type)
                {
                    format = element.Format;
                    return element.Offset;
                }
            }
            format = VertexElementFormat.Unused;
            return -1;
        }



    }

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    public sealed class VertexElementAttribute : Attribute
    {
        internal int Offset {get; set;}
        public VertexElementFormat Format {get; set;}
        public VertexElementUsage Usage {get; set;}
        public int UseIndex {get; set;}

        public VertexElementAttribute(VertexElementUsage usage)
        {
            Usage = usage;
            Format = VertexElementFormat.Unused;
        }

        public VertexElementAttribute(VertexElementUsage usage, VertexElementFormat format)
        {
            Usage = usage;
            Format = format;
        }
    }


    #endregion

    /// <summary>
    /// Static class, factory automatically creating vertex descriptors from vertex structures using attributes and reflection
    /// Also returns informations about the vertex
    /// </summary>
    public static class VertexFactory
    {
        static Dictionary<Type, VertexDescriptor> cachedData = new Dictionary<Type, VertexDescriptor>();


        /// <summary>
        /// Creates new vertex of specified type
        /// </summary>
        /// <param name="vertextype"></param>
        /// <returns></returns>
        public static IVertex CreateNewVertex(Type vertextype)
        {
            object obj = Activator.CreateInstance(vertextype);
            return obj as IVertex;
        }

        /// <summary>
        /// Returns vertex type from type name
        /// </summary>
        /// <param name="vertextypename">type name of vertex</param>
        /// <returns>Type or null if not IVertex</returns>
        public static Type GetTypeFromName(string vertextypename)
        {
            if (vertextypename == null)
                return null;

            Type t = Type.GetType(vertextypename);

            if (t == null)
            {
                vertextypename = "VisiMath." + vertextypename;
                t = Type.GetType(vertextypename);
            }

            try
            {
                GetDescriptor(t);
            }
            catch
            {
                return null; //not a vertex type
            }
            return t;
        }

        public static IVertexArray ReadFromXml(XmlReader reader)
        {
            string vertypename = reader["VertexFormat"];

            Type vertype = GetTypeFromName(vertypename);

            if (vertype == null)
            {
                throw new ArgumentException("VertexFactory:Read vertexarray failed (no vertextype specifier)");
            }

            IVertexArray rval = CreateArray(vertype, 1);
            if (rval == null)
                return null;    //failure?

            rval.ReadXml(reader);
            return rval;
        }

        /// <summary>
        /// Copies relevant portions of source vertex to target vertex
        /// </summary>
        /// <param name="source">source vertex</param>
        /// <param name="target">target vertex</param>
        public static void CopyVertex(ref IVertex source, ref IVertex target)
        {
            //NOTE! improve for generic!
            target.Position = source.Position;
            target.Normal = source.Normal;
            target.Color = source.Color;
            target.TexCoord2 = source.TexCoord2;
        }

        /// <summary>
        /// Returns copy of the vertex in the specified format
        /// </summary>
        /// <typeparam name="T">Requested vertex format</typeparam>
        /// <param name="source">Source vertex</param>
        /// <returns>New vertex </returns>
        public static T CopyVertex<T>(IVertex source)
            where T : IVertex
        {
            T rval = default(T);
            rval.Position = source.Position;
            rval.Normal = source.Normal;
            rval.Color = source.Color;
            rval.TexCoord2 = source.TexCoord2;
            return rval;
        }

        public static IVertex CopyVertex(IVertex source, Type VertexType)
        {
            var rval = CreateNewVertex(VertexType);
            rval.Position = source.Position;
            rval.Normal = source.Normal;
            rval.Color = source.Color;
            rval.TexCoord2 = source.TexCoord2;
            return rval;
        }

        /// <summary>
        /// Copies source vertex array to vertex array of different vertex format
        /// </summary>
        /// <typeparam name="T">Target vertex</typeparam>
        /// <param name="source">Source vertex array</param>
        /// <param name="target">target vertex array</param>
        public static void CopyVertexArray<T>(IVertexArray source, out VertexArray<T> target)
            where T : IVertex
        {
            target = new VertexArray<T>(source.Count);
            for (int i = 0; i < source.Count; i++)
            {
                target[i] = CopyVertex<T>((IVertex)source[i]);
            }
        }

        /// <summary>
        /// Copies source vertex array to vertex array of different vertex format
        /// </summary>
        /// <param name="source">Source vertex array</param>
        /// <param name="target">target vertex array</param>
        /// <param name="VertexType">Required vertex type</param>
        public static void CopyVertexArray(Type VertexType, IVertexArray source, out IVertexArray target)
        {
            target = CreateArray(VertexType, source.Count);
            for (int i = 0; i < source.Count; i++)
            {
                target[i] = CopyVertex(source[i], VertexType);
            }
        }



        /// <summary>
        /// Creates normal array of vertices of selected type
        /// </summary>
        /// <param name="vertextype">requested type</param>
        /// <param name="count">size of the array</param>
        /// <returns>new IVertex[]</returns>
        public static IVertex[] CreateNormalArray(Type vertextype, int count)
        {
            if (!(vertextype is IVertex))
            {
                throw new ArgumentException("Can't create vertex array of this type - it's not vertex!");
            }

            return (IVertex[])Array.CreateInstance(vertextype, count);
        }

        /// <summary>
        /// Creates VertexArray of specified type
        /// </summary>
        /// <param name="vertextype">requested type</param>
        /// <param name="count">size of the array</param>
        /// <returns>new VertexArray</returns>
        public static IVertexArray CreateArray(Type vertextype, int count)
        {
            if ((vertextype.GetInterface("IVertex") == null))
            {
                throw new ArgumentException("Can't create vertex array of this type - it's not vertex!");
            }

            Type genericarray = typeof(VertexArray<>);

            Type constrclass = genericarray.MakeGenericType(vertextype);

            object obj;

            try
            {
                obj = Activator.CreateInstance(constrclass, count);
            }
            catch (Exception ex)
            {
                throw new ArgumentException("Can't create vertex array from specified type", ex);
            }

            return (IVertexArray)obj;
        }

        /// <summary>
        /// Creates VertexArray of specified type
        /// </summary>
        /// <param name="vertextype">requested type</param>
        /// <param name="count">starting size of the list</param>
        /// <returns>new VertexList</returns>
        public static VertexList<IVertex> CreateList(Type vertextype, int count = 1)
        {
            if (!(vertextype is IVertex))
            {
                throw new ArgumentException("Can't create vertex list of this type - it's not vertex!");
            }

            Type genericarray = typeof(VertexList<>);

            Type constrclass = genericarray.MakeGenericType(vertextype);

            object obj;

            try
            {
                obj = Activator.CreateInstance(constrclass, count);
            }
            catch (Exception ex)
            {
                throw new ArgumentException("Can't create vertex list from specified type", ex);
            }

            return (VertexList<IVertex>)obj;
        }

        /// <summary>
        /// Returns byte size of vertex type
        /// </summary>
        /// <param name="VertexType">Vertex type</param>
        /// <returns>Size in bytes</returns>
        public static int GetByteSize(Type VertexType)
        {
            if (VertexType.GetInterface("IVertex") == null)
                throw new ArgumentException("GetByteSize: Vertex is not a type of Ivertex");
            return Marshal.SizeOf(VertexType);
        }


        /// <summary>
        /// Returns vertex descriptor for the specified vertextype.
        /// Either returns cached result from previous calls or creates new descriptor using reflection.
        /// </summary>
        /// <param name="vertexType">Type of the vertex - must be struct using custom VertexElementAtttributes</param>
        /// <returns>Vertexdescriptor for the vertex</returns>
        /// <exception cref="System.ArgumentException">Thrown if specified type: 
        /// -is not a value type 
        /// - doesn't contain at least one recognized vertex element type</exception>
        public static VertexDescriptor GetDescriptor(Type vertexType)
        {
            //Check for cached result
            if (cachedData.ContainsKey(vertexType))
                return cachedData[vertexType];

            if (!vertexType.IsValueType)
            {
                throw new ArgumentException("VertexType is not a struct!");
            }


            //Extract information about all fields in the structure
            FieldInfo[] fields = vertexType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            List<VertexElementAttribute> attributes = new List<VertexElementAttribute>();

            foreach (var field in fields)
            {
                VertexElementAttribute[] attrs = (VertexElementAttribute[])field.GetCustomAttributes(typeof(VertexElementAttribute), false);


                //Check for properties
                if (field.Name.Contains("<") && field.Name.Contains(">"))
                {
                    int idx1 = field.Name.IndexOf('<');
                    int idx2 = field.Name.IndexOf('>');

                    string propertyName = field.Name.Substring(idx1 + 1, idx2 - idx1 - 1);
                    PropertyInfo property = vertexType.GetProperty(propertyName, field.FieldType);
                    if (property != null)
                        attrs = (VertexElementAttribute[])property.GetCustomAttributes(typeof(VertexElementAttribute), false);
                }

                if (attrs.Length == 1)
                {
                    //If element format wasn't explicitly specified, check for common types
                    if (attrs[0].Format == VertexElementFormat.Unused)
                    {
                        if (field.FieldType == typeof(Vector2))
                            attrs[0].Format = VertexElementFormat.Vector2;
                        else if (field.FieldType == typeof(Vector3))
                            attrs[0].Format = VertexElementFormat.Vector3;
                        else if (field.FieldType == typeof(Vector4))
                            attrs[0].Format = VertexElementFormat.Vector4;
                        else if (field.FieldType == typeof(ColorByte))
                            attrs[0].Format = VertexElementFormat.Color;
                    }

                    //Calculate attribute
                    attrs[0].Offset = Marshal.OffsetOf(vertexType, field.Name).ToInt32();
                    attributes.Add(attrs[0]);
                }
            }

            //check if structure has at least one vertex attribute

            if (attributes.Count < 1)
                throw new ArgumentException("Structure doesn't contain any vertex data!");

            //Check attributes array for use indices

            Dictionary<VertexElementUsage, int> uselist = new Dictionary<VertexElementUsage, int>();

            List<VertexElement> elements = new List<VertexElement>();

            foreach (var at in attributes)
            {
                if (!uselist.ContainsKey(at.Usage))
                    uselist.Add(at.Usage, 0);

                int idx = uselist[at.Usage];
                uselist[at.Usage]++;            //increase use index

                elements.Add(new VertexElement(at.Offset, at.Format, at.Usage, idx));
            }

            VertexDescriptor vd = new VertexDescriptor(elements.ToArray(), Marshal.SizeOf(vertexType), 0);
            cachedData.Add(vertexType, vd);

            //NOTE!!! Padding is not being calculated for now
            return vd;
        }

        /// <summary>
        /// Checks if specified vertex contains definition for normal
        /// </summary>
        /// <param name="vertextype">type of the vertex</param>
        /// <returns>True if contains normal, false if not or if type is not recognized vertex</returns>
        public static bool HasNormal(Type vertextype)
        {
            if (!cachedData.ContainsKey(vertextype))
            {
                try
                {
                    GetDescriptor(vertextype);
                }
                catch
                {
                    return false;
                }
            }
            return cachedData[vertextype].HasNormal();
        }

        /// <summary>
        /// Checks if vertex type contains color information
        /// </summary>
        /// <param name="vertextype">type of the vertex</param>
        /// <returns>True if contains color, false if not or if type is not recognized vertex</returns>
        public static bool HasColor(Type vertextype)
        {
            if (!cachedData.ContainsKey(vertextype))
            {
                try
                {
                    GetDescriptor(vertextype);
                }
                catch
                {
                    return false;
                }
            }
            return cachedData[vertextype].HasColor();
        }

        /// <summary>
        /// Checks if vertex type contains texture coordinates
        /// </summary>
        /// <param name="vertextype">type of the vertex</param>
        /// <returns>True if contains texturecoordinates, false if not or if type is not recognized vertex</returns>
        public static bool HasTexCoord(Type vertextype)
        {
            if (!cachedData.ContainsKey(vertextype))
            {
                try
                {
                    GetDescriptor(vertextype);
                }
                catch
                {
                    return false;
                }
            }
            return cachedData[vertextype].HasTexCoord();
        }




    }


 
}
