using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VisiMath;

namespace VisiMath.Geometry
{
    /// <summary>
    /// Pyramid geometric object
    /// </summary>
    public class Pyramid : TriangleList, IVolumetric, IConvexObject
    {
        //todo - add overrides for collisions?
        
        Vector3[] DirectVertices = new Vector3[5];
        Vector3 PyramidCenter;
        Vector3 PyramidTop;
        float Width;
        float Height;

        /// <summary>
        /// XML reader constructor
        /// </summary>
        /// <param name="reader">XML reader</param>
        public Pyramid(System.Xml.XmlReader reader)
            : base(reader)
        {
        }

        /// <summary>
        /// Creates uninitialized pyramid
        /// </summary>
        public Pyramid()
            : base(18)
        {
        }

        /// <summary>
        /// Creates uninitialized pyramid with specified vertextype
        /// </summary>
        /// <param name="VertexType">required vertex type</param>
        public Pyramid(Type VertexType)
            : base(VertexType, 18)
        {
        }

        /// <summary>
        /// Creates pyramid with default vertextype (normal vertex)
        /// </summary>
        /// <param name="center">Center of the pyramid base</param>
        /// <param name="top">Top of the pyramid</param>
        /// <param name="width">Width of the base (along x axis)</param>
        /// <param name="height">Height of the base (along y axis)</param>
        public Pyramid(Vector3 center, Vector3 top, float width, float height)
            : this()
        {
            Construct(center, top, width, height);
        }

        /// <summary>
        /// Creates pyramid with specified vertextype
        /// </summary>
        /// <param name="vertexType">Required type of the vertex</param>
        /// <param name="center">Center of the pyramid base</param>
        /// <param name="top">Top of the pyramid</param>
        /// <param name="width">Width of the base (along x axis)</param>
        /// <param name="height">Height of the base (along y axis)</param>
        public Pyramid(Type vertexType, Vector3 center, Vector3 top, float width, float height)
            : this(vertexType)
        {
            Construct(center, top, width, height);
        }


        /// <summary>
        /// Constructs/ reconstructs Pyramid
        /// </summary>
        /// <param name="center">Center of the pyramid base</param>
        /// <param name="top">Top of the pyramid</param>
        /// <param name="width">Width of the base (along x axis)</param>
        /// <param name="height">Height of the base (along y axis)</param>
        /// <remarks>For textured pyramid uses same texture for base (whole texture) and all sides (triangles from the texture sides to center)</remarks>
        public void Construct(Vector3 center, Vector3 top, float width, float height)
        {
            PyramidCenter = center;
            PyramidTop = top;

            //first create basic Pyramid
            width = width / 2;
            height = height / 2;

            Width = width;
            Height = height;

            Construct();
        }

        /// <summary>
        /// Constructs/ reconstructs Pyramid
        /// </summary>
        /// <remarks>For textured pyramid uses same texture for base (whole texture) and all sides (triangles from the texture sides to center)</remarks>
        public void Construct()
        {

            DirectVertices[0] = new Vector3( - Width,  - Height, 0);
            DirectVertices[1] = new Vector3( + Width,  - Height, 0);
            DirectVertices[2] = new Vector3( + Width,  + Height, 0);
            DirectVertices[3] = new Vector3( - Width,  + Height, 0);
            DirectVertices[4] = new Vector3(0, 0, PyramidCenter.Distance(ref PyramidTop));

            //rotate pyramid

            Vector3 norm = PyramidTop - PyramidCenter;
            norm.Normalize();

            Matrix4 trans = Matrix4.CreateRotationNormal(Vector3.UnitZ, norm);
            trans.Translation = PyramidCenter;

            trans.TransformFast(DirectVertices);

            //construct renderable vertices

            Type VertexType = Vertices[0].GetType();

            bool colors = VertexFactory.HasColor(VertexType);
            bool normals = VertexFactory.HasNormal(VertexType);
            bool txcoords = VertexFactory.HasTexCoord(VertexType);

            IVertex vert = VertexFactory.CreateNewVertex(VertexType);

            if (colors)
                vert.Color = ColorByte.White;

            Width = Width / 2;
            Height = Height / 2;

            //base

            Vector3 normal = PyramidCenter - PyramidTop;
            normal.Normalize();

            vert.Position = DirectVertices[0];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(1, 0);
            Vertices[0] = vert;

            vert.Position = DirectVertices[3];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(1, 1);
            Vertices[1] = vert;

            vert.Position = DirectVertices[2];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(0, 1);
            Vertices[2] = vert;

            vert.Position = DirectVertices[2];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(0, 1);
            Vertices[3] = vert;

            vert.Position = DirectVertices[1];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(0, 0);
            Vertices[4] = vert;

            vert.Position = DirectVertices[0];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(1, 0);
            Vertices[5] = vert;

            //sides
            //side -x

            normal = MathTools.CalcTriNormal(ref DirectVertices[1], ref DirectVertices[4], ref DirectVertices[0]);

            vert.Position = DirectVertices[1];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(0, 0);
            Vertices[6] = vert;

            vert.Position = DirectVertices[4];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(0.5f, 0.5f);
            Vertices[7] = vert;


            vert.Position = DirectVertices[0];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(1, 0);
            Vertices[8] = vert;
            
            //side x

            normal = MathTools.CalcTriNormal(ref DirectVertices[3], ref DirectVertices[4], ref DirectVertices[2]);

            vert.Position = DirectVertices[3];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(1, 1);
            Vertices[9] = vert;

            vert.Position = DirectVertices[4];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(0.5f, 0.5f);
            Vertices[10] = vert;


            vert.Position = DirectVertices[2];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(0, 1);
            Vertices[11] = vert;

            //side -y

            normal = MathTools.CalcTriNormal(ref DirectVertices[0], ref DirectVertices[4], ref DirectVertices[3]);

            vert.Position = DirectVertices[0];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(1, 0);
            Vertices[12] = vert;

            vert.Position = DirectVertices[4];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(0.5f, 0.5f);
            Vertices[13] = vert;

            vert.Position = DirectVertices[3];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(1, 1);
            Vertices[14] = vert;

            //side y

            normal = MathTools.CalcTriNormal(ref DirectVertices[2], ref DirectVertices[4], ref DirectVertices[1]);

            vert.Position = DirectVertices[2];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(0, 1);
            Vertices[15] = vert;

            vert.Position = DirectVertices[4];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(0.5f, 0.5f);
            Vertices[16] = vert;

            vert.Position = DirectVertices[1];
            if (normals) vert.Normal = normal;
            if (txcoords) vert.TexCoord2 = new Vector2(0, 0);
            Vertices[17] = vert;                           
         
        }


        /// <summary>
        /// Center of the object, for pyramid it's centre of the base
        /// </summary>
        public override Vector3 Center
        {
            get
            {
                return PyramidCenter;
            }
        }

        /// <summary>
        /// Returns true, if position is inside the pyramid
        /// </summary>
        /// <param name="pos">Position</param>
        /// <returns>True if inside</returns>
        public override bool ContainsPosition(Vector3 pos)
        {
            //note!!! cache planes for quicker checking
            //check if point is INSIDE

            Vector3 normal = MathTools.CalcTriNormal(ref DirectVertices[0], ref DirectVertices[1], ref DirectVertices[2]);
            if (MathTools.DistanceVectorPlane(ref DirectVertices[0], ref normal, ref pos) < 0)
                return false;

            normal = MathTools.CalcTriNormal(ref DirectVertices[3], ref DirectVertices[2], ref DirectVertices[4]);
            if (MathTools.DistanceVectorPlane(ref DirectVertices[3], ref normal, ref pos) < 0)
                return false;

            normal = MathTools.CalcTriNormal(ref DirectVertices[2], ref DirectVertices[1], ref DirectVertices[4]);
            if (MathTools.DistanceVectorPlane(ref DirectVertices[2], ref normal, ref pos) < 0)
                return false;

            normal = MathTools.CalcTriNormal(ref DirectVertices[1], ref DirectVertices[0], ref DirectVertices[4]);
            if (MathTools.DistanceVectorPlane(ref DirectVertices[1], ref normal, ref pos) < 0)
                return false;

            normal = MathTools.CalcTriNormal(ref DirectVertices[0], ref DirectVertices[3], ref DirectVertices[4]);
            if (MathTools.DistanceVectorPlane(ref DirectVertices[4], ref normal, ref pos) < 0)
                return false;

            return true;
        }
        //todo : intersections...

        /// <summary>
        /// Serializes object to XML
        /// </summary>
        /// <param name="writer">XML writer to save values</param>
        public override void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteAttributeString("type", this.GetType().Name);
            writer.WriteAttributeString("VertexType", this.PreferredVertexType.ToString());
            PyramidCenter.WriteXml(writer, "PyramidCenter");
            PyramidTop.WriteXml(writer, "PyramidTop");
            XMLHelper.WriteFloat(writer, "Width", Width);
            XMLHelper.WriteFloat(writer, "Height", Height);
        }

        /// <summary>
        /// Deserializes object from XML
        /// </summary>
        /// <param name="reader">XML reader</param>
        public override void ReadXml(System.Xml.XmlReader reader)
        {
            if (reader["type"] != this.GetType().Name)
                throw new ArgumentException("IGeometry:ReadXML - wrong xml node type!");

            string str = reader["VertexType"];
            
            Type vertype = VertexFactory.GetTypeFromName(str);
            if (vertype == null)
                vertype = typeof(NormalVertex);

            Vertices = VertexFactory.CreateArray(vertype, 18);

            while (reader.Read())
            {
                if (reader.IsStartElement())
                {
                    if (reader.Name == "PyramidCenter")
                    {
                        PyramidCenter.ReadXml(reader);
                    }
                    else if (reader.Name == "PyramidTop")
                    {
                        PyramidTop.ReadXml(reader);
                    }
                    else if (reader.Name == "Width")
                    {
                        Width = XMLHelper.ReadAsFloat(reader);
                    }
                    else if (reader.Name == "Height")
                    {
                        Height = XMLHelper.ReadAsFloat(reader);
                    }
                }
                else break;
            }
            Construct();
        }


        #region IVolumetric Members

        public float Volume
        {            
            get 
            {   
                Vector3 Length = PyramidTop - PyramidCenter;
                return Width * Height * Length.Length / 3;
            }
        }

        #endregion

        #region IConvexObject Members

        public ConvexObjectType BaseType
        {
            get { return ConvexObjectType.Explicit; }
        }

        public int VertexCount
        {
            get { return 5; }
        }

        public void GetVertex(int index, out Vector3 vertex)
        {
            vertex = DirectVertices[index];
        }

        /// <summary>
        /// Gets supporting vertex in local coordinates
        /// </summary>
        /// <param name="vertex">Save vertex</param>
        /// <param name="direction">Direction vector</param>
        public void GetLocalSupportingVertex(ref Vector3 direction, out Vector3 vertex)
        {
            float max = float.NegativeInfinity;
            vertex = new Vector3();

            //note bruteforce implementation, use hill climbing?

            for (int i = 0; i < DirectVertices.Length; i++)
            {
                Vector3 vec = DirectVertices[i];
                float dot = vec.Dot(ref direction);
                if (dot > max)
                {
                    max = dot;
                    vertex = vec;
                }
            }
        }

        #endregion
    }
}
