//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;

namespace DXGfxLib
{
    /// <summary>
    /// This class is the base class of the classes that are going to be created on the fly by the Script object from the
    /// source code in skeleton, varSrc and codeSrc
    /// </summary>
    public class ExecutableBlock
    {
        /// <summary>
        /// The scene node on which the ExecutableBlock can perform operations.
        /// </summary>
        public SceneNode parent = null;

        /// <summary>
        /// The scene we are part of. Scripts can query the scene to get various information including references to other
        /// nodes.
        /// </summary>
        public Scene scene = null;

        /// <summary>
        /// It's good to have a Dispose method, although I must confess this one is totally empty at least for now.
        /// </summary>
        public void Dispose()
        {
        }

        /// <summary>
        /// Good this is the method that will be overriden in the classes deriving from the one that will be created on the fly
        /// to allow execution of the code n our script.
        /// </summary>
        /// <param name="scene"></param>
        /// <param name="parent"></param>
        /// <param name="appTime"></param>
        /// <param name="elapsedTime"></param>
        public virtual void Evaluate(Scene scene, SceneNode parent, double appTime, float elapsedTime)
        {
        }

        /// <summary>
        /// Good this is the method that will be overriden in the classes deriving from the one that will be created on the fly
        /// to allow execution of the code n our script.
        /// </summary>
        /// <param name="scene"></param>
        /// <param name="parent"></param>
        /// <param name="appTime"></param>
        /// <param name="elapsedTime"></param>
        public virtual void Click(Scene scene, SceneNode parent, double appTime, float elapsedTime)
        {
        }

        /// <summary>
        /// Good this is the method that will be overriden in the classes deriving from the one that will be created on the fly
        /// to allow execution of the code n our script.
        /// </summary>
        /// <param name="scene"></param>
        /// <param name="parent"></param>
        /// <param name="appTime"></param>
        /// <param name="elapsedTime"></param>
        /// <param name="collidingObject"></param>
        public virtual void Collision(Scene scene, SceneNode parent, double appTime, float elapsedTime, SceneObject collidingObject)
        {
        }
    }

    /// <summary>
    /// The script class. This class contains the logic to build 
    /// </summary>
    public class Script
    {
        /// <summary>
        /// The scene node to which this script is attached.
        /// </summary>
        private SceneObject parent = null;

        /// <summary>
        /// The scene we are part of.
        /// </summary>
        private Scene scene = null;

        /// <summary>
        /// Skeleton for the source code of the class deriving from Executable block that will be created on the fly.
        /// </summary>
        private string skeleton;

        /// <summary>
        /// At some point it is desirable to persist the script to a *.cs file (at the moment scripts are only C# though this
        /// might be easily changed).
        /// </summary>
        public string fileName = "";

        /// <summary>
        /// Script name.
        /// </summary>
        public string name = "";

        /// <summary>
        /// This string will contain all variables definition. Aka all the variables we want to live through all the simualtion
        /// time and keep their value between evaluations of the script.
        /// </summary>
        private string varSrc = "// If you want to add variables that will retain their values until the script is reseted please define them below:\n";

        public string VarSrc
        {
            get { return GetVarSrc(); }
            set { UpdateVarSrc(value); }
        }

        /// <summary>
        /// The code of the script. This will go directly in the Evaluate method of the class deriving from Executable block
        /// that will be created on the fly. Maybe at the moment this does limit a bit what can be done through script so I should
        /// soon add a way which let the users to fully edit the code of class deriving from ExecutableBlock. 
        /// </summary>
        private string codeSrc = "// Please write your script code below. You can reference the variables you have defined and also the 'parent' (the SceneNode object to which this script is attached) and the 'scene' (the Scene object to which the parent is attached)";

        public string CodeSrc
        {
            get { return GetCodeSrc(); }
            set { UpdateCodeSrc(value); }
        }

        /// <summary>
        /// The script in that string will be evaluated if the associated object is clicked.
        /// </summary>
        private string clickCodeSrc = "// Write code to react to click event below!";

        public string ClickCodeSrc
        {
            get { return GetClickCodeSrc(); }
            set { UpdateClickCodeSrc(value); }
        }

        /// <summary>
        /// The script in that string will be run if there is a collision.
        /// </summary>
        private string collisionCodeSrc = "// Write code to react to collision below!";

        public string CollisionCodeSrc
        {
            get { return GetCollisionCodeSrc(); }
            set { UpdateCollisionCodeSrc(value); }
        }
        /// <summary>
        /// Instance of the class we do create on the fly.
        /// </summary>
        private ExecutableBlock currentExecutableBlockInstance = null;

        /// <summary>
        /// We keep the time our script started beiing evaluated.
        /// </summary>
        private double scriptStartTime = 0;
        
        /// <summary>
        /// We also keep track of when the script was last evaluated.
        /// </summary>
        private double scriptLastEvaluation = 0;

        /// <summary>
        /// Let's keep track of when we have got a click.
        /// </summary>
        private double scriptLastClick = 0;

        /// <summary>
        /// Let's keep track of when we have got a collision.
        /// </summary>
        private double scriptLastCollision = 0;

        /// <summary>
        /// Default constructor. We build one bug string which server as the 'skeleton' of the class deriving from ExecutableBlock
        /// that will be a receptable for our script.
        /// </summary>
        public Script()
        {
            StringBuilder strBuilder = new StringBuilder("");
            strBuilder.AppendLine("using System;");
            strBuilder.AppendLine("using System.Windows.Forms;");
            strBuilder.AppendLine("using System.Collections.Generic;");
            strBuilder.AppendLine("using System.Text;");
            strBuilder.AppendLine("using System.Diagnostics;");
            strBuilder.AppendLine("using Microsoft.DirectX;");
            strBuilder.AppendLine("using Microsoft.DirectX.Direct3D;");
            strBuilder.AppendLine("using Microsoft.Samples.DirectX.UtilityToolkit;");
            strBuilder.AppendLine("namespace DXGfxLib {");
            strBuilder.AppendLine("public class ReplaceByName : ExecutableBlock {");
            strBuilder.AppendLine("ReplaceByMemberVariableSrc");
            strBuilder.AppendLine("public override void Evaluate(Scene scene, SceneNode parent, double appTime, float elapsedTime) {");
            strBuilder.AppendLine("ReplaceByMethodCodeSrc");
            strBuilder.AppendLine("}");
            strBuilder.AppendLine("public override void Click(Scene scene, SceneNode parent, double appTime, float elapsedTime) {");
            strBuilder.AppendLine("ReplaceByMethodClickCodeSrc");
            strBuilder.AppendLine("}");
            strBuilder.AppendLine("public override void Collision(Scene scene, SceneNode parent, double appTime, float elapsedTime, SceneObject collidingObject) {");
            strBuilder.AppendLine("ReplaceByMethodCollisionCodeSrc");
            strBuilder.AppendLine("}}}");
            skeleton = strBuilder.ToString();
        }

        /// <summary>
        /// This set the parent of the script.
        /// </summary>
        /// <param name="parent"></param>
        public void SetParent(SceneObject parent)
        {
            /// This is ugly, change it. Create a IScriptableObject interface and make both the scene object and the scene
            /// implement that interface (this is longer than what we do below though so this will be done later.
            if (parent == null)
            {
                // If we are here is means our parent is in fact the scene (and thus not a scene object!).
                // So get the name from the current scene.
                fileName = DXGfxManager.GetGlobalInstance().currentSceneName;
            }
            else
            {
                this.parent = parent;
                fileName = parent.fileName; 
            }
            fileName += ".script.cs";
            name = StringUtil.GetName(fileName);
            int firstDotIndex = name.IndexOf('.');
            name = name.Substring(0, firstDotIndex);
        }

        /// <summary>
        /// This set the scene associated with the script.
        /// </summary>
        /// <param name="scene"></param>
        public void SetScene(Scene scene)
        {
            this.scene = scene;
        }

        /// <summary>
        /// If you want to get a string containing the script code use this. This is in fact the code of the Evaluate method.
        /// </summary>
        /// <returns></returns>
        public string GetCodeSrc()
        {
            return codeSrc;
        }

        /// <summary>
        /// If you want to get a string containing the script code use this. This is in fact the code of the Evaluate method.
        /// </summary>
        /// <returns></returns>
        public string GetClickCodeSrc()
        {
            return clickCodeSrc;
        }

        /// <summary>
        /// If you want to get a string containing the script code use this. This is in fact the code of the Evaluate method.
        /// </summary>
        /// <returns></returns>
        public string GetCollisionCodeSrc()
        {
            return collisionCodeSrc;
        }

        /// <summary>
        /// Member variable definition code.
        /// </summary>
        /// <returns></returns>
        public string GetVarSrc()
        {
            return varSrc;
        }

        /// <summary>
        /// Get the source of our class deriving for ExecutableBlock.
        /// </summary>
        /// <returns></returns>
        public string GetSrcForCompilation()
        {
            string output = skeleton;

            output = output.Replace("ReplaceByName", name);
            output = output.Replace("ReplaceByMemberVariableSrc", varSrc);
            output = output.Replace("ReplaceByMethodCodeSrc", codeSrc);
            output = output.Replace("ReplaceByMethodClickCodeSrc", clickCodeSrc);
            output = output.Replace("ReplaceByMethodCollisionCodeSrc", collisionCodeSrc);

            return output;
        }

        /// <summary>
        /// When you want to set the code defining the variable use this.
        /// </summary>
        /// <param name="src"></param>
        public void UpdateVarSrc(string src)
        {
            this.varSrc = src;

            ScriptManager.GetGlobalInstance().ScriptUpdated(this);
        }

        /// <summary>
        /// When you want to set the code of the script that will be run use this method.
        /// </summary>
        /// <param name="src"></param>
        public void UpdateCodeSrc(string src)
        {
            this.codeSrc = src;

            ScriptManager.GetGlobalInstance().ScriptUpdated(this);
        }

        /// <summary>
        /// When you want to set the code of the script that will be run use this method.
        /// </summary>
        /// <param name="src"></param>
        public void UpdateClickCodeSrc(string src)
        {
            this.clickCodeSrc = src;

            ScriptManager.GetGlobalInstance().ScriptUpdated(this);
        }

        /// <summary>
        /// When you want to set the code of the script that will be run use this method.
        /// </summary>
        /// <param name="src"></param>
        public void UpdateCollisionCodeSrc(string src)
        {
            this.collisionCodeSrc = src;

            ScriptManager.GetGlobalInstance().ScriptUpdated(this);
        }

        /// <summary>
        /// This reset the script. StartTime value and everything!!
        /// </summary>
        /// <returns></returns>
        public bool Reset()
        {
            scriptStartTime = 0;
            scriptLastEvaluation = 0;
            scriptLastClick = 0;
            scriptLastCollision = 0;

            // Delete current instance and recreate it!!

            return true;
        }

        /// <summary>
        /// Each script will be a new class deriving from Script which will override the Evaluate method!
        /// </summary>
        public virtual void Evaluate(double appTime, float elapsedTime)
        {
            if (scene == null)
            {
                throw new Exception("No scene associated with script. Script can not be run!");
            }

            // We need to check with the scriptManager is the scripts are ready to run!
            // If not we need to get them ready!!
            if (!ScriptManager.GetGlobalInstance().scriptsAreReady)
            {
                ScriptManager.GetGlobalInstance().GetReady();
            }

            if ((scriptStartTime == 0)||(currentExecutableBlockInstance == null))
            {
                // If we are here, it's the first run for this script either because it has just been
                // created or because it has been reseted because the user has reseted the simulation
                // or because a script has changed and thus all scripts have been invalidated by the scriptmanager.
                scriptStartTime = appTime;
                // Need to create a new instance ! 
                currentExecutableBlockInstance = ScriptManager.GetGlobalInstance().GetNewExecutableBlock(name);

                currentExecutableBlockInstance.parent = parent;
                currentExecutableBlockInstance.scene = scene;
            }

            // Here the scripts are ready!!
            currentExecutableBlockInstance.Evaluate(scene, parent, appTime, elapsedTime);

            scriptLastEvaluation = appTime;
        }

        /// <summary>
        /// Scene objects have clicked method, when they are clicked they are going to react to that in that method and they
        /// will also call this metod on their associated script if they have any.
        /// </summary>
        public virtual void Clicked(double appTime, float elapsedTime)
        {
            if (parent == null)
            {
                throw new Exception("No parent associated with script. Script can not be run!");
            }
            if (scene == null)
            {
                throw new Exception("No scene associated with script. Script can not be run!");
            }

            // We need to check with the scriptManager is the scripts are ready to run!
            // If not we need to get them ready!!
            if (!ScriptManager.GetGlobalInstance().scriptsAreReady)
            {
                ScriptManager.GetGlobalInstance().GetReady();
            }

            if ((scriptStartTime == 0)||(currentExecutableBlockInstance == null))
            {
                // If we are here, it's the first run for this script either because it has just been
                // created or because it has been reseted because the user has reseted the simulation
                // or because a script has changed and thus all scripts have been invalidated by the scriptmanager.
                scriptStartTime = appTime;
                // Need to create a new instance ! 
                currentExecutableBlockInstance = ScriptManager.GetGlobalInstance().GetNewExecutableBlock(name);

                currentExecutableBlockInstance.parent = parent;
                currentExecutableBlockInstance.scene = scene;
            }

            // Here the scripts are ready!!
            currentExecutableBlockInstance.Click(scene, parent, appTime, elapsedTime);

            scriptLastClick = appTime;
            parent.HasBeenClicked = false;
        }

        /// <summary>
        /// Scene objects can collide with other objects. When that happen they can react in their Collision method. They should
        /// also call this method on their associated script if they have any. Thus the user can script reaction to collision.
        /// </summary>
        /// <param name="appTime"></param>
        /// <param name="elapsedTime"></param>
        /// <param name="collidingObject"></param>
        public virtual void Collision(double appTime, float elapsedTime, SceneObject collidingObject)
        {
                                    if (parent == null)
            {
                throw new Exception("No parent associated with script. Script can not be run!");
            }
            if (scene == null)
            {
                throw new Exception("No scene associated with script. Script can not be run!");
            }

            // We need to check with the scriptManager is the scripts are ready to run!
            // If not we need to get them ready!!
            if (!ScriptManager.GetGlobalInstance().scriptsAreReady)
            {
                ScriptManager.GetGlobalInstance().GetReady();
            }

            if ((scriptStartTime == 0)||(currentExecutableBlockInstance == null))
            {
                // If we are here, it's the first run for this script either because it has just been
                // created or because it has been reseted because the user has reseted the simulation
                // or because a script has changed and thus all scripts have been invalidated by the scriptmanager.
                scriptStartTime = appTime;
                // Need to create a new instance ! 
                currentExecutableBlockInstance = ScriptManager.GetGlobalInstance().GetNewExecutableBlock(name);

                currentExecutableBlockInstance.parent = parent;
                currentExecutableBlockInstance.scene = scene;
            }

            // Here the scripts are ready!!
            currentExecutableBlockInstance.Collision(scene, parent, appTime, elapsedTime, collidingObject);

            scriptLastCollision = appTime;
        }
    }
}
