using System;
using System.Collections.Generic;
using System.Linq;
using ComponentFramework.Modules;
using MTV3D65;
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;
using System.IO;

namespace ComponentFramework
{
    /// <summary>
    /// The base core class for the component framework.
    /// This class handles the application start, looping and end, and provides API to load and unload modules.
    /// It also has access to TV3D singletons, and exclusive access to the <see cref="TVEngine"/> reference.
    /// </summary>
    public class Core : ICore, IDisposable
    {
        /// <summary>
        /// The current run mode.
        /// </summary>
        readonly RunMode runMode;
        /// <summary>
        /// The looping manager reference.
        /// </summary>
        Looping looping;
        /// <summary>
        /// The current engine settings.
        /// </summary>
        EngineSettings settings;
        /// <summary>
        /// The anonymous form for fullscreen usage.
        /// </summary>
        readonly Form fullscreenForm = new Form();

        /// <summary>
        /// The currently loaded components.
        /// </summary>
        readonly List<Component> components = new List<Component>();
        /// <summary>
        /// The components added in the current loop and that will be added before the next loop.
        /// </summary>
        readonly List<Component> componentsToAdd = new List<Component>();
        /// <summary>
        /// The components removed in the current loop and that will be removed before the next loop.
        /// </summary>
        readonly List<Component> componentsToRemove = new List<Component>();

        /// <summary>
        /// The currently loaded services.
        /// </summary>
        readonly List<Service> services = new List<Service>();
        /// <summary>
        /// The service interface map, including all base interfaces.
        /// </summary>
        readonly Dictionary<Type, IService> serviceMap = new Dictionary<Type, IService>();

        /// <summary>
        /// The elapsed time stopwatch.
        /// </summary>
        readonly Stopwatch stopwatch = new Stopwatch();

        /// <summary>
        /// Constructor type arguments for module loading.
        /// </summary>
        readonly static Type[] ctorTypes = new[] { typeof(ICore) };
        /// <summary>
        /// Constructor parameters for module loading.
        /// </summary>
        readonly object[] ctorParams;

        /// <summary>
        /// Parameterless constructor.
        /// Initializes references and identifies the run mode.
        /// </summary>
        public Core() 
        {
            ctorParams = new object[] { this };

#if DEBUG
            runMode = RunMode.Debug;
#else
            runMode = RunMode.Release;
#endif

            Atmosphere = new TVAtmosphere();
            CameraFactory = new TVCameraFactory();
            Globals = new TVGlobals();
            InputEngine = new TVInputEngine();
            InternalObjects = new TVInternalObjects();
            LightEngine = new TVLightEngine();
            InternalObjects = new TVInternalObjects();
            LightEngine = new TVLightEngine();
            MathLibrary = new TVMathLibrary();
            Physics = new TVPhysics();
            Scene = new TVScene();
            Screen2DImmediate = new TVScreen2DImmediate();
            Screen2DText = new TVScreen2DText();
            TextureFactory = new TVTextureFactory();
            MaterialFactory = new TVMaterialFactory();
            GameControllers = new TVGameControllers();
            DeviceInfo = new TVDeviceInfo();
        }

        /// <summary>
        /// Schedules the engine initialization and starts the message loop.
        /// </summary>
        /// <param name="initializationSettings">The engine settings to use.</param>
        public void Run(EngineSettings initializationSettings)
        {
            settings = initializationSettings;

            Application.Idle += InitializeEngine;
            Application.Run();
        }

        /// <summary>
        /// Initializes the engine, services and components.
        /// </summary>
        /// <param name="sender">Ignored</param>
        /// <param name="e">Ignored</param>
        void InitializeEngine(object sender, EventArgs e)
        {
            // Attach the looping events
            looping = new Looping(this);

            Engine = new TVEngine();
#if DEBUG
            Engine.SetDebugMode(true, false, false, false);
            Engine.SetDebugFile(settings.DebugFile.FullName);
#else
            Engine.SetDebugMode(false, false, false, false);            
#endif
            Engine.AllowMultithreading(settings.MultiThreading);

            if (File.Exists(GetType().Name + ".lic"))
                Engine.SetLicenseFile(GetType().Name + ".lic");

            // When I use doubles, I WANT DOUBLE-PRECISION DAMNIT.
            Engine.SetFPUPrecision(true);

            if (settings.MultisampleType != CONST_TV_MULTISAMPLE_TYPE.TV_MULTISAMPLE_NONE)
                Engine.SetAntialiasing(true, settings.MultisampleType);

            // If no windowed handle is given, the form handle is used (if possible)
            if (settings.WindowedHandle == IntPtr.Zero && settings.WindowedForm != null)
                settings.WindowedHandle = settings.WindowedForm.Handle;

            // If no form is used, a couple of naive tries...
            if (settings.WindowedForm == null && settings.WindowedHandle != IntPtr.Zero)
            {
                Control control = Control.FromHandle(settings.WindowedHandle);
                if (control is Form)
                    settings.WindowedForm = control as Form;
                else if (control.Parent is Form)
                    settings.WindowedForm = control.Parent as Form;
            }

            bool initSuccess = false;
            if (settings.Fullscreen)
            {
                if (settings.FullscreenMode.Width == 0 || settings.FullscreenMode.Height == 0)
                    throw new InvalidOperationException("No fullscreen resolution was specified");

                settings.WindowedForm = fullscreenForm;

                try
                {
                    initSuccess = Engine.Init3DFullscreen(settings.FullscreenMode.Width, settings.FullscreenMode.Height,
                        settings.FullscreenMode.Format, true, settings.VSync,
                        CONST_TV_DEPTHBUFFERFORMAT.TV_DEPTHBUFFER_BESTBUFFER, 1, fullscreenForm.Handle);
                } 
                catch (AccessViolationException) { /* License failure */ }
            }
            else
            {
                if (settings.WindowedHandle == IntPtr.Zero && settings.WindowedForm == null)
                    throw new InvalidOperationException("Incomplete Settings for windowed mode");

                Engine.SetVSync(settings.VSync);
                try
                {
                    initSuccess = Engine.Init3DWindowed(settings.WindowedHandle);
                    Engine.GetViewport().SetAutoResize(true);
                }
                catch (AccessViolationException) { /* License failure */ }
            }

            // Probably a license failure
            if (!initSuccess)
            {
                MessageBox.Show("Couldn't initialize engine! Exiting.", "Critical error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                Application.Idle -= InitializeEngine;
                Dispose();
                Application.Exit();
                return;
            }

            // If nothing is found, just don't hook the events
            if (settings.WindowedForm != null)
            {
                settings.WindowedForm.FormClosing += delegate 
                { 
                    Dispose();
                    looping.StopLoop();
                };
                settings.WindowedForm.Deactivate += delegate { looping.PauseLoop(); };
                settings.WindowedForm.Activated += delegate
                {
                    InputEngine.ClearKeyBuffer();
                    InputEngine.ForceUpdate();
                    InputEngine.ForceUpdate();
                    stopwatch.Reset();
                    looping.ResumeLoop();
                };
            }

            Initialize();
            InitializeModules();

            if (!settings.Fullscreen && settings.WindowedForm != null)
                settings.WindowedForm.Show();

            Application.Idle -= InitializeEngine;
        }

        /// <summary>
        /// Switches the rendering from windowed mode to fullscreen mode.
        /// </summary>
        public void SwitchWindowed()
        {
            if (!settings.Fullscreen)
                throw new InvalidOperationException("Already in windowed mode.");

            settings.Fullscreen = false;

            fullscreenForm.Hide();
            settings.WindowedForm.Show();

            Engine.SwitchWindowed(settings.WindowedHandle);
        }

        /// <summary>
        /// Switches the rendering from fullscreen mode to windowed mode.
        /// </summary>
        public void SwitchFullscreen()
        {
            if (settings.Fullscreen)
                throw new InvalidOperationException("Already in fullscreen mode.");

            settings.Fullscreen = true;

            fullscreenForm.Show();
            settings.WindowedForm.Hide();

            Engine.SwitchFullscreen(settings.FullscreenMode.Width,
                settings.FullscreenMode.Height,
                settings.FullscreenMode.Format,
                CONST_TV_DEPTHBUFFERFORMAT.TV_DEPTHBUFFER_BESTBUFFER,
                fullscreenForm.Handle);   
        }

        /// <summary>
        /// Performs additional initialization before auto-loading services and components.
        /// The base implementation is empty.
        /// </summary>
        protected virtual void Initialize()
        {
        }

        /// <summary>
        /// Stops looping and exits the application.
        /// </summary>
        public void Exit()
        {
            Dispose();
            looping.StopLoop();
        }

        /// <summary>
        /// Finds all auto-loaded components and enabled services in the core implementation assembly and loads them.
        /// If the <see cref="Core"/> class is directly used and not overridden, nothing will be loaded!
        /// </summary>
        void InitializeModules()
        {
            // Madness...? THIS, IS, REFLECTION!!
            foreach (Type type in GetType().Assembly.GetTypes().Where(t => t.IsClass))
            {
                bool isComponent = false;
                bool isService = false;

                Type baseType = type.BaseType;
                while(baseType != typeof(Object) && !isComponent && !isService) 
                {
                    var attribute = type.GetCustomAttributes(typeof(AutoLoadAttribute), true).FirstOrDefault() as AutoLoadAttribute;
                    if (attribute != null && (attribute.RunModeRestriction == RunMode.None || attribute.RunModeRestriction == runMode))
                        isComponent = baseType == typeof(Component);

                    if(type.GetCustomAttributes(typeof(DisableAttribute), true).Length == 0) 
                        isService = baseType == typeof(Service);

                    baseType = baseType.BaseType;
                }

                if (isComponent)
                {
                    var component = type.GetConstructor(ctorTypes).Invoke(ctorParams) as Component;
                    components.Add(component);
                }

                if (isService)
                {
                    var service = type.GetConstructor(ctorTypes).Invoke(ctorParams) as Service;
                    services.Add(service);

                    var serviceInterfaces = new List<Type>();

                    foreach (Type serviceInterface in type.GetInterfaces())
                    {
                        bool isServiceInterface = false;

                        Type[] baseInterfaces = serviceInterface.GetInterfaces();
                        while (baseInterfaces.Length > 0 && !isServiceInterface)
                        {
                            Type baseInterface = baseInterfaces[0];
                            isServiceInterface = baseInterface == typeof(IService);
                            baseInterfaces = baseInterface.GetInterfaces();
                        }

                        if (isServiceInterface)
                        {
                            serviceInterfaces.Add(serviceInterface);

                            baseInterfaces = serviceInterface.GetInterfaces();
                            do 
                            {
                                Type baseInterface = baseInterfaces[0];
                                if(baseInterface != typeof(IService))
                                    serviceInterfaces.Add(serviceInterface);
                                baseInterfaces = baseInterface.GetInterfaces();
                            }
                            while(baseInterfaces.Length > 0);
                        }
                    }

                    foreach(Type serviceInterface in serviceInterfaces)
                        serviceMap.Add(serviceInterface, service);
                }
            }

            foreach (Service service in services)
            {
                InjectServices(service);
                service.Initialize();
            }

            InjectServices(this);

            components.Sort();
            foreach (Component component in components)
            {
                InjectServices(component);
                component.Initialize();
            }
        }

        /// <summary>
        /// Injects service dependencies into a module (or any class that has service dependencies).
        /// </summary>
        /// <param name="module">The module to process</param>
        public void InjectServices(object module)
        {
            Type type = module.GetType();
            do
            {
                foreach (PropertyInfo property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
                {
                    object[] attributes = property.GetCustomAttributes(typeof(ServiceDependencyAttribute), true);

                    if (attributes.Length == 1)
                    {
                        Type dependentServiceType = property.PropertyType;
                        IService dependentSevice;
                        bool serviceFound = serviceMap.TryGetValue(dependentServiceType, out dependentSevice);
                        var attribute = attributes[0] as ServiceDependencyAttribute;

                        if (!serviceFound)
                        {
                            if (!attribute.Optional)
                                throw new MissingServiceException(type, dependentServiceType);
                        }
                        else
                        {
                            MethodInfo setter = property.GetSetMethod(true);
                            if (setter == null)
                                throw new MissingSetterException(type, dependentServiceType);

                            setter.Invoke(module, new[] { dependentSevice });
                        }
                    }
                }

                type = type.BaseType;
            } while (type != typeof(object));           
        }

        /// <summary>
        /// Gets the singleton service reference from an interface type.
        /// </summary>
        /// <typeparam name="T">The interface type to search for</typeparam>
        /// <returns>The matching service implementation</returns>
        /// <exception cref="KeyNotFoundException">If the service cannot be found</exception>
        public T GetService<T>() where T : IService
        {
            return (T)serviceMap[typeof(T)];
        }

        /// <summary>
        /// Creates a component from a type and loads it.
        /// </summary>
        /// <typeparam name="T">The component type to create</typeparam>
        /// <returns>The created, loaded but not yet initialized component</returns>
        public T CreateAndLoadComponent<T>() where T : Component
        {
            var component = typeof(T).GetConstructor(ctorTypes).Invoke(ctorParams) as T;
            LoadComponent(component);
            return component;
        }

        /// <summary>
        /// Creates a component from a type and loads it.
        /// </summary>
        /// <param name="componentType">The component type to create</param>
        /// <returns>The created, loaded but not yet initialized component</returns>
        public Component CreateAndLoadComponent(Type componentType)
        {
            var component = componentType.GetConstructor(ctorTypes).Invoke(ctorParams) as Component;
            LoadComponent(component);
            return component;
        }

        /// <summary>
        /// Loads an already-instantiated component. 
        /// The initialization is deferred to the next loop.
        /// </summary>
        /// <param name="component">The component to load</param>
        public void LoadComponent(Component component)
        {
            componentsToAdd.Add(component);
            InjectServices(component);
            component.Initialize();
        }

        /// <summary>
        /// Unloads a component, which schedules its removal and dispose.
        /// </summary>
        /// <param name="component">The component to remove</param>
        public void UnloadComponent(Component component)
        {
            componentsToRemove.Add(component);
        }

        /// <summary>
        /// Sorts the component list.
        /// </summary>
        void SortComponents()
        {
            components.Sort();
        }

        /// <summary>
        /// Synchronizes the component addition/removal lists.
        /// </summary>
        void SynchronizeLists()
        {
            if (componentsToAdd.Count > 0)
            {
                var addedComponents = componentsToAdd.ToArray();
                foreach (Component component in addedComponents)
                {
                    components.Add(component);
                    SortComponents();
                    component.OrderChanged += SortComponents;
                }
                componentsToAdd.RemoveAll(x => addedComponents.Contains(x));
            }

            if (componentsToRemove.Count > 0)
            {
                var removedComponents = componentsToRemove.ToArray();
                foreach (Component component in removedComponents)
                {
                    component.Dispose();
                    component.OrderChanged -= SortComponents;
                    components.Remove(component);
                }
                componentsToRemove.RemoveAll(x => removedComponents.Contains(x));
            }
        }

        /// <summary>
        /// Does the game logic update of a single loop.
        /// </summary>
        internal void UpdateInternal()
        {
            stopwatch.Stop();
            TimeSpan elapsed = stopwatch.Elapsed;
            stopwatch.Reset();
            stopwatch.Start();

            SynchronizeLists();

            foreach (Service service in services)
                service.Update(elapsed);
            foreach (Component component in components.Where(x => x.Enabled))
                component.Update(elapsed);

            Update();
        }

        /// <summary>
        /// Performs additional global update logic.
        /// The base implementation is empty.
        /// </summary>
        protected virtual void Update()
        {
        }

        /// <summary>
        /// Re-calls the <see cref="Component.Draw()"/> method of all loaded components.
        /// </summary>
        public void ReDraw()
        {
            foreach (Component component in components.Where(x => x.Enabled))
                component.Draw();
        }

        /// <summary>
        /// Does the drawing logic of a single loop.
        /// </summary>
        internal void Draw()
        {
            var toDraw = components.Where(x => x.Enabled);

            foreach (Component component in toDraw)
                component.PreDraw();

            Engine.Clear();

            foreach (Component component in toDraw)
                component.Draw();

            foreach (Component component in toDraw)
                component.PostDraw();

            Engine.RenderToScreen();
        }

        /// <summary>
        /// Provides access to the current engine settings.
        /// </summary>
        public EngineSettings Settings
        {
            get { return new EngineSettings(settings); }
        }

        /// <summary>
        /// Provides access to the only <see cref="TVEngine"/> reference.
        /// </summary>
        public TVEngine Engine { get; private set; }

        /// <summary>A reference to the <see cref="TVAtmosphere"/> singleton.</summary>
        protected TVAtmosphere Atmosphere { get; private set; }
        /// <summary>A reference to the <see cref="TVCameraFactory"/> singleton.</summary>
        protected TVCameraFactory CameraFactory { get; private set; }
        /// <summary>A reference to the <see cref="TVGlobals"/> singleton.</summary>
        protected TVGlobals Globals { get; private set; }
        /// <summary>A reference to the <see cref="TVInputEngine"/> singleton.</summary>
        protected TVInputEngine InputEngine { get; private set; }
        /// <summary>A reference to the <see cref="TVInternalObjects"/> singleton.</summary>
        protected TVInternalObjects InternalObjects { get; private set; }
        /// <summary>A reference to the <see cref="TVLightEngine"/> singleton.</summary>
        protected TVLightEngine LightEngine { get; private set; }
        /// <summary>A reference to the <see cref="TVMathLibrary"/> singleton.</summary>
        protected TVMathLibrary MathLibrary { get; private set; }
        /// <summary>A reference to the <see cref="TVPhysics"/> singleton.</summary>
        protected TVPhysics Physics { get; private set; }
        /// <summary>A reference to the <see cref="TVScene"/> singleton.</summary>
        protected TVScene Scene { get; private set; }
        /// <summary>A reference to the <see cref="TVScreen2DImmediate"/> singleton.</summary>
        protected TVScreen2DImmediate Screen2DImmediate { get; private set; }
        /// <summary>A reference to the <see cref="TVScreen2DText"/> singleton.</summary>
        protected TVScreen2DText Screen2DText { get; private set; }
        /// <summary>A reference to the <see cref="TVTextureFactory"/> singleton.</summary>
        protected TVTextureFactory TextureFactory { get; private set; }
        /// <summary>A reference to the <see cref="TVMaterialFactory"/> singleton.</summary>
        protected TVMaterialFactory MaterialFactory { get; private set; }
        /// <summary>A reference to the <see cref="TVGameControllers"/> singleton.</summary>
        protected TVGameControllers GameControllers { get; private set; }
        /// <summary>A reference to the <see cref="TVDeviceInfo"/> singleton.</summary>
        protected TVDeviceInfo DeviceInfo { get; private set; }

        /// <summary>
        /// Does the actual dispose operation, as per the Dispose .NET pattern.
        /// </summary>
        protected virtual void DisposeInternal()
        {
        }

        ///<summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            componentsToRemove.AddRange(components);
            SynchronizeLists();

            DisposeInternal();
            GC.SuppressFinalize(this);
            Disposed = true;
        }

        /// <summary>
        /// Whether this component is disposed.
        /// </summary>
        public bool Disposed { get; private set; }
    }
}
