//BSD License  
//DXGfx® - http://www.eteractions.com
//Copyright (c) 2005
//by Guillaume Randon
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software
//and associated documentation files (the "Software"), to deal in the Software without restriction,
//including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
//and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all copies or substantial
//portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
//AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
//DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.Samples.DirectX.UtilityToolkit;

namespace DXGfxLib
{
    public class SceneObject : SceneNode, IDisposable, IComparable, IDrawable, IAmScriptable
    {
        /// <summary>
        /// I want to keep track of whether the object was clicked or not. I am going to use this
        /// boolean for that purpose.
        /// </summary>
        private bool hasBeenClicked = false;

        /// <summary>
        /// Accessor for cliked bolean. It is useful to access it through an accessor so if in the future
        /// we want to trigger some custom action from here it will be possible.
        /// </summary>
        public bool HasBeenClicked
        {
            get { return hasBeenClicked; }
            set { hasBeenClicked = value; }
        }

        /// <summary>
        /// I want to keep track of whether this SceneObject is colliding with something or not.
        /// Typically when I test for collision I will update these information and clean them again
        /// as soon as the collision has been handled.
        /// </summary>
        private bool colliding = false;

        /// <summary>
        /// We also want to keep track of which sceneNode we are colliding with. See colliding boolean
        /// to see further description of how these information can be used.
        /// </summary>
        private SceneObject collidingObject = null;

        /// <summary>
        /// In case we want to draw the bouding box this will be useful.
        /// </summary>
        protected DrawableBBox drawBox;

        /// <summary>
        /// Should be set to true is we want to draw the bounding box!
        /// </summary>
        protected bool drawBoundingBox = false;

        /// <summary>
        /// Accessors to state whether you want to display bounding box or not!
        /// </summary>
        public bool DrawBoundingBox
        {
            get { return drawBoundingBox; }
            set { drawBoundingBox = value; }
        }

        /// <summary>
        /// This boolean indicates if the scene object want to be notified when the user click on it!
        /// </summary>
        protected bool canBeClicked = false;

        /// <summary>
        /// This boolean indicates if the scene object want to be notified when the user click on it!
        /// </summary>
        public bool CanBeClicked
        {
            get { return canBeClicked; }
            set { canBeClicked = value; }
        }

        /// <summary>
        /// This boolean indicates if this scene object should be taken into account when checking for collisions
        /// between scene objects or if it is some sort of ghost that doesn't even want to know that somehting is going
        /// through it:).
        /// </summary>
        protected bool checkForCollision = false;

        /// <summary>
        /// This boolean indicates if this scene object should be taken into account when checking for collisions
        /// between scene objects or if it is some sort of ghost that doesn't even want to know that somehting is going
        /// through it:).
        /// </summary>
        public bool CheckForCollision
        {
            get { return checkForCollision; }
            set { checkForCollision = value; }
        }

        /// <summary>
        /// This boolean indicates if the base scene object code should automatically take care of not having this scene object
        /// interpenatre another one. If true this clearly indicates that this is a solid object. In both situations as soon
        /// as checkForCollision is true, the object will be notified of the collision! If checkForCollision is false there is
        /// no use setting this to true as the collision situation won't even be detected.
        /// </summary>
        protected bool solid = false;

        /// <summary>
        /// This boolean indicates if the base scene object code should automatically take care of not having this scene object
        /// interpenatre another one. If true this clearly indicates that this is a solid object. In both situations as soon
        /// as checkForCollision is true, the object will be notified of the collision! If checkForCollision is false there is
        /// no use setting this to true as the collision situation won't even be detected.
        /// </summary>
        public bool Solid
        {
            get { return solid; }
            set { solid = value; }
        }

        /// <summary>
        /// Each scene object now keep tracks of the fileName from which it has been built.
        /// This is important mostly when the library is used in conjunction with the editor. It does ease a lot of things.
        /// </summary>
        public string fileName;

        //[Editor(typeof(FileNameEditor), typeof(UITypeEditor))]
        public string Name
        {
            get { return fileName; }
        }

        /// <summary>
        /// Each SceneObject will have an associated effect group containing the effectst that will be used to render it
        /// in various situation.
        /// </summary>
        protected EffectGroup associatedEffectGroup = null;

        /// <summary>
        /// Associated effect group accessor.
        /// </summary>
        [System.ComponentModel.BrowsableAttribute(false)]
        public EffectGroup AssociatedEffectGroup
        {
            get { return associatedEffectGroup; }
            set { associatedEffectGroup = value; }
        }

        /// <summary>
        /// A boolean which allow use to specific if the SceneObject should be displayed or not.
        /// </summary>
        protected bool hide = false;

        /// <summary>
        /// Accessor for hide boolean. When true the scene  object won't be rendered.
        /// </summary>
        public bool Hide
        {
            get { return hide; }
            set { hide = value; }
        }


        /// <summary>
        /// A scene Object is a scene node with geometric information associated to it. Scene object should be able to know
        /// what is bounding box is in its local coordinate system. That information is going to be used in the update phase
        /// to calculate the bounding box in world coordinate system and then to determine whether or not this scene object need
        /// to be rendered or not.
        /// </summary>
        public BBox localBoundingBox = null;

        /// <summary>
        /// Default constructor. Build a default bounding box, a very smal and tiny one centered on the origin of the local
        /// coordinate system. Thus by default without any further operation this scene object might be added to the list of
        /// objects to be rendered if the position of the scene object is in the viewing frustrum.
        /// </summary>
        public SceneObject()
        {
            Initialize();
        }

        public override void Initialize()
        {
            base.Initialize();

            localBoundingBox = new BBox();

            associatedEffectGroup = DXGfxManager.GetGlobalInstance().defaultEffectGroup;

            events = new List<Event>();
        }

        /// <summary>
        /// Most SceneObjects don't need to Dispose any ressource. In fact most of the time DX ressources
        /// are only attached to them, the scene object itself does not have the responsability to load
        /// or dispose them, we leave that responsability to the code which has loaded the ressource and
        /// attached it to a scene object. The only exception to this are terrain tiles, these are scene
        /// objects very specific and create there own vertex, index buffer from the information provided
        /// on the terrain creation. They need to dispose this (along with the textures loaded and everything).
        /// </summary>
        public override void Dispose()
        {
        }

        /// <summary>
        /// For loading a scene from the XML file this method is pretty useful as we just get the filename
        /// from the file, create an instance of the desired type and instruct that instance to populate
        /// its internal state from the specified file.
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="fileName"></param>
        public virtual void LoadFromFile(Device d3ddevice, string fileName)
        {
        }

        /// <summary>
        /// The update method does pretty much what it does for a scene node except that here we also use the local bbox to calculate
        /// the bounding box.
        /// </summary>
        /// <param name="frustrum"></param>
        /// <param name="appTime"></param>
        /// <param name="elapsedTime"></param>
        public override void Update(Frustrum frustrum, double appTime, float elapsedTime)
        {
            if (moved)
            {
                worldAABBox = new AABBox(BBox.Transform(localBoundingBox, WorldMat));
                moved = false;
            }

            foreach (SceneNode child in children)
            {
                child.Update(frustrum, appTime, elapsedTime);
                worldAABBox.BoxFromBoxes(child.worldAABBox);
            }
        }

        /// <summary>
        /// When a scene is updated it will first call the Update method on the node hierarchy and then it
        /// will call the AttachForRendering method on the node hierarchy. This will give an opportunity to each
        /// scene object (or any instance of a derived class) to decide whether it needs to be rendered this frame or not.
        /// </summary>
        /// <param name="frustrum"></param>
        /// <param name="objectsToBeRendered"></param>
        public override void AttachForRendering(Frustrum frustrum, List<IDrawable> objectsToBeRendered)
        {
            // If we are in the frustrum we attach for rendering an let's our children decide for
            // themselves.
            if (useFrustrumCulling)
            {
                if (!frustrum.IsInFrustrum(worldAABBox))
                    return;
            }

            objectsToBeRendered.Add(this);

            foreach (SceneNode child in children)
            {
                child.AttachForRendering(frustrum, objectsToBeRendered);
            }
        }

        /// <summary>
        /// This will calculate a local bounding box based on associated geometry.
        /// </summary>
        /// <param name="meshToUse"></param>
        /// <returns></returns>
        public BBox CalculateLocalBBox(Mesh meshToUse)
        {
            return AABBox.CalculateLocalBBox(meshToUse);
        }

        /// <summary>
        /// Use this method when you want to cut the association between a SceneObject and anything that
        /// might have been associated to it.
        /// </summary>
        public override void DetachAll()
        {
            base.DetachAll();
        }

        public virtual void SetEffectsValues(Device d3ddevice, Effect eff, int pass)
        {
        }

        /// <summary>
        /// In its draw method the scene will call the Draw method on all scene object (or derived class instances) that
        /// have registered to be rendered in the AttachForRendering method.
        /// </summary>
        /// <param name="device"></param>
        public virtual void Draw(Device device)
        {
            device.SetTransform(TransformType.World, worldMat);

            if (drawBoundingBox)
            {
                if (drawBox == null)
                {
                    drawBox = new DrawableBBox(device, localBoundingBox);
                }

                drawBox.Draw(device);
            }
        }

        /// <summary>
        /// This method can be used to decide whether a ray intersect with the scene object or not. RayPos and RayDir have to
        /// be given in world space coordinates. They will be transform to local coordinates to perform the testing by the
        /// the method itself. This method return IntersectInformation on the closest hit to the RayPos.
        /// </summary>
        /// <param name="rayPos"></param>
        /// <param name="rayDir"></param>
        /// <param name="closestHit"></param>
        /// <returns></returns>
        public virtual bool Intersect(Vector3 rayPos, Vector3 rayDir, out IntersectInformation closestHit)
        {
            closestHit = new IntersectInformation() ;
            return false;
        }

        /// <summary>
        /// This method is somehow similar to the previous one except that instead of returning the closest hit information
        /// it does return a list of colliding objects.
        /// </summary>
        /// <param name="rayOri"></param>
        /// <param name="rayDir"></param>
        /// <param name="collidingSceneObjects"></param>
        /// <returns></returns>
        public override bool Intersect(Vector3 rayOri, Vector3 rayDir, ref List<SceneObject> collidingSceneObjects)
        {
            return false;
        }

        /// <summary>
        /// This method is somehow similar to the previous one except that you can specify here the desired type of the 
        /// scene objects you will get back in the list of objects colliding with the ray.
        /// </summary>
        /// <param name="rayOri"></param>
        /// <param name="rayDir"></param>
        /// <param name="collidingSceneObjects"></param>
        /// <param name="desiredType"></param>
        /// <returns></returns>
        public override bool Intersect(Vector3 rayOri, Vector3 rayDir, ref List<SceneObject> collidingSceneObjects, System.Type desiredType)
        {
            return false;
        }

        /// <summary>
        /// Each scene object has an associated effect. We want SceneObject to implement the CompareTo method, thus when they
        /// will be added to the sorted list of objects to be rendered, objects are going to be ordered based on the effect filename
        /// they use.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public virtual int CompareTo(Object obj)
        {
            int ret = 1;
            IDrawable drawable = obj as IDrawable;

            if (drawable != null)
            {
                ret = AssociatedEffectGroup.CompareTo(drawable.AssociatedEffectGroup);
            }
            return ret;
        }

        /// <summary>
        /// Each scene object has an associated effect. We want SceneObject to implement the CompareTo method, thus when they
        /// will be added to the sorted list of objects to be rendered, objects are going to be ordered based on the effect filename
        /// they use.
        /// </summary>
        /// <param name="sceneObj"></param>
        /// <returns></returns>
        public virtual int CompareTo(SceneObject sceneObj)
        {
            return AssociatedEffectGroup.CompareTo(sceneObj.AssociatedEffectGroup);
        }

        /// <summary>
        /// Each scene object has an associated effect. We want SceneObject to implement the CompareTo method, thus when they
        /// will be added to the sorted list of objects to be rendered, objects are going to be ordered based on the effect filename
        /// they use.
        /// </summary>
        /// <param name="sceneObj"></param>
        /// <returns></returns>
        public virtual int CompareTo(IDrawable sceneObj)
        {
            return AssociatedEffectGroup.CompareTo(sceneObj.AssociatedEffectGroup);
        }

        /// <summary>
        /// We need to always have a valid position, so we keep the last known valid position here.
        /// </summary>
        protected Matrix lastValidWorldMat = Matrix.Identity;

        /// <summary>
        /// We want to override this because we want to perform collision detection at some point.
        /// Doing this in the OnMove method is interesting this means that we will go trhough this method for all
        /// move of the scene object, then we have the opportunity to do what we want 'need' based on the checkForCollision
        /// and doNotInterpenatre booleans.
        /// 
        /// If a collision is detected and that this object is marked as doNotInterpenatre, then the we will revert it's
        /// position to its last known valid position.
        /// </summary>
        public override void OnMove()
        {
            base.OnMove();

            if (checkForCollision)
            {
                List<SceneObject> collidingObjects = new List<SceneObject>();

                if (scene.CollideSolid(this, ref collidingObjects))
                {
                    double appTime = FrameworkTimer.GetTime();
                    float elapsedTime = (float)FrameworkTimer.GetElapsedTime();

                    foreach (SceneObject sceneObj in collidingObjects)
                    {
                        Collision(appTime, elapsedTime, sceneObj);
                        sceneObj.Collision(appTime, elapsedTime, this);
                    }

                    if (solid)
                    {
                        MathUtil.CopyMatrixValues(worldMat, lastValidWorldMat);
                    }
                    else
                    {
                        MathUtil.CopyMatrixValues(lastValidWorldMat, worldMat);
                    }
                }
                else
                {
                    MathUtil.CopyMatrixValues(lastValidWorldMat, worldMat);
                }
            }
            else
            {
                MathUtil.CopyMatrixValues(lastValidWorldMat, worldMat);
            }
        }

        /// <summary>
        /// Each scene node can now have a script attached to it. We keep a ref to the instance of the Script class in this
        /// member variable.
        /// </summary>
        internal Script script = null;

        /// <summary>
        /// Accessor for the script attached to this scene node.
        /// </summary>
        internal Script OurScript
        {
            get { return script; }
        }

        /// <summary>
        /// Each scene node also has a timeline attached to it. This is where we keep a ref to the instance of the Timeline
        /// class associated with this node.
        /// </summary>
        protected TimeLine timeline = null;

        /// <summary>
        /// THis method will use the Script instance passed as parameter and will set as the Script associated with this scene node.
        /// </summary>
        /// <param name="script"></param>
        public void SetScript(Script script)
        {
            this.script = script;
            this.script.SetParent(this);
            this.script.SetScene(scene);
        }

        /// <summary>
        /// This method will return the Script instance associated with the scene node.
        /// </summary>
        /// <returns></returns>
        public Script GetScript()
        {
            if (script == null)
            {
                SetScript(new Script());
            }
            return script;
        }

        /// <summary>
        /// Same as SetScript but for timelines.
        /// </summary>
        /// <param name="timeLine"></param>
        public void SetTimeLine(TimeLine timeLine)
        {
            this.timeline = timeLine;
            this.timeline.SetParent(this);
            this.timeline.SetScene(scene);
        }

        /// <summary>
        /// Will return the instance of the TimeLine class associated with this scene node.
        /// </summary>
        /// <returns></returns>
        public TimeLine GetTimeLine()
        {
            if (timeline == null)
            {
                SetTimeLine(new TimeLine());
            }
            return timeline;
        }

        /// <summary>
        /// The evaluate method will ask the script and the timeline to evaluate themselves. This is a form of update but the work is different from
        /// what is performed in the update method. The update method will update geometric information (bounding boxes....).
        /// The evaluate method will 'update' the node as far as the logic (script and timeline) associated with it is concerned.
        /// This method is marked virtual because you could create classes deriving from SceneNode that will implement their own
        /// logic through different means than the script and the timeline. Then in these derive classes you will have the opportunity
        /// to update the node logic and values through this method.
        /// </summary>
        /// <param name="appTime"></param>
        /// <param name="elapsedTime"></param>
        public virtual void Evaluate(double appTime, float elapsedTime)
        {
            if (timeline != null)
            {
                timeline.Evaluate(appTime, elapsedTime);
            }

            if (script == null)
            {
                return;
            }


            // Also here we should check a flag to know whether we have been clicked or not and if we have
            // we should also trigger the appropriate scripts !
            if(hasBeenClicked)
            {
                script.Clicked(appTime, elapsedTime);
            }
            // Ok Now check for collision and execute appropriate script if there are any collision.
            //if (colliding)
            //{
            //    if (collidingObject != null)
            //    {
            //        script.Collision(appTime, elapsedTime, collidingObject);
            //    }
            //}

            script.Evaluate(appTime, elapsedTime);
        }

        /// <summary>
        /// Scripts and timeline have reset methods. They will be called when the simulation is reseted (stoped and restarted from
        /// the beginning).
        /// </summary>
        public void Reset()
        {
            if (script != null)
            {
                script.Reset();
            }
            if (timeline != null)
            {
                timeline.Reset();
            }
        }


        /// <summary>
        /// A list of event associated with the scene node. You can post events to either the scene to a specific node. This
        /// will be done through the AddEvent method on the scene rootNode or on any node of the scene graph.
        /// This is where the events will be added. Also when you remove them they will be removed from the list.
        /// </summary>
        private List<Event> events = null;

        /// <summary>
        /// This method will add events to the list of events of our scene node.
        /// </summary>
        /// <param name="evt"></param>
        public void AddEvent(Event evt)
        {
            events.Add(evt);
        }

        /// <summary>
        /// This method is the pendant of the RemoveEvent.
        /// </summary>
        /// <param name="evt"></param>
        public void RemoveEvent(Event evt)
        {
            events.Remove(evt);
        }

        /// <summary>
        /// This method should be overriden in classes deriving from this one. It could possible be used to list
        /// all the files you need to move in you decide to change location of the main file associated with this
        /// scene object.
        /// </summary>
        /// <returns></returns>
        public virtual string[] ListFileRessources()
        {
            // By default return fileName and the effect files associated with the effectGroup
            string[] list = new string[2];

            list[0] = fileName;
            list[1] = associatedEffectGroup.DefaultFile;

            return list;
        }

        /// <summary>
        /// This method will be called on the Scene object if canBeClicked is true and if someone click on this object!
        /// </summary>
        /// <param name="appTime"></param>
        /// <param name="elapsedTime"></param>
        public virtual void Clicked(double appTime, float elapsedTime)
        {
            if (script != null)
            {
                script.Clicked(appTime, elapsedTime);
            }
        }

        /// <summary>
        /// This method will be called if another scene object collides with this one and checkForCollision is true.
        /// TODO: Add code to avoid sceneobject to interpenatre !
        /// </summary>
        /// <param name="appTime"></param>
        /// <param name="elapsedTime"></param>
        /// <param name="collidingObject"></param>
        public virtual void Collision(double appTime, float elapsedTime, SceneObject collidingObject)
        {
            if (script != null)
            {
                script.Collision(appTime, elapsedTime, collidingObject);
            }
        }

        /// <summary>
        /// Return an Array with all fired events currently associated with the scriptable object and removes the association with
        /// the object.
        /// 
        /// Just as a reminder this is the life of events:
        /// Events are added to the scriptable object timeline.
        /// At the appropriate time they are fired, I mean associated with the object (aka added to a specific event collection).
        /// Then they remain associated with the object until some code removes them from there!
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Event[] popEventsByName(string name)
        {
            List<Event> ret = new List<Event>();

            foreach (Event evt in events)
            {
                if (evt.name.Equals(name))
                {
                    ret.Add(evt);
                    events.Remove(evt);
                }
            }

            return ret.ToArray();
        }

        /// <summary>
        /// Return an Array with all fired events currently associated with the scriptable object and removes the association
        /// with the object.
        /// 
        /// Just as a reminder this is the life of events:
        /// Events are added to the scriptable object timeline.
        /// At the appropriate time they are fired, I mean associated with the object (aka added to a specific event collection).
        /// Then they remain associated with the object until some code removes them from there!
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public Event[] popEventsByType(string type)
        {
            List<Event> ret = new List<Event>();

            foreach (Event evt in events)
            {
                if (evt.type.Equals(type))
                {
                    ret.Add(evt);
                    events.Remove(evt);
                }
            }

            return ret.ToArray();
        }

        /// <summary>
        /// Like popEventsByName except that the events will remain associated with the object until some other code
        /// removes that association. It is a bit like the events remain 'fired' for further consumtion.
        /// 
        /// Just as a reminder this is the life of events:
        /// Events are added to the scriptable object timeline.
        /// At the appropriate time they are fired, I mean associated with the object (aka added to a specific event collection).
        /// Then they remain associated with the object until some code removes them from there!
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Event[] peekEventsByName(string name)
        {
            List<Event> ret = new List<Event>();

            foreach (Event evt in events)
            {
                if (evt.name.Equals(name))
                {
                    ret.Add(evt);
                }
            }

            return ret.ToArray();
        }

        /// <summary>
        /// Like popEventsByType except that the events will remain associated with the object until some other code
        /// removes that association. It is a bit like the events remain 'fired' for further consumtion.
        /// 
        /// Just as a reminder this is the life of events:
        /// Events are added to the scriptable object timeline.
        /// At the appropriate time they are fired, I mean associated with the object (aka added to a specific event collection).
        /// Then they remain associated with the object until some code removes them from there!
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Event[] peekEventsByType(string type)
        {
            List<Event> ret = new List<Event>();

            foreach (Event evt in events)
            {
                if (evt.type.Equals(type))
                {
                    ret.Add(evt);
                }
            }

            return ret.ToArray();
        }
    }
}
