#pragma once
#include "../GPU/gpu_types.hpp"
#include "raw_controller.hpp"

namespace JabyEngine {
    namespace Periphery {
        class GenericController : public RawController {
        public:
            struct Rumble {
                static constexpr uint8_t LargeMotorThreshold = 0x60;
            };

            enum struct Button : uint16_t {
                L2       = static_cast<uint16_t>(GenericButton::D0),
                R2       = static_cast<uint16_t>(GenericButton::D1),
                L1       = static_cast<uint16_t>(GenericButton::D2),
                R1       = static_cast<uint16_t>(GenericButton::D3),
                Triangle = static_cast<uint16_t>(GenericButton::D4),
                Circle   = static_cast<uint16_t>(GenericButton::D5),
                Cross    = static_cast<uint16_t>(GenericButton::D6),
                Square   = static_cast<uint16_t>(GenericButton::D7),
                SEL      = static_cast<uint16_t>(GenericButton::D8),
                ST       = static_cast<uint16_t>(GenericButton::D11),
                Up       = static_cast<uint16_t>(GenericButton::D12),
                Right    = static_cast<uint16_t>(GenericButton::D13),
                Down     = static_cast<uint16_t>(GenericButton::D14),
                Left     = static_cast<uint16_t>(GenericButton::D15)
            };

            void set_digital_rumble() {
                RawController::header.rumble0 = 0x1;
                RawController::header.rumble1 = 0x7F;
            }

            void set_analog_rumble(uint8_t largeMotor, bool smallMotor) {
                RawController::header.rumble0 = smallMotor ? 0x1 : 0x0;
                RawController::header.rumble1 = largeMotor;
            }

            void stopRumble() {
                RawController::header.rumble0 = 0x0;
                RawController::header.rumble1 = 0x0;
            }

            bool is_small_rumble() const {
                return static_cast<bool>(RawController::header.rumble0);
            }

            uint8_t get_large_rumble() const {
                return RawController::header.rumble1;
            }

            bool is_connected() const {
                return RawController::header.state != RawController::State::Disconnected;
            }

            bool is_useable() const {
                const auto type = RawController::get_type();
                return ((RawController::header.state == RawController::State::Stable) && (type == ControllerType::Controller || type == ControllerType::DualShock));
            }
        };

        class AnalogeController : public GenericController {
        public:
            enum struct Button : uint16_t {
                L2       = static_cast<uint16_t>(GenericButton::D0),
                R2       = static_cast<uint16_t>(GenericButton::D1),
                L1       = static_cast<uint16_t>(GenericButton::D2),
                R1       = static_cast<uint16_t>(GenericButton::D3),
                Triangle = static_cast<uint16_t>(GenericButton::D4),
                Circle   = static_cast<uint16_t>(GenericButton::D5),
                Cross    = static_cast<uint16_t>(GenericButton::D6),
                Square   = static_cast<uint16_t>(GenericButton::D7),
                SEL      = static_cast<uint16_t>(GenericButton::D8),
                L3       = static_cast<uint16_t>(GenericButton::D9),
                R3       = static_cast<uint16_t>(GenericButton::D10),
                ST       = static_cast<uint16_t>(GenericButton::D11),
                Up       = static_cast<uint16_t>(GenericButton::D12),
                Right    = static_cast<uint16_t>(GenericButton::D13),
                Down     = static_cast<uint16_t>(GenericButton::D14),
                Left     = static_cast<uint16_t>(GenericButton::D15)
            };

        private:
            uint8_t read_special_byte(size_t idx) const {
                return reinterpret_cast<const uint8_t(&)[4]>(this->special)[idx];
            }

        public:
            GPU::PositionI16 get_right_stick_pos() const {
                const uint8_t joy_x = AnalogeController::read_special_byte(0);
                const uint8_t joy_y = AnalogeController::read_special_byte(1);

                return GPU::PositionI16::create(joy_x - 0x80, joy_y - 0x80);
            }

            GPU::PositionI16 get_left_stick_pos() const {
                const uint8_t joy_x = AnalogeController::read_special_byte(2);
                const uint8_t joy_y = AnalogeController::read_special_byte(3);
                
                return GPU::PositionI16::create(joy_x - 0x80, joy_y - 0x80);
            }
        };

        using GenericButtonState = GenericController::ButtonState;
    }
}