using AxibugEmuOnline.Client.ClientCore;
using AxibugEmuOnline.Client.InputDevices;
using AxibugProtobuf;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace AxibugEmuOnline.Client.Settings
{
    /// <summary>
    /// 管理键位映射设置
    /// </summary>
    public class KeyMapperSetting
    {
        Dictionary<RomPlatformType, EmuCoreControllerKeyBinding> m_binders = new Dictionary<RomPlatformType, EmuCoreControllerKeyBinding>();
        Dictionary<Type, EmuCoreControllerKeyBinding> m_bindersByType = new Dictionary<Type, EmuCoreControllerKeyBinding>();
        public KeyMapperSetting()
        {
            var baseType = typeof(EmuCoreControllerKeyBinding);
            foreach (var t in baseType.Assembly.ExportedTypes)
            {
                if (t.IsAbstract) continue;
                if (!baseType.IsAssignableFrom(t)) continue;

                var binderIns = Activator.CreateInstance(t) as EmuCoreControllerKeyBinding;
                m_binders.Add(binderIns.Platform, binderIns);
                m_bindersByType.Add(binderIns.GetType(), binderIns);
            }
        }

        public T GetBinder<T>() where T : EmuCoreControllerKeyBinding
        {
            m_bindersByType.TryGetValue(typeof(T), out var binder);
            return binder as T;
        }

        public T GetBinder<T>(RomPlatformType romType) where T : EmuCoreControllerKeyBinding
        {
            m_binders.TryGetValue(romType, out var binder);
            return binder as T;
        }
    }

    /// <summary>
    /// 此类为内部继承, 请勿继承此类
    /// </summary>
    public abstract class EmuCoreControllerKeyBinding
    {
        /// <summary> 所属核心 </summary>
        public abstract RomPlatformType Platform { get; }
        /// <summary> 控制器数量 </summary>
        public abstract int ControllerCount { get; }
    }

    /// <summary>
    /// 模拟器核心控制器键位绑定器
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class EmuCoreControllerKeyBinding<T> : EmuCoreControllerKeyBinding,
        IDeviceBinder<T, Keyboard_D>,
        IDeviceBinder<T, GamePad_D>,
        IDeviceBinder<T, DualShockController_D>,
        IDeviceBinder<T, XboxController_D>,
        IDeviceBinder<T, PSVController_D>
        where T : Enum
    {
        //每一个实例代表一个对应模拟器平台的控制器索引
        List<ControllerBinder> m_bindingPages = new List<ControllerBinder>();

        public EmuCoreControllerKeyBinding()
        {
            var types = GetType().GetInterfaces();

            for (int i = 0; i < ControllerCount; i++)
            {
                m_bindingPages.Add(new ControllerBinder(i, this));
            }

            foreach (var device in App.input.GetDevices())
            {
                foreach (var binding in m_bindingPages)
                {
                    binding.RegistInputDevice(device);
                }
            }

            App.input.OnDeviceLost += InputDevicesMgr_OnDeviceLost;
            App.input.OnDeviceConnected += InputDevicesMgr_OnDeviceConnected;
        }

        private void InputDevicesMgr_OnDeviceConnected(InputDevice_D connectDevice)
        {
            foreach (var binding in m_bindingPages)
            {
                binding.RegistInputDevice(connectDevice);
            }
        }

        private void InputDevicesMgr_OnDeviceLost(InputDevice_D lostDevice)
        {
            foreach (var binding in m_bindingPages)
            {
                binding.UnregistInputDevice(lostDevice);
            }
        }

        internal void RaiseDeviceRegist(InputDevice_D device, ControllerBinder binding)
        {
            if (device is Keyboard_D keyboard) Bind(keyboard, binding);
            else if (device is GamePad_D gamePad) Bind(gamePad, binding);
            else if (device is DualShockController_D dsC) Bind(dsC, binding);
            else if (device is XboxController_D xbC) Bind(xbC, binding);
            else if (device is PSVController_D psvC) Bind(psvC, binding);
            else throw new NotImplementedException($"{device.GetType()}");
        }

        public bool Start(T emuControl, int controllerIndex)
        {
            var binding = m_bindingPages[controllerIndex];
            foreach (var key in binding.GetBinding(emuControl))
            {
                if (key.Start) return true;
            }

            return false;
        }

        public bool Release(T emuControl, int controllerIndex)
        {
            var binding = m_bindingPages[controllerIndex];
            foreach (var key in binding.GetBinding(emuControl))
            {
                if (key.Release) return true;
            }

            return false;
        }

        /// <summary>
        /// 获取指定控件是否处于按下状态
        /// <para>如果绑定了多个物理按键,则只有这多个物理按键全部不处于按下状态时,才会返回false</para>
        /// </summary>
        /// <param name="emuControl"></param>
        /// <param name="controllerIndex"></param>
        /// <returns></returns>
        public bool GetKey(T emuControl, int controllerIndex)
        {
            var binding = m_bindingPages[controllerIndex];
            foreach (var key in binding.GetBinding(emuControl))
            {
                if (key.Performing) return true;
            }

            return false;
        }

        /// <summary>
        /// 获取调用帧是否有任意按键触发了按下操作
        /// </summary>
        /// <param name="controllerIndex"></param>
        /// <returns></returns>
        public bool AnyKeyDown(int controllerIndex)
        {
            var binding = m_bindingPages[controllerIndex];
            return binding.AnyKeyDown();
        }

        /// <summary>
        /// 获取指定控件的向量值
        /// <para>通常用于摇杆类型的控件</para>
        /// <para>如果同时绑定了多个物理输入设备,只会返回其中一个物理设备的向量值</para>
        /// </summary>
        /// <param name="emuControl">模拟器平台的具体键枚举</param>
        /// <param name="controllerIndex">模拟器平台的控制器序号</param>
        /// <returns></returns>
        public Vector2 GetVector2(T emuControl, int controllerIndex)
        {
            var binding = m_bindingPages[controllerIndex];
            foreach (var control in binding.GetBinding(emuControl))
            {
                if (!control.Performing) continue;

                return control.GetVector2();
            }

            return default(Vector2);
        }

        /// <summary>
        /// 获取指定控件的浮点值,取值范围为[0f,1f]
        /// <para>通常用于线性类按键,例如PS手柄的扳机键</para>
        /// <para>普通的按键也能读取这个值,但返回值只会有0f和1f两种值</para>
        /// <para>如果同时绑定了多个物理控件,则会从所有处于按下状态的物理控件中取平均值</para>
        /// </summary>
        /// <param name="emuControl">模拟器平台的具体键枚举</param>
        /// <param name="controllerIndex">模拟器平台的控制器序号</param>
        /// <returns></returns>
        public float GetFloat(T emuControl, int controllerIndex)
        {
            var totalFloat = 0f;
            var totalControl = 0;

            var binding = m_bindingPages[controllerIndex];
            foreach (var key in binding.GetBinding(emuControl))
            {
                if (!key.Performing) continue;

                totalControl++;
                totalFloat += key.GetFlaot();
            }

            if (totalControl == 0) return default(float);
            else return totalFloat / totalControl;
        }

        public class MapSetting : Dictionary<T, List<InputControl_C>> { }

        public class ControllerBinder
        {
            Dictionary<Type, InputDevice_D> m_registedDevices = new Dictionary<Type, InputDevice_D>();
            Dictionary<InputDevice_D, MapSetting> m_mapSetting = new Dictionary<InputDevice_D, MapSetting>();

            public int ControllerIndex { get; }
            public EmuCoreControllerKeyBinding<T> Host { get; }

            internal ControllerBinder(int controllerIndex, EmuCoreControllerKeyBinding<T> host)
            {
                ControllerIndex = controllerIndex;
                Host = host;
            }

            internal bool IsRegisted<DEVICE>() where DEVICE : InputDevice_D
            {
                var type = typeof(T);
                return IsRegisted(type);
            }
            internal bool IsRegisted(Type deviceType)
            {
                return m_registedDevices.ContainsKey(deviceType);
            }

            internal void RegistInputDevice(InputDevice_D device)
            {
                var type = device.GetType();
                if (IsRegisted(type)) return;

                m_registedDevices.Add(type, device);
                m_mapSetting[device] = new MapSetting();
                Host.RaiseDeviceRegist(device, this);
            }

            internal void UnregistInputDevice(InputDevice_D device)
            {
                var type = device.GetType();
                if (!IsRegisted(type)) return;

                m_registedDevices.Remove(type);
                m_mapSetting.Remove(device);
            }

            public void SetBinding(T emuBtn, InputControl_C key, int settingSlot)
            {
                var device = key.Device;
                m_registedDevices.TryGetValue(device.GetType(), out var inputDevice);

                Debug.Assert(inputDevice == device);

                var setting = m_mapSetting[inputDevice];
                if (!setting.TryGetValue(emuBtn, out var settingList))
                {
                    settingList = new List<InputControl_C>();
                    setting[emuBtn] = settingList;
                }

                int needFixCount = settingSlot - settingList.Count + 1;
                if (needFixCount > 0) for (int i = 0; i < needFixCount; i++) settingList.Add(null);

                settingList[settingSlot] = key;
            }

            public InputControl_C GetBinding(T emuBtn, InputDevice_D device, int settingSlot)
            {
                m_mapSetting.TryGetValue(device, out var mapSetting);
                if (mapSetting == null) return null;

                mapSetting.TryGetValue(emuBtn, out var settingList);
                if (settingList == null || settingSlot >= settingList.Count) return null;

                return settingList[settingSlot];
            }

            private List<InputControl_C> m_caches = new List<InputControl_C>();
            public IEnumerable<InputControl_C> GetBinding(T emuBtn)
            {
                m_caches.Clear();

                foreach (var mapSettings in m_mapSetting.Values)
                {
                    mapSettings.TryGetValue(emuBtn, out var bindControls);
                    if (bindControls != null)
                    {
                        m_caches.AddRange(bindControls);
                    }
                }

                return m_caches;
            }

            public bool AnyKeyDown()
            {
                foreach (var mapSettings in m_mapSetting.Values)
                {
                    foreach (var keys in mapSettings.Values)
                    {
                        foreach (var key in keys)
                        {
                            if (key.Start) return true;
                        }
                    }
                }

                return false;
            }
        }

        public abstract void Bind(Keyboard_D device, ControllerBinder controller);
        public abstract void Bind(GamePad_D device, ControllerBinder controller);
        public abstract void Bind(DualShockController_D device, ControllerBinder controller);
        public abstract void Bind(XboxController_D device, ControllerBinder controller);
        public abstract void Bind(PSVController_D device, ControllerBinder controller);
    }
}