using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VisimBox.Renderer;
using VisimBox.Renderer.RenderObjects;
using VisiMath;
using VisiMath.Geometry;
using System.ComponentModel;

namespace VisimBox.Scene
{
    /// <summary>
    /// Scene node for holding light
    /// </summary>
    public class LightNode : RenderableNode, Resources.IResourceConsumer
    {
        /// <summary>
        /// ILight with the actual light
        /// </summary>
        [Description("Light object associated to the node")]
        [Category("Light data")]
        [TypeConverter(typeof(ExpandableObjectConverter))]
        public ILight LightObject { get; set; }

        Mesh DebugMesh;
        IRenderMaterial DebugMaterial;

        public override RenderFlags DefaultRenderFlags
        {
            get
            {
                return Scene.RenderFlags.Light;
            }
        }

        public LightNode(System.Xml.XmlReader reader)
            : base(reader)
        {
            Visible = true;
            BoundingObject = new Sphere(new Vector3(0), 1);
        }

        public LightNode()
        {
            Visible = true;
            BoundingObject = new Sphere(new Vector3(0), 1);            
        }

        public LightNode(ILight light)
        {
            Visible = true;
            BoundingObject = new Sphere(new Vector3(0), 1);
            LightObject = light;
            light.AddReference(this);
            UpdateLight(0);
        }

        Vector3 []LightLines;

        protected void CreateLinesDirectional()
        {
            if (LightLines == null)
                LightLines = new Vector3[10];

            /*
            LightLines[0] = new Vector3(0);
            LightLines[1] = new Vector3(LightObject.Direction, 5);
            
            LightLines[2] = new Vector3(0.5f, 0, 0);
            LightLines[3] = new Vector3(LightObject.Direction, 5);
            LightLines[3].Add(ref LightLines[2]);

            LightLines[4] = new Vector3(-0.5f, 0, 0);
            LightLines[5] = new Vector3(LightObject.Direction, 5);
            LightLines[5].Add(ref LightLines[4]);

            LightLines[6] = new Vector3(0, 0.5f, 0);
            LightLines[7] = new Vector3(LightObject.Direction, 5);
            LightLines[7].Add(ref LightLines[6]);

            LightLines[8] = new Vector3(0, -0.5f, 0);
            LightLines[9] = new Vector3(LightObject.Direction, 5);
            LightLines[9].Add(ref LightLines[8]);*/


            LightLines[0] = new Vector3(0);
            LightLines[1] = new Vector3(-5, 0, 0);
            LightLines[2] = new Vector3(0, 1, 0);
            LightLines[3] = new Vector3(-5, 1, 0);
            LightLines[4] = new Vector3(0, -1, 0);
            LightLines[5] = new Vector3(-5, -1, 0);
            LightLines[6] = new Vector3(0, 0, 1);
            LightLines[7] = new Vector3(-5, 0, 1);
            LightLines[8] = new Vector3(0, 0, -1);
            LightLines[9] = new Vector3(-5, 0, -1);

        }

        protected void CreateLinesSpot()
        {
            if (LightLines == null)
                LightLines = new Vector3[10];

            LightLines[0] = LightLines[2] = LightLines[4] = LightLines[6] = LightLines[8] = new Vector3(0);

            LightLines[1] = new Vector3(5, 0, 0);

            float angle = MathTools.DegToRad(LightObject.SpotAngle);
            LightLines[3] = new Vector3(5 * MathTools.Cos(angle), -5 * MathTools.Sin(angle), 0);
            LightLines[5] = new Vector3(5 * MathTools.Cos(angle), 5 * MathTools.Sin(angle), 0);
            
            LightLines[7] = new Vector3(5 * MathTools.Cos(angle), 0, -5 * MathTools.Sin(angle));
            LightLines[9] = new Vector3(5 * MathTools.Cos(angle), 0, 5 * MathTools.Sin(angle));
        }

        /// <summary>
        /// Updates light
        /// </summary>
        /// <param name="time">elapsed time</param>
        /// <returns>True if light object was changed</returns>
        public virtual bool UpdateLight(double time)
        {
            //Note!!! optimize and recalc light ONLY if there was a change....

            if (LightObject == null)
                return false;
            
            Matrix4 trans = Matrix4.Identitity;
            GetWorldTransformation(ref trans);

            Vector3 norm = new Vector3(1, 0, 0);

            switch (LightObject.LightType)
            {
                case LightTypes.Directional:
                    //update only direction
                    LightObject.Direction = trans.Rotation.Multiply(ref norm);
                    CreateLinesDirectional();

                    break;
                case LightTypes.Position:
                    //update only position
                    LightObject.Position = trans.Translation;
                    break;
                case LightTypes.Spot:
                    //update position AND direction
                    LightObject.Position = trans.Translation;
                    LightObject.Direction = trans.Rotation.Multiply(ref norm);
                    CreateLinesSpot();

                    break;
            }

            return true;
        }



        public override bool TimeTick(double time)
        {
            bool rval = UpdateLight(time);

            if (base.TimeTick(time) || rval)
                return true;
            return false;
        }


        public override void Dispose()
        {
            if (LightObject != null)
            {
                //we should be the ONLY owner of any light.
                //So remove it
                LightObject.RemoveReference(this);
                VisimEngine.Instance.Renderer.LightMan.Remove(LightObject.Name);
                LightObject = null;
            }

            if (DebugMaterial != null)
            {
                DebugMaterial.RemoveReference(this);
                DebugMaterial = null;
            }

            if (DebugMesh != null)
            {
                DebugMesh.RemoveReference(this);
                DebugMesh = null;
            }
            base.Dispose();
        }

        #region IResourceConsumer Members

        public void ResourceDisposing(Resources.IResource resource)
        {
            if ((resource as ILight) != null)
                LightObject = null; //shouldn't happen, but...
            if ((resource as IRenderMaterial) != null)
                DebugMaterial = null;
            if ((resource as Mesh) != null)
                DebugMesh = null;
        }

        public bool ResourceReplaced(Resources.IResource original, Resources.IResource newresource)
        {
            if ((original as ILight) == LightObject)
            {
                LightObject = newresource as ILight;
                return true;
            }

            if ((original as IRenderMaterial) == DebugMaterial)
            {
                DebugMaterial = newresource as IRenderMaterial;
                return true;
            }

            if ((original as Mesh) == DebugMesh)
            {
                DebugMesh = newresource as Mesh;
                return true;
            }

            return false;
        }

        #endregion


        Matrix4 worldtrans = Matrix4.Identitity;

        public override void CreateRenderBuffer()
        {
            base.CreateRenderBuffer();
            if (DebugMaterial == null)
            {
                var matman = VisimEngine.Instance.Renderer.MaterialMan;
                if (matman.Contains("LightSphereDebugMaterial"))
                {
                    DebugMaterial = matman["LightSphereDebugMaterial"];
                }
                else
                {
                    DebugMaterial = matman.Add("LightSphereDebugMaterial", new ColorByte(255, 255, 0, 128));
                }
                DebugMaterial.AddReference(this);
            }

            BufferedItem.Material = DebugMaterial;
        }        

        public override void RenderGeometry(RendererEventArgs args)
        {
                if (DebugMesh == null)
                {
                    var meshman = VisimEngine.Instance.Renderer.MeshMan;
                    if (meshman.Contains("LightSphereDebugObject"))
                    {
                        DebugMesh = meshman["LightSphereDebugObject"];
                    }
                    else
                    {
                        IndexedMesh im = BoundingObject.ToIndexedMesh<NormalVertex>(0.2f);
                        DebugMesh = meshman.Add(im, "LightSphereDebugObject");
                    }
                    DebugMesh.AddReference(this);
                }
            //DebugMaterial.Activate(args);
            DebugMesh.Render(args.RC);

            if (LightLines == null)
                return;
            switch (LightObject.LightType)
            {
                case LightTypes.Directional:
                case LightTypes.Spot:
                    for (int i = 0; i < LightLines.Length / 2; i++)
                        args.RC.DrawLine3D(ref LightLines[i*2], ref LightLines[i*2+1]);
                    break;
            }
        }

        protected override void CopyData(Node targetNode)
        {
            base.CopyData(targetNode);

            LightNode mynode = targetNode as LightNode;
            if (mynode == null)
            {
                VisimBox.Console.DebugLine(FullName + " asked to do copy to different node type!");
                return;
            }

            if (LightObject != null)
            {
                //todo : active area!
                mynode.LightObject = VisimEngine.Instance.Renderer.LightMan.CreateCopy(LightObject);
            }
                 

        }


        public override bool ReadInternalData(System.Xml.XmlReader reader)
        {
            if (base.ReadInternalData(reader))
                return true;
            switch (reader.Name)
            {
                case "Light":
                    if (LightObject != null)
                    {
                        LightObject.RemoveReference(this);
                    }

                    var lightman = VisimEngine.Instance.Renderer.LightMan;

                    LightObject = lightman.ReadFromXml(reader);
                    break;
                default:
                    return false;
            }
            return true;
        }

        public override void WriteInternalData(System.Xml.XmlWriter writer)
        {
            base.WriteInternalData(writer);
            writer.WriteStartElement("Light");
            if (LightObject != null)
            {
                LightObject.WriteXml(writer);
            }
            //TODO - active area saving if set from node!
            writer.WriteEndElement();
        }

        /// <summary>
        /// Deserializes object from XML
        /// </summary>
        /// <param name="reader">XML reader</param>
        public override void ReadXml(System.Xml.XmlReader reader)
        {
            base.ReadXml(reader);
            UpdateLight(0);
        }

    }
}
