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

namespace VisimBox.Scene
{
    
    /// <summary>
    /// Renderable node implementing IRenderable
    /// </summary>
    public abstract class RenderableNode : Node, IRenderable
    {
        /// <summary>
        /// Buffered item
        /// </summary>
        protected RenderBufferItem BufferedItem;

        /// <summary>
        /// Bounding object of the geometry.
        /// </summary>
        [Description("Bounding object (don't touch it!)")]
        [Category("Rendering data")]
        public IGeometry BoundingObject { get; set; }

        protected LineList BoundingObjectWireFrame;

        /// <summary>
        /// If set to true, Bounding object will be rendered around as wireframe (with zbuffer off)
        /// </summary>
        [Description("If set to true, Bounding object will be rendered around as wireframe (with zbuffer off)")]
        [Category("Rendering data")]        
        public bool RenderBoundObject { get; set; }

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


        /// <summary>
        /// Constructs parentless renderable node
        /// </summary>
        public RenderableNode()
            : this(null as Node)
        {
        }

        /// <summary>
        /// Constructs node with specified parent (and automatically saves itselfs to parents children)
        /// </summary>
        /// <param name="parent">Parent node</param>
        public RenderableNode(Node parent) : base(parent)
        {
            this.Visible = true;
        }

        /// <summary>
        /// XML reader constructor
        /// </summary>
        /// <param name="reader">XML reader</param>
        public RenderableNode(System.Xml.XmlReader reader)
            : base(reader)
        {
            this.Visible = true;
        }

        protected override bool PickNode(ref NodeIntersectionInfo intersinfo)
        {
            bool rval = false;
            if (Pickable)
            {
                //store ray
                Vector3 ori = intersinfo.PickingRay.Origin;
                Vector3 dir = intersinfo.PickingRay.Direction;

                //transform ray

                Matrix4 mat = Matrix4.Identitity;
                bool transf = GetWorldTransformation(ref mat);
                if (transf)
                {
                    Matrix4 inv = Matrix4.Invert(ref mat);
                    intersinfo.PickingRay.Transform(ref inv);
                }

                rval = PickNodeInternal(ref intersinfo);

                //return ray to original state
                if (transf)
                {
                    intersinfo.PickingRay.FromDirection(ori, dir);

                    if (rval)
                    {
                        intersinfo.IntersectionPoint = mat.Multiply(intersinfo.IntersectionPoint);
                    }
                }
            }
            return base.PickNode(ref intersinfo) | rval;
        }

        /// <summary>
        /// Internal picking, expects ray already transformed!
        /// </summary>
        /// <param name="intersinfo"></param>
        /// <returns></returns>
        protected virtual bool PickNodeInternal(ref NodeIntersectionInfo intersinfo)
        {
            if (BoundingObject != null)
            {
                IIntersectionInfo info = (IIntersectionInfo)intersinfo;
                if (BoundingObject.GetIntersectionInfo(intersinfo.PickingRay, ref info))
                {
                    intersinfo.NodeObject = this;
                    intersinfo.Distance = info.Distance;
                    intersinfo.GeoObject = info.GeoObject;
                    intersinfo.IntersectionPoint = info.IntersectionPoint;
                    return true;
                }
            }
            return false;
        }



        #region IRenderable Members

        /// <summary>
        /// Called by the renderer when rendering hiearchy of elements.
        /// Object must either perform complete render (set transformation, material and use direct rendering calls)
        /// or feed renderbuffer of the context with correct data.
        /// </summary>
        /// <param name="args">Rendering events - pointer to rendering context and renderer</param>
        /// <remarks>Direct rendering should be done only in special cases in this call as it removes any rendering optimalizations performed 
        /// on the render buffer. This is important especially for any objects using alpha transparency, which could create visual artefacts unless rendered
        /// in sorter back to front order.</remarks>
        public virtual void OnRender(RendererEventArgs args)
        {
            UpdateRenderBuffer(args);
            args.RC.AddRenderItem(BufferedItem);

            if ((RenderBoundObject) || (Scene.RenderFlags.BoundingObject&((RenderFlags)args.RenderFlags)) != 0)
            {
                //if (false)
                //{
                    if (BoundingObject != null)
                    {
                        if (BoundingObjectWireFrame == null)
                        {
                            BoundingObjectWireFrame = BoundingObject.ToLines(0.1f, 0);
                        }

                        if (BufferedItem.TransValid)
                            args.RC.SetTransformation(ref BufferedItem.Trans);
                        else
                            args.RC.SetTransformation(Matrix4.Identitity);
                        args.Renderer.MaterialMan["default"].Activate(args);
                        //args.RC.SetColor(ColorByte.White);
                        bool v = args.RC.ZBufferUsing;
                        args.RC.ZBufferUsing = false;
                        args.RC.RenderGeometry(BoundingObjectWireFrame);
                        args.RC.ZBufferUsing = v;
                    }
                //}
            }

            //note - todo: add cliping against frustrum here, maybe direct LOD support?
        }

        /// <summary>
        /// Creates render buffer item
        /// </summary>
        public virtual void CreateRenderBuffer()
        {
            BufferedItem = new RenderBufferItem();
            BufferedItem.Item = this;
            BufferedItem.TransValid = false;
        }

        /// <summary>
        /// Updates render buffer with current transformation and other data
        /// </summary>
        public virtual void UpdateRenderBuffer(RendererEventArgs args)
        {
            if (BufferedItem == null)
            {
                CreateRenderBuffer();
            }
            BufferedItem.TransValid = GetWorldTransformation(ref BufferedItem.Trans);
            Vector3 mypos;
            
            if (args.Camera == null)
            {
                //camera is null, set camera distance to zero
                BufferedItem.CameraDistance = 0;
            }
            else
            {
                if (BufferedItem.TransValid)
                    mypos = BufferedItem.Trans.Translation;
                else mypos = new Vector3();

                BufferedItem.CameraDistance = args.Camera.Position.DistanceSquared(ref mypos);
            }            
        }


        /// <summary>
        /// Feed geometry to the renderer (all other settings should be already set)
        /// </summary>
        /// <param name="args">Rendering events - pointer to rendering context and renderer</param>
        /// <remarks>Used when rendering from the renderbuffer</remarks>
        public abstract void RenderGeometry(RendererEventArgs args);

        /// <summary>
        /// Returns bounding object of the renderable item (for lights etc). Should be simplest possible (box)
        /// </summary>
        /// <returns>Bounding object or null if not using one</returns>
        public virtual IGeometry GetBoundingObject()
        {
            return BoundingObject;
        }

        /// <summary>
        /// Fallback function if GetBoundingObject returns null.
        /// </summary>
        /// <returns>Center of the object</returns>
        public virtual Vector3 GetCenter()
        {
            if (Trans != null)
                return Trans.Position;
            return new Vector3();
        }


        #endregion

        public override bool ReadInternalData(System.Xml.XmlReader reader)
        {
            if (base.ReadInternalData(reader))
                return true;
            switch (reader.Name)
            {
                case "BoundingObject":
                    BoundingObject = GeoFactory.ReadXml(reader);
                    break;
                default:
                    return false;
            }
            return true;
        }

        public override void WriteInternalData(System.Xml.XmlWriter writer)
        {
            base.WriteInternalData(writer);
            if (BoundingObject != null)
            {
                writer.WriteStartElement("BoundingObject");
                BoundingObject.WriteXml(writer);
                writer.WriteEndElement();
            }
        }

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

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

            mynode.BoundingObject = BoundingObject.GetCopy();
        }

    }

    /// <summary>
    /// Renderable node containing already created renderer mesh object
    /// </summary>
    public class NodeMesh : RenderableNode, Resources.IResourceConsumer
    {
        protected Mesh _RenderMesh;
        protected IRenderMaterial _Material;

        /// <summary>
        /// if true, will force saving of the mesh geometry to XML
        /// </summary>
        [Description("if true, will force saving of the mesh geometry to XML")]
        [Category("Settings")]
        public bool ForceSaveMesh { get; set; }

        /// <summary>
        /// Rendering mesh
        /// </summary>
        [Description("Rendering mesh")]
        [Category("Rendering data")]
        [TypeConverter(typeof(ExpandableObjectConverter))]
        public Mesh RenderMesh { get { return _RenderMesh; } }
        
        
        /// <summary>
        /// Rendering material
        /// </summary>
        [Description("Rendering material")]
        [Category("Rendering data")]
        [TypeConverter(typeof(ExpandableObjectConverter))]
        public IRenderMaterial Material 
        { 
            get { return _Material; }
            set
            {
                if (_Material != null)
                {
                    _Material.RemoveReference(this);
                }
                _Material = value;
                if (_Material != null)
                    _Material.AddReference(this);
            }
        }

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



        protected NodeMesh()
            : base()
        {
        }

        public NodeMesh(System.Xml.XmlReader reader)
            : base(reader)
        {
        }

        /// <summary>
        /// Constructs node with specified parent (and automatically saves itselfs to parents children) and from Mesh
        /// </summary>
        /// <param name="parent">Parent node</param>
        /// <param name="rendermesh">Renderable mesh (can be null)</param>
        public NodeMesh(Node parent, Mesh rendermesh, IRenderMaterial mat)
            : base(parent)
        {
            //by default - dont' save this object! (usually used as renderer object for something else)
            DontSave = true;
            _RenderMesh = rendermesh;
            if (rendermesh != null)
                rendermesh.AddReference(this);
            _Material = mat;
            if (mat != null)
                mat.AddReference(this);
        }

        /// <summary>
        /// Constructs parentless node with mesh
        /// </summary>
        /// <param name="parent">Parent node</param>
        /// <param name="rendermesh">Renderable mesh (can be null)</param>
        public NodeMesh(Mesh rendermesh, IRenderMaterial mat)
            : this(null, rendermesh, mat)
        {
        }

        /// <summary>
        /// Feed geometry to the renderer (all other settings should be already set)
        /// </summary>
        /// <param name="args">Rendering events - pointer to rendering context and renderer</param>
        /// <remarks>Used when rendering from the renderbuffer</remarks>
        public override void RenderGeometry(RendererEventArgs args)
        {
            if (_RenderMesh != null)
                _RenderMesh.Render(args.RC);
        }

        public override void CreateRenderBuffer()
        {
            base.CreateRenderBuffer();
            BufferedItem.Material = _Material;
        }

        public override void Dispose()
        {
            base.Dispose();
            if (_RenderMesh != null)
            {
                _RenderMesh.RemoveReference(this);
                _RenderMesh = null;
            }
            if (_Material != null)
            {
                _Material.RemoveReference(this);
                _Material = null;
            }
        }

        #region IResourceConsumer Members

        public void ResourceDisposing(Resources.IResource resource)
        {
            if ((resource is IRenderMaterial) && ((resource as IRenderMaterial) == _Material) && (_Material != null))
            {
                if (BufferedItem != null)
                {
                    BufferedItem.Material = null;
                }
                Console.DebugLine("Renderable Node lost it's material!");
                _Material = null;
            }

            if ((resource is Mesh) && ((resource as Mesh) == _RenderMesh) && (_RenderMesh != null))
            {
                _RenderMesh = null;
                Console.DebugLine("Renderable Node lost it's rendermesh!");
            }
        }

        public bool ResourceReplaced(Resources.IResource original, Resources.IResource newresource)
        {

            if ((original is IRenderMaterial) && (original as IRenderMaterial)== _Material)
            {
                IRenderMaterial newmat = newresource as IRenderMaterial;
                if (newmat != null)
                {
                    _Material = newmat;
                    BufferedItem.Material = newmat;
                    return true;
                }
                return false;   //not interested in null material
            }

            if ((original is Mesh) && (original as Mesh) == _RenderMesh)
            {
                Mesh newmesh = newresource as Mesh;
                if (newmesh != null)
                {
                    _RenderMesh = newmesh;
                    return true;
                }
                return false;   //not interested in null mesh
            }

            return false; //not our callback?!
        }

        #endregion


        public override bool ReadInternalData(System.Xml.XmlReader reader)
        {
            if (base.ReadInternalData(reader))
                return true;
            switch (reader.Name)
            {
                case "RenderMaterial":
                    string materialname = reader["id"];

                    if (_Material != null)
                        _Material.RemoveReference(this);

                    var matman = VisimBox.VisimEngine.Instance.Renderer.MaterialMan;
                    
                    if (matman.Contains(materialname))
                    {
                        _Material = matman[materialname];
                    }
                    else
                    {
                        _Material = matman["default"];
                        Console.DebugLine("RenderableNode import - material manager doesn't contain requested material, using default!");
                    }
                    _Material.AddReference(this);
                    break;
                default:
                    return false;
            }
            return true;
        }

        public override void WriteInternalData(System.Xml.XmlWriter writer)
        {
            base.WriteInternalData(writer);

            writer.WriteStartElement("RenderMaterial");

            if (_Material != null)
            {
                writer.WriteAttributeString("id", _Material.Name);
            }
            else
                writer.WriteAttributeString("id", "default");

            writer.WriteEndElement();
        }

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

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

            if (_Material != null)
            {
                mynode._Material = _Material;
                _Material.AddReference(mynode);
            }
        }

    }





}
