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.Xml.Serialization;
using System.ComponentModel;

namespace VisimBox.Scene
{

    public struct NodeIntersectionInfo : IIntersectionInfo
    {
        //geointersection data

        /// <summary>
        /// Distance from the origin
        /// </summary>
        public float Distance { get; set; }
        /// <summary>
        /// Geometrical object with intersection
        /// </summary>
        public IGeometry GeoObject { get; set; }
        /// <summary>
        /// Intersection point
        /// </summary>
        public Vector3 IntersectionPoint { get; set; }

        //node data

        /// <summary>
        /// Node containing object
        /// </summary>
        public Node NodeObject { get; set; }

        /// <summary>
        /// Ray used for picking
        /// </summary>
        public Ray PickingRay { get; set; }



        /// <summary>
        /// creates new starting intersection info
        /// </summary>
        /// <returns></returns>
        public static NodeIntersectionInfo CreateNew(Ray ray)
        {
            NodeIntersectionInfo rval = new NodeIntersectionInfo();
            rval.Distance = float.MaxValue;
            rval.GeoObject = null;
            rval.IntersectionPoint = Vector3.Invalid;
            rval.NodeObject = null;
            rval.PickingRay = ray;
            return rval;
        }
    }

    public delegate void NodeCallBack(Node caller, object args);

    /// <summary>
    /// Base class for all nodes
    /// </summary>    
    public class Node : IDisposable, IEnumerable<Node>, IXmlSerializable
    {
        #region fields
        protected Node _parent;
        protected NodeDictionary _children;
        protected bool _visible;

        #endregion

        #region children handling
        /// <summary>
        /// Parent of the Node
        /// </summary>
        [Description("Parent of the node")]
        [Category("Generic data")]
        public Node Parent
        {
            get
            {
                return _parent;
            }
        }

        private NodeDictionary Children
        {
            get
            {
                return _children;
            }
        }

        /// <summary>
        /// Change node ownership
        /// </summary>
        /// <param name="newparent">New parent of the node</param>
        /// <param name="remove">if true, node will be also removed from the current parent list</param>
        /// <param name="add">if true, will be added to newparent nodes</param>
        public void ChangeParent(Node newparent, bool remove = true, bool add = false)
        {
            if (_parent == newparent)
                return;

            if ((_parent != null) && (remove))
            {
                _parent.Children.Remove(this);
            }
            _parent = newparent;
            if (add)
            {
                _parent.AddNode(this, this.Name);
            }
        }

        protected void RenameChild(Node child, string newname)
        {
            _children.RenameChild(child, newname);
        }


        /// <summary>
        /// Array like accessor to all direct subnodes
        /// </summary>
        /// <param name="index">name of the child node</param>
        /// <returns>Null or named Node</returns>
        public Node this[string index]
        {
            get
            {
                Node rval;
                if (_children.TryGetValue(index, out rval))
                    return rval;
                return null;
            }
        }

        #region events

        /// <summary>
        /// Callback for node local transformation change
        /// </summary>
        public event NodeCallBack NodeMoved;
        /// <summary>
        /// User callback for new node added, arguments = new node
        /// </summary>
        public event NodeCallBack NodeAdded;
        /// <summary>
        /// User callback for new node added, arguments = node removed
        /// </summary>
        public event NodeCallBack NodeRemoved;


        #endregion


        /// <summary>
        /// Adds node to children
        /// </summary>
        /// <param name="node">Node</param>
        /// <param name="name">Name (warning, if node already contains node of this name, name will be changed!</param>
        public virtual void AddNode(Node node, string name = "subnode")
        {
            if (name == null)
                name = "unnamed";
            Children.Add(name, node);
        }

        /// <summary>
        /// Callback from children, reporting it has new child
        /// </summary>
        /// <param name="tonode">Node receiving new node</param>
        /// <param name="newchild">New node</param>
        public virtual void OnNodeAdded(Node tonode, Node newchild)
        {
            if (_parent != null)
            {
                Parent.OnNodeAdded(tonode, newchild);   //send it up to the scene manager or anyone interested
            }
            if (NodeAdded != null)
                NodeAdded(this, newchild);
        }

        /// <summary>
        /// Callback from children, reporting child has been removed
        /// </summary>
        /// <param name="tonode">Node losing child</param>
        /// <param name="newchild">removed node</param>
        public virtual void OnNodeRemoved(Node fromnode, Node removed)
        {
            if (_parent != null)
            {
                _parent.OnNodeRemoved(fromnode, removed);
            }
            if (NodeRemoved != null)
                NodeRemoved(this, removed);
        }

        public virtual void RemoveNode(string name)
        {
            if (!_children.ContainsKey(name))
                return;
            Node node = this[name];
            if (!_children.Remove(name))
            {
                Console.DebugLine(string.Format("Node {0} asked to remove node {1} - node removal failed!", this.FullName, name));
                return;
            }
            if (_parent != null)
            {
                Parent.OnNodeRemoved(this, node);
            }

        }

        public virtual void RemoveNode(Node node)
        {
            if (!_children.ContainsKey(node.Name))
            {
                Console.DebugLine(string.Format("Node {0} asked to remove node {1} - can't find by name!", this.FullName, node.FullName));
                return;
            }
            Node nod = this[node.Name];
            if (nod != node)
            {
                Console.DebugLine(string.Format("Node {0} asked to remove node {1} - name found, but nodes are different!", this.FullName, node.FullName));
                return;
            }
            RemoveNode(node.Name);
        }

        /// <summary>
        /// Searches for child by fullname
        /// </summary>
        /// <param name="fullname">Fullname of the child</param>
        /// <returns>null or child node</returns>
        public virtual Node GetChildByFullName(string fullname)
        {
            return _children.GetChildByFullName(fullname);
        }

        /// <summary>
        /// Searches subnodes for specified name.
        /// </summary>
        /// <param name="name">Name of the desired child, can be specified with parent/parent2/child syntax to find correct node</param>
        /// <returns>Null or found child node</returns>
        public virtual Node SearchChild(string name)
        {
            return _children.SearchChild(name);
        }


        /// <summary>
        /// Renders node hierarchy to rendercontext
        /// </summary>
        /// <param name="args">Renderer arguments</param>
        public virtual void RenderHierarchy(RendererEventArgs args)
        {
            if (!this.VisibleHierarchy)
                return;

            if ((this._RenderFlags & (RenderFlags)args.RenderFlags)!= 0 && this.Visible)
            {
                IRenderable node = this as IRenderable;

                if (node != null)
                {
                    node.OnRender(args);
                }
            }

            foreach (var child in _children)
            {
                child.Value.RenderHierarchy(args);
            }                    
        }

        #endregion

        #region Properties

        protected string _Name;

        /// <summary>
        /// Name of the node
        /// </summary>
        [Description("Name of the node")]
        [Category("Generic data")]
        public string Name { 
            get 
            { 
                return _Name; 
            }
        }

        public void Rename(string newname)
        {
            if (Parent != null)
            {
                _parent.RenameChild(this, newname);
            }
            else
                _Name = newname;
        }

        /// <summary>
        /// DO NOT USE, used internaly by NodeDictionary
        /// </summary>
        /// <param name="newname"></param>
        public void RenameForDictionary(string newname)
        {
            _Name = newname;    //todo, allow generic renaming!
        }

        /// <summary>
        /// Fullname of the node (complete tree)
        /// </summary>
        [Description("Fullname of the node (complete tree)")]
        [Category("Generic data")]
        public string FullName
        {
            get
            {
                if (_parent != null)
                    return string.Format("{0}/{1}", _parent.FullName, Name);
                return Name;
            }
        }

        /// <summary>
        /// If set to true, node won't save itself nor any of it's children (used by temporary renderer and other objects)
        /// </summary>
        [Description("If set to true, node won't save itself nor any of it's children")]
        [Category("Settings")]
        public bool DontSave { get; set; }


        /// <summary>
        /// Current Render flags for the node (node will be rendered only if flags are correct
        /// </summary>
        [Description("Render flags for the node (node will be rendered only if flags are correct)")]
        [Category("Settings")]
        public RenderFlags RenderFlags { get { return _RenderFlags; } set { _RenderFlags = value; } }

        /// <summary>
        /// Default render flags for the node (node will be rendered only if flags are correct)
        /// </summary>
        [Description("Default render flags for the node (node will be rendered only if flags are correct)")]
        [Category("Settings")]
        public virtual RenderFlags DefaultRenderFlags { get { return Scene.RenderFlags.HierarchyNode; } }


        protected RenderFlags _RenderFlags;

        protected ITransformation _Trans;

        /// <summary>
        /// Spatial transformation of the node. If null, node has no transformation.
        /// </summary>
        [TypeConverter(typeof(ExpandableObjectConverter))]
        [Description("Spatial transformation of the node. If null, node has no transformation.")]
        [Category("Generic data")]
        virtual public ITransformation Trans {
            get { return _Trans; }
            set
            {
                if (_Trans != null)
                {
                    _Trans.ChangeCallback -= TransformationChanged;
                }
                _Trans = value;
                if (_Trans != null)
                {
                    _Trans.ChangeCallback += new TransformationCallback(TransformationChanged);
                    _MyTransChanged = true;
                }
            }
        }

        /// <summary>
        /// My transformation was changed, used by getworldtransformation
        /// </summary>
        protected bool _MyTransChanged;
        /// <summary>
        /// My boss transformation was changed, used by getworldtransformation
        /// </summary>
        protected bool _MyParentTransChanged;

        /// <summary>
        /// Cached world transformation
        /// </summary>
        protected Matrix4 _WorldTrans;

        protected virtual void TransformationChanged(ITransformation caller)
        {
            _MyTransChanged = true;
            //report it to subnodes
            foreach (var node in _children)
            {
                node.Value.ParentTransChanged();
            }

            OnNodeMoved();
        }

        protected void ParentTransChanged()
        {
            _MyParentTransChanged = true;
            foreach (var node in _children)
            {
                node.Value.ParentTransChanged();
            }
        }

        protected virtual void OnNodeMoved()
        {
            if (NodeMoved != null)
            {
                NodeMoved(this, null);
            }
        }

        /// <summary>
        /// If node has world transformation (either it's own transform multiplied by owner hierarchy, or valid transform of one of his parents),
        /// it sets parameter to this transformation and returns true
        /// </summary>
        /// <param name="trans">Parameter to be filled with transformation matrix</param>
        /// <returns>True if trans is valid (if false, world transformation for node is identity)</returns>
        /// <remarks>Optimize, cache results and update them only if transformation changes</remarks>
        virtual public bool GetWorldTransformation(ref Matrix4 trans)
        {
            if (Trans == null)
            {
                if (Parent != null)
                    return Parent.GetWorldTransformation(ref trans);
                return false;
            }

            if (!_MyTransChanged && !_MyParentTransChanged)
            {
                //return cached result
                trans = _WorldTrans;
                return true;
            }

            if ((Parent != null) && (Parent.GetWorldTransformation(ref trans)))
            {
                trans.MultiplyBy(Trans.TransMatrix);
            }            
            else trans = Trans.TransMatrix;
            _WorldTrans = trans;
            _MyTransChanged = false;
            _MyParentTransChanged = false;

            return true;
        }



        /// <summary>
        /// Should be node and its children rendered on render call?
        /// </summary>
        [Description("Should be node and its children rendered on render call?")]
        [Category("Settings")]
        public bool VisibleHierarchy { get; set; }

        /// <summary>
        /// Is node visible and renderable? (in this case it must implement IRenderable interface)
        /// </summary>
        [Description("Is node visible and renderable? (in this case it must implement IRenderable interface)")]
        [Category("Settings")]
        public bool Visible { get { return _visible; } set { _visible = value; } }
        /// <summary>
        /// Is node and it's children static (no relevant changes for renderer / physics)
        /// </summary>
        [Description("Is node and it's children static (no relevant changes for renderer / physics)")]
        [Category("Settings")]
        public bool StaticHierarchy { get; set; }

        /// <summary>
        /// Can node be picked by mouse click?
        /// </summary>
        [Description("Can node be picked by mouse click?")]
        [Category("Settings")]
        public bool Pickable { get; set; }

        #endregion

        #region Constructors

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

        public Node(System.Xml.XmlReader reader) : this()
        {
            ReadXml(reader);
        }

        /// <summary>
        /// Constructs node with specified parent (and automatically saves itselfs to parents children)
        /// </summary>
        /// <param name="parent"></param>
        public Node(Node parent)
        {
            _parent = parent;
            _children = new NodeDictionary(this, parent);
            this.VisibleHierarchy = true;
            Pickable = true;
            _RenderFlags = DefaultRenderFlags;
        }

        /// <summary>
        /// creates and returns copy of this node
        /// </summary>
        /// <returns>Copy of the node</returns>
        public virtual Node CreateCopy()
        {
            Node rval = GetNodeSameType();

            if (rval == null)
                return null;

            //copy my data

            CopyData(rval);

            //get child copies

            foreach (Node node in this)
            {
                Node copy = node.CreateCopy();
                if (copy!= null)
                    rval.AddNode(copy, copy.Name);
            }

            return rval;
        }

        /// <summary>
        /// creates new default node of the same type as this node
        /// </summary>
        /// <returns>new default node of the same type as this node</returns>
        protected virtual Node GetNodeSameType()
        {
            Type nodetype = this.GetType();
            try
            {
                return (Node)Activator.CreateInstance(nodetype);
            }
            catch (Exception ex)
            {
                VisimBox.Console.DebugLine("Node copying " + FullName + " failed (no default constructor?)");
                VisimBox.Console.DebugLine(ex.Message);
                return null;
            }
        }

        #endregion

        #region TimeTickUpdates

        /// <summary>
        /// Do a time tick for node and it's children, parameter time is seconds from the last call, returns true if this node was changed somehow
        /// </summary>
        /// <param name="time">elapsed time</param>
        /// <returns>true if node or some of it's children changed state</returns>
        public virtual bool TimeTickHierarchy(double time)
        {
            bool rval = false;
            foreach (var val in _children)
            {
                if (val.Value.TimeTickHierarchy(time))
                    rval = true;
            }
            if (TimeTick(time))
                return true;
            return rval;
        }

        /// <summary>
        /// Do a time tick
        /// </summary>
        /// <param name="time">Elapsed time in seconds</param>
        /// <returns>, returns true if this node was changed somehow</returns>
        public virtual bool TimeTick(double time)
        {
            return false;
        }

        /// <summary>
        /// set time of node and it's children to absolute value
        /// </summary>
        /// <param name="time">elapsed time in seconds</param>
        /// <returns>returns true if this node was changed somehow</returns>
        public virtual bool SetTimeHierarchy(double time)
        {
            bool rval = false;
            foreach (var val in _children)
            {
                if (val.Value.SetTimeHierarchy(time))
                    rval = true;
            }
            if (SetTime(time))
                return true;
            return rval;
        }

        /// <summary>
        /// set node time to absolute value
        /// </summary>
        /// <param name="time">elapsed time in seconds</param>
        /// <returns>returns true if this node was changed somehow</returns>
        public virtual bool SetTime(double time)
        {
            return false;
        }

        #endregion

        #region Picking

        /// <summary>
        /// Runs picking ray through the node hierarchy and returns first hit node (if any)
        /// </summary>
        /// <param name="ray">Picking ray</param>
        /// <returns>First hit node or null</returns>        
        public virtual Node PickNode(Ray ray)
        {
            NodeIntersectionInfo ninfo = NodeIntersectionInfo.CreateNew(ray);

            if (PickNode(ref ninfo))
                return ninfo.NodeObject;
            return null;
        }

        /// <summary>
        /// Checks node and it's hierarchy for intersection with ray. If node contains some closer object then already found, it returns true and modifies structure.
        /// </summary>
        /// <param name="intersinfo">Intersection info</param>
        /// <returns>True if ray hits something closer</returns>
        protected virtual bool PickNode(ref NodeIntersectionInfo intersinfo)
        {
            bool rval = false;
            foreach (var node in _children)
            {
                if (node.Value.PickNode(ref intersinfo))
                    rval = true;
            }
            return rval;
        }

        #endregion


        #region IDisposable

        protected bool _Disposing = false;
        public virtual void Dispose()
        {
            if (_Disposing) return;
            _Disposing = true;


/*            foreach (var v in _children)
            {
                v.Value.Dispose();
            }*/
            _children.Clear();
        }
        #endregion

        #region IEnumerator<Node> Members

        private class NodeEnumerator : IEnumerator<Node>
        {
            protected IEnumerator<KeyValuePair<string, Node>> ChildrenEnumerator = null;
            protected Node Boss;

            public Node Current
            {
                get
                {
                    return ChildrenEnumerator.Current.Value;
                }
            }
            object System.Collections.IEnumerator.Current
            {
                get
                {
                    return Current;
                }
            }

            public bool MoveNext()
            {
                return ChildrenEnumerator.MoveNext();
            }

            public void Reset()
            {
                ChildrenEnumerator.Reset();
            }

            public NodeEnumerator(Node boss)
            {
                Boss = boss;
                ChildrenEnumerator = boss._children.GetEnumerator();
            }




            #region IDisposable Members

            public void Dispose()
            {
                if (ChildrenEnumerator != null)
                {
                    ChildrenEnumerator.Dispose();
                    ChildrenEnumerator = null;
                }

            }

            #endregion
        }


        public IEnumerator<Node> GetEnumerator()
        {
            return new NodeEnumerator(this);
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new NodeEnumerator(this);
        }

        
        /// <summary>
        /// for xml serialization...
        /// </summary>
        /// <param name="value"></param>
        public void Add(object value)
        {
            Node node = value as Node;
            if (node != null)
                Add(node);
        }
        


        #endregion

        #region Serialization


        /// <summary>
        /// Write internal data of the node to XML, without subnodes (called from WriteXML)
        /// </summary>
        /// <param name="writer">XML Writer</param>
        public virtual void WriteInternalData(System.Xml.XmlWriter writer)
        {
            writer.WriteAttributeString("id", this.Name);

            writer.WriteElementString("Visible", this._visible.ToString());
            writer.WriteElementString("VisibleHierarchy", this.VisibleHierarchy.ToString());
            writer.WriteElementString("StaticHierarchy", this.StaticHierarchy.ToString());
            writer.WriteElementString("Pickable", this.Pickable.ToString());
            writer.WriteElementString("RenderFlags", this.RenderFlags.ToString());

            if (this.Trans != null)
            {
                writer.WriteStartElement("Transformation");
                Trans.WriteXml(writer);
                
                writer.WriteEndElement();
            }
        }

        /// <summary>
        /// Reads one item of internal data. Should return true if recognized value (for overloading)
        /// </summary>
        /// <param name="reader">Reader to read from</param>
        /// <returns>True if value was handled, false if not</returns>
        public virtual bool ReadInternalData(System.Xml.XmlReader reader)
        {
            switch (reader.Name)
            {
                case "Visible":
                    Visible = XMLHelper.ReadAsBool(reader);
                    break;
                case "VisibleHierarchy":
                    VisibleHierarchy = XMLHelper.ReadAsBool(reader);
                    break;
                case "StaticHierarchy":
                    StaticHierarchy = XMLHelper.ReadAsBool(reader);
                    break;
                case "Transformation":
                    Trans = TransformationFactory.ReadFromXML(reader);
                    break;
                case "Pickable":
                    Pickable = XMLHelper.ReadAsBool(reader);
                    break;
                case "RenderFlags":
                    Enum.TryParse<RenderFlags>(reader.ReadString(), out _RenderFlags);
                    break;
                default:
                    return false;
            }
            return true;
        }
        
        protected virtual void CopyData(Node targetNode)
        {
            targetNode._Name = this._Name;
            if (this._Trans != null)
            {
                //targetNode._Trans
            }
            else targetNode._Trans = null;
            targetNode.Visible = this.Visible;
            targetNode.VisibleHierarchy = this.VisibleHierarchy;
            targetNode.StaticHierarchy = this.StaticHierarchy;
            targetNode.Pickable = this.Pickable;
            targetNode.DontSave = this.DontSave;
        }


        #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 virtual void ReadXml(System.Xml.XmlReader reader)
        {
            _Name = reader["id"];
            while (reader.Read())
            {
                if (reader.IsStartElement())
                {
                    if (reader.Name == "Nodes")
                    {
                        ReadSubNodes(reader);
                    }
                    else
                    {
                        if (!ReadInternalData(reader))
                        {
                            Console.DebugLine("Node import failure on " + reader.Name);
                            reader.Skip();
                            //reader.ReadEndElement();
                        }
                    }
                }
                else 
                    return;
            }
        }

        protected virtual void ReadSubNodes(System.Xml.XmlReader reader)
        {
            if (reader.IsEmptyElement)
                return;
            while (reader.Read())
            {
                if (reader.IsStartElement())
                {
                    Node node = NodeFactory.ReadXml(reader);
                    if (node != null)
                        AddNode(node, node.Name);
                    else
                    {
                        Console.WriteLine("SubNode loading failed!");
                    }
                }
                else return;
            }
        }

        /// <summary>
        /// Serializes object to XML
        /// </summary>
        /// <param name="writer">XML writer to save values</param>
        public virtual void WriteXml(System.Xml.XmlWriter writer)
        {
            //writer.WriteStartElement(this.GetType().Name);    //done by caller

            WriteInternalData(writer);
            
            if (_children.Count > 0)
            {

                writer.WriteStartElement("Nodes");

                foreach (var kvp in _children)
                {
                    if (!kvp.Value.DontSave)
                    {
                        string name = kvp.Value.XMLNodeName;

                        writer.WriteStartElement(name);

                        kvp.Value.WriteXml(writer);

                        writer.WriteEndElement();
                    }
                }

                writer.WriteEndElement();
            }

            //writer.WriteEndElement(); //done by caller
        }



        /// <summary>
        /// XML name
        /// </summary>
        [Description("Name used for saving XML")]
        [Category("Generic data")]
        public virtual string XMLNodeName
        {
            get
            {
                return this.GetType().Name;
            }
        }

        #endregion
        

        #endregion

        public override string ToString()
        {
            return string.Format("{0} ({1})", Name, XMLNodeName);
        }

    }



}
