using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using VisimBox.Scene;
using VisimBox.Renderer;

namespace VisimBox
{
    public class VisimBoxException : SystemException
    {
        public VisimBoxException(string message)
            : base("VisimBox failure" + message)
        {
        }

        public VisimBoxException(string message, Exception innerException)
            : base("VisimBox failure" + message, innerException)
        {
        }
    }

    
    /// <summary>
    /// Main visimbox engine
    /// </summary>
    public class VisimEngine : Singleton<VisimEngine>, IDisposable
    {
        bool initialized = false;
        bool running = false;
                
        Renderer.Renderer MainRender;

        SceneRoot mainScene;


        System.Diagnostics.Stopwatch SimulationTimer;

        /// <summary>
        /// Main renderer of the engine
        /// </summary>
        public Renderer.Renderer Renderer
        {
            get
            {
                return MainRender;
            }
        }

        /// <summary>
        /// Main scene of the simulation
        /// </summary>
        public SceneRoot MainScene
        {
            get
            {
                return mainScene;
            }
        }

        /// <summary>
        /// Is Visimbox running (ie rendering and calculating continuously without requests)
        /// </summary>
        public bool Running { 
            get 
            { 
                return running; 
            }
 
            set 
            {
                running = value;
                if (running == false)
                {
                    SimulationTimer.Stop();
                }
                else
                {
                    SimulationTimer.Start();
                }
            } 
        }


        #region Initializing and closing

        #region Initializing

        /// <summary>
        /// Perform standard initialization and creates standard rendering window
        /// </summary>
        public void Initialize()
        {
            Initialize(new Renderer.RenderContextSettings());
        }

        /// <summary>
        /// Performs standard initialization with specified renderwindow as main window or creates new one
        /// </summary>
        /// <param name="settings">Settings for the main rendering window, renderwindow is null new standard window will be created</param>
        public void Initialize(Renderer.RenderContextSettings settings)
        {
            if (initialized)
                return;
            
            VisimBox.Console.WriteLine("Initializing Visimbox.");

            FileSystem.Instance.Initialize();
            InitializeRenderer(settings);

            NewScene();
            initialized = true;
            VisimBox.Console.WriteLine("Visimbox initilized");
        }

        /// <summary>
        /// Initialize renderer
        /// </summary>
        public void InitializeRenderer(Renderer.RenderContextSettings settings)
        {
            Assembly renderplugin;
            Type rentype = typeof(int);
            
            VisimBox.Console.WriteLine("Initializing renderer - searching for rendering plugin.");

            try
            {
                renderplugin = System.Reflection.Assembly.LoadFrom("VisimRenderGL.dll");
            }
            catch (Exception ex)
            {
                throw new VisimBoxException("Can't load renderer plugin!", ex);
            }

            try
            {
                //rentype = renderplugin.GetType("RenderPlugin", true);

                Type[] types = renderplugin.GetTypes();
                bool found = false;

                foreach (var type in types)
                {
                    if (type.IsNotPublic || !type.IsClass)
                        continue;

                    Type[] intrf = type.GetInterfaces();

                    if (intrf.Contains(typeof(Renderer.IRenderer)))
                    {
                        rentype = type;
                        found = true;
                        break;
                    }
                }

                if (!found)
                    throw new VisimBoxException("Cannot find renderer plugin!");

            }
            catch (Exception ex)
            {
                throw new VisimBoxException("Specified assembly doesn't contain renderer plugin!", ex);
            }

            try
            {
                object renderinstance = Activator.CreateInstance(rentype);

                MainRender = (Renderer.Renderer)renderinstance;
            }
            catch (Exception ex)
            {
                throw new VisimBoxException("Can't create instance of the renderer!", ex);
            }

            VisimBox.Console.WriteLine("Visimbox renderer plugin loaded.");

            MainRender.Initialize(settings);

            SimulationTimer = new System.Diagnostics.Stopwatch();


            //System.Windows.Forms.Application.Idle += new EventHandler(Application_Idle);


        }

        public void Run()
        {
            Running = true;

            IRenderContext irc = MainRender.MainContext;

            while (!irc.Settings.Window.IsDisposed)
            {
                System.Windows.Forms.Application.DoEvents();

                while (MainRender.IsIdle())
                {
                    TimeTick();
                    //TimeTick(0.01f);
                    irc.StartFrame();
                    irc.RenderNodes(this.mainScene);
                    irc.EndFrame();
                }
            }

            Close();
        }

        #endregion

        public void Close()
        {
            if (!initialized)
                return;
            VisimBox.Console.WriteLine("Closing Visimbox.");
            FreeScene();
            MainRender.Close();
            FileSystem.Instance.Close();
        }

        public void Dispose()
        {
            Close();
        }

        #endregion


        #region Scene managing

        /// <summary>
        /// Frees current main scene and all associated data
        /// </summary>
        public void FreeScene()
        {
            if (mainScene != null)
            {
                mainScene.Dispose();
                mainScene = null;
            }
        }

        /// <summary>
        /// Creates new main scene (deletes old one)
        /// </summary>
        public void NewScene()
        {
            FreeScene();
            mainScene = new SceneRoot();
        }

        /// <summary>
        /// Loads scene from the dist (deletes old)
        /// </summary>
        /// <param name="filename">File with scene in recognizable format</param>
        /// <returns>True if load sucessfull</returns>
        public bool LoadScene(string filename)
        {
            return mainScene.LoadScene(filename);
            //following doesn't clean some stuff properly (lights)
            /*
            SceneRoot news = new SceneRoot();
            if (!news.LoadScene(filename))
                return false;
            FreeScene();
            mainScene = news;
            return true;
            */
        }

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

        /// <summary>
        /// Save main scene to disk
        /// </summary>
        /// <param name="filename">Filename of the scene</param>
        /// <returns>True if everything went ok</returns>
        public bool SaveScene(string filename)
        {
            return mainScene.SaveScene(filename);
        }


        #endregion

        #region Rendering

        /// <summary>
        /// Renders main scene in the main rendering window
        /// </summary>
        public void RenderScene()
        {
            RenderScene(MainRender.MainContext);
        }

        /// <summary>
        /// Renders main scene in the specified rendering context
        /// </summary>
        /// <param name="context"></param>
        public void RenderScene(IRenderContext context)
        {
            context.StartFrame();

            RendererEventArgs rargs = new RendererEventArgs(MainRender, context, context.MainCamera);
            mainScene.RenderScene(rargs);
            context.EndFrame();
        }

        #endregion


        #region Simulation

        /// <summary>
        /// Performs timetick of the simulation on scene based on the realtime from the last called time tick
        /// </summary>
        /// <returns>Elapsed time from the last time tick</returns>
        public double TimeTick()
        {
            SimulationTimer.Stop();
            double lasttime = SimulationTimer.ElapsedMilliseconds / 1000.0f;
            SimulationTimer.Reset();
            SimulationTimer.Start();

            TimeTick(lasttime);
            return lasttime;
        }

        /// <summary>
        /// Performs timetick of the simulation on scene
        /// </summary>
        /// <param name="seconds">requested elapsed time</param>
        public void TimeTick(double elapsedseconds)
        {
            mainScene.TimeTickHierarchy(elapsedseconds);
        }

        /// <summary>
        /// Sets time for the whole scene to specified time
        /// </summary>
        /// <param name="seconds"></param>
        public void SetTime(double seconds)
        {
            mainScene.SetTimeHierarchy(seconds);
        }


        #endregion

        void Application_Idle(object sender, EventArgs e)
        {
            if (running)
            {
                IRenderContext irc = MainRender.MainContext;
                while (MainRender.IsIdle())
                {
                    //MainRender.RenderAllContexts();
                    irc.StartFrame();
                    irc.RenderNodes(this.mainScene);
                    irc.EndFrame();
                }
            }
        }


    }
}
