using static Ryujinx.HLE.Input.Hid;

namespace Ryujinx.HLE.Input
{
    public abstract class BaseController : IHidDevice
    {
        protected ControllerStatus HidControllerType;
        protected ControllerId     ControllerId;

        private long _currentLayoutOffset;
        private long _mainLayoutOffset;

        protected long DeviceStateOffset => Offset + 0x4188;

        protected Switch Device { get; }

        public long Offset    { get; private set; }
        public bool Connected { get; protected set; }

        public ControllerHeader            Header             { get; private set; }
        public ControllerStateHeader       CurrentStateHeader { get; private set; }
        public ControllerDeviceState       DeviceState        { get; private set; }
        public ControllerLayouts           CurrentLayout      { get; private set; }
        public ControllerState             LastInputState     { get; set; }
        public ControllerConnectionState   ConnectionState    { get; protected set; }

        public BaseController(Switch device, ControllerStatus controllerType)
        {
            Device            = device;
            HidControllerType = controllerType;
        }

        protected void Initialize(
            bool isHalf,
            (NpadColor left, NpadColor right) bodyColors,
            (NpadColor left, NpadColor right) buttonColors,
            ControllerColorDescription        singleColorDesc   = 0,
            ControllerColorDescription        splitColorDesc    = 0,
            NpadColor                         singleBodyColor   = 0,
            NpadColor                         singleButtonColor = 0
            )
        {
            Header = new ControllerHeader()
            {
                IsJoyConHalf           = isHalf ? 1 : 0,
                LeftBodyColor          = bodyColors.left,
                LeftButtonColor        = buttonColors.left,
                RightBodyColor         = bodyColors.right,
                RightButtonColor       = buttonColors.right,
                Status                 = HidControllerType,
                SingleBodyColor        = singleBodyColor,
                SingleButtonColor      = singleButtonColor,
                SplitColorDescription  = splitColorDesc,
                SingleColorDescription = singleColorDesc,
            };

            CurrentStateHeader = new ControllerStateHeader
            {
                EntryCount        = HidEntryCount,
                MaxEntryCount     = HidEntryCount - 1,
                CurrentEntryIndex = -1
            };

            DeviceState = new ControllerDeviceState()
            {
                PowerInfo0BatteryState = BatteryState.Percent100,
                PowerInfo1BatteryState = BatteryState.Percent100,
                PowerInfo2BatteryState = BatteryState.Percent100,
                DeviceType             = ControllerDeviceType.NPadLeftController | ControllerDeviceType.NPadRightController,
                DeviceFlags            = DeviceFlags.PowerInfo0Connected
                                            | DeviceFlags.PowerInfo1Connected
                                            | DeviceFlags.PowerInfo2Connected
            };

            LastInputState = new ControllerState()
            {
                SamplesTimestamp  = -1,
                SamplesTimestamp2 = -1
            };
        }

        public virtual void Connect(ControllerId controllerId)
        {
            ControllerId = controllerId;

            Offset = Device.Hid.HidPosition + HidControllersOffset + (int)controllerId * HidControllerSize;

            _mainLayoutOffset = Offset + HidControllerHeaderSize
                + ((int)ControllerLayouts.Main * HidControllerLayoutsSize);

            Device.Memory.FillWithZeros(Offset, 0x5000);
            Device.Memory.WriteStruct(Offset, Header);
            Device.Memory.WriteStruct(DeviceStateOffset, DeviceState);

            Connected = true;
        }

        public void SetLayout(ControllerLayouts controllerLayout)
        {
            CurrentLayout = controllerLayout;

            _currentLayoutOffset = Offset + HidControllerHeaderSize
                + ((int)controllerLayout * HidControllerLayoutsSize);
        }

        public void SendInput(
            ControllerButtons buttons,
            JoystickPosition  leftStick,
            JoystickPosition  rightStick)
        {
            ControllerState currentInput = new ControllerState()
            {
                SamplesTimestamp  = (long)LastInputState.SamplesTimestamp + 1,
                SamplesTimestamp2 = (long)LastInputState.SamplesTimestamp + 1,
                ButtonState       = buttons,
                ConnectionState   = ConnectionState,
                LeftStick         = leftStick,
                RightStick        = rightStick
            };

            ControllerStateHeader newInputStateHeader = new ControllerStateHeader
            {
                EntryCount        = HidEntryCount,
                MaxEntryCount     = HidEntryCount - 1,
                CurrentEntryIndex = (CurrentStateHeader.CurrentEntryIndex + 1) % HidEntryCount,
                Timestamp         = GetTimestamp(),
            };

            Device.Memory.WriteStruct(_currentLayoutOffset, newInputStateHeader);
            Device.Memory.WriteStruct(_mainLayoutOffset,    newInputStateHeader);

            long currentInputStateOffset = HidControllersLayoutHeaderSize
                + newInputStateHeader.CurrentEntryIndex * HidControllersInputEntrySize;

            Device.Memory.WriteStruct(_currentLayoutOffset + currentInputStateOffset, currentInput);
            Device.Memory.WriteStruct(_mainLayoutOffset + currentInputStateOffset,    currentInput);

            LastInputState     = currentInput;
            CurrentStateHeader = newInputStateHeader;
        }
    }
}