using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Decal.Interop.Core;
using Microsoft.Win32;

namespace Decal.Core
{
    [ComVisible(true)]
    [Guid("4557D5A1-00DB-48F6-ACB3-4FEF30E2F358")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces("Decal.Interop.Core.IDecalEvents\0\0")]
    [ProgId("Decal.DecalCore")]
    public class DecalCoreImpl : IDecalCore
    {
        // COM events
        public event IDecalEvents_InitializeCompleteEventHandler InitializeComplete;
        public event IDecalEvents_TerminateCompleteEventHandler TerminateComplete;

        // Services and plugins
        private struct ServiceEntry
        {
            public Guid Clsid;
            public IDecalService Service;
            public IDecalRender Renderer;
        }

        private readonly List<ServiceEntry> _services = new List<ServiceEntry>();
        private readonly List<PluginSite2Impl> _plugins = new List<PluginSite2Impl>();
        private readonly ACHooksImpl _hooks = new ACHooksImpl();
        private bool _servicesRunning;
        private bool _pluginsRunning;

        // Graphics
        private object _d3dDevice;
        private int _hwnd;
        private int _screenWidth = 800;
        private int _screenHeight = 600;

        internal ACHooksImpl ACHooksInstance => _hooks;

        // IDecalCore implementation
        public void InitGraphics(object pD3DDevice)
        {
            _d3dDevice = pD3DDevice;

            // Notify render services
            foreach (var svc in _services)
            {
                svc.Renderer?.ChangeDirectX();
            }
        }

        public object GetD3DDevice(ref Guid riid) => _d3dDevice;

        public int HWND
        {
            get => _hwnd;
            set
            {
                _hwnd = value;
                foreach (var svc in _services)
                    svc.Renderer?.ChangeHWND();
            }
        }

        public bool Focus => true;

        public void SendWM(int HWND, short uMsg, int wParam, int lParam, ref bool pbEat)
        {
            pbEat = false;
        }

        public void GetScreenSize(out int pWidth, out int pHeight)
        {
            pWidth = _screenWidth;
            pHeight = _screenHeight;
        }

        public string MapPath(string pPath)
        {
            if (string.IsNullOrEmpty(pPath)) return pPath;

            string result = pPath;
            int pos = 0;

            while (pos < result.Length)
            {
                int start = result.IndexOf('%', pos);
                if (start < 0) break;

                int end = result.IndexOf('%', start + 1);
                if (end < 0) break;

                string token = result.Substring(start + 1, end - start - 1);
                string resolved = ResolveToken(token);
                result = result.Substring(0, start) + resolved + result.Substring(end + 1);
                pos = start + resolved.Length;
            }

            return result;
        }

        private string ResolveToken(string token)
        {
            string lower = token.ToLowerInvariant();
            if (lower == "ac" || lower.StartsWith("ac:"))
            {
                try
                {
                    using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Turbine\Asheron's Call"))
                    {
                        return key?.GetValue("Path") as string ?? "";
                    }
                }
                catch { return ""; }
            }

            if (lower == "decal" || lower.StartsWith("decal:"))
            {
                try
                {
                    using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Decal\Agent"))
                    {
                        return key?.GetValue("AgentPath") as string ?? "";
                    }
                }
                catch { return ""; }
            }

            return "%" + token + "%";
        }

        public void StartPlugins()
        {
            if (_pluginsRunning) return;
            if (!_servicesRunning) StartServices();

            // Notify services: before plugins
            foreach (var svc in _services)
                svc.Service.BeforePlugins();

            // Enumerate and load plugins from registry
            var pluginEnum = new DecalEnumImpl();
            pluginEnum.Initialize("Plugins");

            try
            {
                pluginEnum.Next(); // Move to first
                while (true)
                {
                    if (pluginEnum.Enabled)
                    {
                        try
                        {
                            var riid = typeof(IPlugin2).GUID;
                            var pluginObj = pluginEnum.CreateInstance(ref riid);
                            var plugin = pluginObj as IPlugin2;
                            if (plugin != null)
                            {
                                var site = new PluginSite2Impl();
                                site.Setup(this, plugin, pluginEnum.ComClass);
                                _plugins.Add(site);

                                plugin.Initialize((PluginSite2)(object)site);
                            }
                        }
                        catch { } // Bad plugins are skipped
                    }
                    pluginEnum.Next();
                }
            }
            catch { } // Next() throws when past end

            _pluginsRunning = true;

            // Notify services: after plugins
            foreach (var svc in _services)
                svc.Service.AfterPlugins();

            InitializeComplete?.Invoke(eDecalComponentType.ePlugin);
        }

        public void StopPlugins()
        {
            if (!_pluginsRunning) return;

            // Unload all plugins (copy list since Unload modifies it)
            var sites = new List<PluginSite2Impl>(_plugins);
            foreach (var site in sites)
            {
                try { site.Unload(); } catch { }
            }
            _plugins.Clear();

            _pluginsRunning = false;

            // Notify services: after plugins stopped
            foreach (var svc in _services)
            {
                try { svc.Service.AfterPlugins(); } catch { }
            }

            TerminateComplete?.Invoke(eDecalComponentType.ePlugin);
        }

        public object Object => null;

        public void StartServices()
        {
            if (_servicesRunning) return;

            // Initialize ACHooks first
            _hooks.SetDecal(this);

            // Enumerate and load services from registry
            var serviceEnum = new DecalEnumImpl();
            serviceEnum.Initialize("Services");

            try
            {
                serviceEnum.Next();
                while (true)
                {
                    if (serviceEnum.Enabled)
                    {
                        try
                        {
                            var riid = typeof(IDecalService).GUID;
                            var svcObj = serviceEnum.CreateInstance(ref riid);
                            var service = svcObj as IDecalService;
                            if (service != null)
                            {
                                service.Initialize((DecalCore)(object)this);

                                // Check for render capability
                                IDecalRender renderer = svcObj as IDecalRender;

                                _services.Add(new ServiceEntry
                                {
                                    Clsid = serviceEnum.ComClass,
                                    Service = service,
                                    Renderer = renderer
                                });
                            }
                        }
                        catch { } // Service init failure - skip
                    }
                    serviceEnum.Next();
                }
            }
            catch { } // Next() throws when past end

            _servicesRunning = true;
            InitializeComplete?.Invoke(eDecalComponentType.eService);
        }

        public DecalEnum Configuration
        {
            get
            {
                var e = new DecalEnumImpl();
                e.Initialize("Plugins");
                return (DecalEnum)(object)e;
            }
        }

        public void StopServices()
        {
            if (!_servicesRunning) return;
            if (_pluginsRunning) StopPlugins();

            // Stop services in reverse order (LIFO)
            for (int i = _services.Count - 1; i >= 0; i--)
            {
                try { _services[i].Service.Terminate(); } catch { }
            }
            _services.Clear();

            _servicesRunning = false;
            TerminateComplete?.Invoke(eDecalComponentType.eService);
        }

        public object Plugin => null;
        public object Service => null;

        public void Render2D()
        {
            foreach (var svc in _services)
                svc.Renderer?.Render2D();
        }

        public void Render3D()
        {
            _hooks.FireRenderPreUI();
            foreach (var svc in _services)
                svc.Renderer?.Render3D();
        }

        public void PreReset()
        {
            foreach (var svc in _services)
                svc.Renderer?.PreReset();
        }

        public void PostReset()
        {
            foreach (var svc in _services)
                svc.Renderer?.PostReset();
        }

        public ACHooks Hooks => (ACHooks)(object)_hooks;

        public void StartFilters()
        {
            // Filters are started as part of services
        }

        public void StopFilters()
        {
            // Filters are stopped as part of services
        }

        public bool KillBitCheckByGUID(string bstrGuid, string bstrVersion) => false;
        public bool KillBitCheckByProgID(string bstProgID, string bstrVersion) => false;

        public bool PluginsRunning => _pluginsRunning;

        // Internal methods for PluginSite2
        internal void RemovePlugin(PluginSite2Impl site) => _plugins.Remove(site);

        internal object GetService(Guid clsid)
        {
            foreach (var svc in _services)
                if (svc.Clsid == clsid) return svc.Service;
            return null;
        }

        internal object GetPlugin(Guid clsid)
        {
            foreach (var site in _plugins)
                if (site.PluginClsid == clsid) return site.Plugin;
            return null;
        }
    }
}
