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

namespace VisimBox.Scene
{
    /// <summary>
    /// Scene root node, scene manager for scenes
    /// </summary>
    public class SceneRoot : Node
    {
        #region Fields

        /// <summary>
        /// Filename of the string
        /// </summary>

        [Description("File to be saved / reloaded from")]
        [Category("Scene data")]
        public string FileName { get; set; }
        /// <summary>
        /// Name of the scene
        /// </summary>
        [Description("Name of the scene")]
        [Category("Scene data")]
        public string SceneName { get; set; }
        /// <summary>
        /// Author of the scene
        /// </summary>
        [Description("Author of the scene")]
        [Category("Scene data")]
        public string Author { get; set; }
        /// <summary>
        /// Version
        /// </summary>
        [Description("Version text")]
        [Category("Scene data")]
        public string Version { get; set; }
        /// <summary>
        /// Creation date
        /// </summary>
        [Description("Date of creation")]
        [Category("Scene data")]
        public DateTime CreationDate { get; set; }
        /// <summary>
        /// Last saved update
        /// </summary>
        [Description("Last time save")]
        [Category("Scene data")]
        public DateTime LastUpdate { get { return _LastUpdate; } }

        protected DateTime _LastUpdate;

        /// <summary>
        /// Main camera of the scene
        /// </summary>
        [Description("Main camera of the scene")]
        [Category("Scene data")]
        public ICamera MainCamera { get; set; }

        Physics.PhysicalScene _PhysScene = new Physics.PhysicalScene();

        /// <summary>
        /// Physical scene associated to scene
        /// </summary>
        [Description("Physical scene associated to scene")]
        [Category("Scene data")]
        [TypeConverter(typeof(ExpandableObjectConverter))]
        public Physics.PhysicalScene PhysScene
        {
            get
            {
                return _PhysScene;
            }
        }


        #endregion

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


        public SceneRoot()
        {
            _LastUpdate = CreationDate = DateTime.Now;            
        }

        public SceneRoot(XmlReader reader)
            : base(reader)
        {
        }

        /// <summary>
        /// Render scene
        /// </summary>
        public void RenderScene(RendererEventArgs args)
        {
            args.RC.RenderNodes(this);
        }

        /// <summary>
        /// Performs simple and fast physical collision check for selected rigid body, does not update position change, just collision check
        /// </summary>
        /// <param name="node">Node to perform check</param>
        /// <returns>True if node collides on current position</returns>
        public bool CheckPhysicalCollision(Physics.RigidBody node)
        {
            return _PhysScene.CheckRigidBodyCollision(node);
        }

        public override void AddNode(Node node, string name = "subnode")
        {
            base.AddNode(node, name);
            CheckNodeAdded(node);
        }

        public override void OnNodeAdded(Node tonode, Node newchild)
        {
            base.OnNodeAdded(tonode, newchild);
            //if new child is rigid body or static collision, tell it to the physical scene
            //same for any children of added node
            CheckNodeAdded(newchild);
        }

        public override void OnNodeRemoved(Node fromnode, Node removed)
        {
            base.OnNodeRemoved(fromnode, removed);
            //if new child is rigid body or static collision, tell it to the physical scene
            //same for any children of added node
            CheckNodeRemoved(removed);
        }

        protected void CheckNodeRemoved(Node newnode)
        {
            Physics.RigidBody rb = newnode as Physics.RigidBody;
            if (rb != null)
                PhysScene.UnregisterBody(rb);

            Physics.StaticCollision sc = newnode as Physics.StaticCollision;
            if (sc != null)
                PhysScene.UnRegisterStaticCol(sc);

            foreach (Node node in newnode)
            {
                CheckNodeAdded(node);
            }
        }


        protected void CheckNodeAdded(Node newnode)
        {
            Physics.RigidBody rb = newnode as Physics.RigidBody;
            if (rb != null)
                PhysScene.RegisterBody(rb);

            Physics.StaticCollision sc = newnode as Physics.StaticCollision;
            if (sc != null)
                PhysScene.RegisterStaticCol(sc);

            foreach (Node node in newnode)
            {
                CheckNodeAdded(node);
            }
        }

        public override bool TimeTick(double time)
        {
            base.TimeTick(time);

            PhysScene.StartFrame();
            PhysScene.TimeTick((float)time);
            return true;
        }

        #region File operations

        public void Clear()
        {
            _children.Clear();
            _PhysScene.Clear();
            _PhysScene = new Physics.PhysicalScene();
        }

        /// <summary>
        /// Clear scene and load new one
        /// </summary>
        /// <param name="filename">Filename of the scene</param>
        /// <param name="Format">required scene format</param>
        /// <returns>True if everything went ok</returns>
        public bool LoadScene(string filename, string Format = "VSB")
        {
            if (Format != "VSB")
                throw new ArgumentException("Load scene: Other format then VSB not implemented!");

            Clear();

            System.IO.TextReader tr;

            try
            {
                tr = new System.IO.StreamReader(filename);
            }
            catch
            {
                Console.DebugLine("Load scene failed, cannot load file " + filename);
                return false;
            }

            FileName = filename;

            XmlTextReader reader = new XmlTextReader(tr);

            reader.Read();  //document start

            bool rval = true;


            if (reader.IsStartElement("VisimBoxScene"))
            {
                while (reader.Read())
                {
                    if (reader.IsStartElement())
                    {
                        if (reader.Name == "MaterialLibrary")
                        {
                            VisimEngine.Instance.Renderer.MaterialMan.LoadLibrary(reader, true);
                        }
                        else if (reader.Name == "SceneRoot")
                        {
                            ReadXml(reader);
                        }
                        else if (reader.Name == "VisimBoxScene")
                        {
                        }
                        else
                        {
                            Console.DebugLine("Ignored - Unknown type of root data " + reader.Name + " in file " + filename);
                        }
                    }
                }
            }
            else 
            {
                Console.DebugLine("Not a VisimBoxScene!");
                rval = false;
            }

            reader.Close();
            tr.Close();
            return rval;
        }

        /// <summary>
        /// Reloads scene from the disk
        /// </summary>
        /// <returns>True if everything went ok</returns>
        public bool ReloadScene()
        {
            return LoadScene(FileName);
        }

        /// <summary>
        /// Saves scene to disk using used name (will overwrite the file)
        /// </summary>
        /// <returns>True if save sucessful</returns>
        public bool SaveScene()
        {
            return SaveScene(FileName);
        }

        /// <summary>
        /// Save scene to disk
        /// </summary>
        /// <param name="filename">Filename of the scene</param>
        /// <param name="Format">required scene format</param>
        /// <returns>True if everything went ok</returns>
        public bool SaveScene(string filename, string Format = "VSB")
        {
            if (Format != "VSB")
                throw new ArgumentException("Scene Save: Other format then VSB not implemented!");

            if (filename == null && FileName == null)
                FileName = "Visimbox.VSB";
            else if (filename != null)
                FileName = filename;




            System.IO.TextWriter tw;

            try
            {
                tw = new System.IO.StreamWriter(FileName);
            }
            catch
            {
                Console.WriteLine("Save scene failed, can't create file " + FileName + "!");
                return false;
            }

            _LastUpdate = DateTime.Now;

            System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(tw);
            writer.WriteStartDocument();
            writer.Formatting = System.Xml.Formatting.Indented;

            writer.WriteStartElement("VisimBoxScene");
            
            //write material library

            var matman = VisimEngine.Instance.Renderer.MaterialMan.SaveLibrary(writer, false);

            //write nodes

            writer.WriteStartElement(this.GetType().Name);

            WriteXml(writer);

            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.WriteEndDocument();

            Console.WriteLine("Scene saved");

            tw.Close();
            return true;
        }

        public override void WriteInternalData(System.Xml.XmlWriter writer)
        {
            base.WriteInternalData(writer); //??? not needed?

            writer.WriteElementString("FileName", FileName);
            if (SceneName != null)
                writer.WriteElementString("SceneName", SceneName);
            if (Author != null)
                writer.WriteElementString("Author", Author);
            if (Version != null)
                writer.WriteElementString("Version", Version);

            writer.WriteElementString("CreationDate", CreationDate.ToString(VisiMath.XMLHelper.CF));
            writer.WriteElementString("LastUpdate", CreationDate.ToString(VisiMath.XMLHelper.CF));
        }

        public override Node CreateCopy()
        {
            Console.DebugLine("Cannot create whole copy of the scene!");
            return null;
        }

        public override bool ReadInternalData(XmlReader reader)
        {
            if (base.ReadInternalData(reader))
                return true;
            switch (reader.Name)
            {
                case "FileName":
                    FileName = reader.ReadString();
                    break;
                case "SceneName":
                    SceneName = reader.ReadString();
                    break;
                case "Author":
                    Author = reader.ReadString();
                    break;
                case "Version":
                    Version = reader.ReadString();
                    break;
                case "CreationDate":
                    CreationDate = Convert.ToDateTime(reader.ReadString(), VisiMath.XMLHelper.CF);
                    break;
                case "LastUpdate":
                    _LastUpdate = Convert.ToDateTime(reader.ReadString(), VisiMath.XMLHelper.CF);
                    break;
                default:
                    return false;
            }
            return true;
        }

#endregion
    }
}
