#include <Core/Common/CoreUtils.h>
#include <Core/Common/EnumUtils.h>
#include <Core/HAL/Event/KeyboardMapping.h>
#include <Core/HAL/Keyboard/KeyCode.h>
#include <Core/HAL/Keyboard/ScanCode.h>

namespace Juliet
{
    namespace
    {
        // clang-format off
        KeyCode UnshiftedDefaultSymbols[] = {
            KeyCode::Num1,
            KeyCode::Num2,
            KeyCode::Num3,
            KeyCode::Num4,
            KeyCode::Num5,
            KeyCode::Num6,
            KeyCode::Num7,
            KeyCode::Num8,
            KeyCode::Num9,
            KeyCode::Num0,
            KeyCode::Return,
            KeyCode::Escape,
            KeyCode::Backspace,
            KeyCode::Tab,
            KeyCode::Space,
            KeyCode::Minus,
            KeyCode::Equals,
            KeyCode::LeftBracket,
            KeyCode::RightBracket,
            KeyCode::Backslash,
            KeyCode::Hash,
            KeyCode::Semicolon,
            KeyCode::Apostrophe,
            KeyCode::GraveAccent,
            KeyCode::Comma,
            KeyCode::Period,
            KeyCode::Slash,
        };

        KeyCode ShiftedDefaultSymbols[] = {
            KeyCode::ExclamationPoint,
            KeyCode::CommercialAt,
            KeyCode::Hash,
            KeyCode::Dollar,
            KeyCode::Percent,
            KeyCode::Caret,
            KeyCode::Ampersand,
            KeyCode::Asterisk,
            KeyCode::LeftParenthesis,
            KeyCode::RightParenthesis,
            KeyCode::Return,
            KeyCode::Escape,
            KeyCode::Backspace,
            KeyCode::Tab,
            KeyCode::Space,
            KeyCode::Underscore,
            KeyCode::Plus,
            KeyCode::LeftBrace,
            KeyCode::RightBrace,
            KeyCode::Pipe,
            KeyCode::Hash,
            KeyCode::Colon,
            KeyCode::DoubleApostrophe,
            KeyCode::Tilde,
            KeyCode::LessThan,
            KeyCode::GreaterThan,
            KeyCode::QuestionMark,
        };
        // clang-format on

        KeyCode GetNonPrintableKeys(ScanCode scanCode)
        {
            switch (scanCode)
            {
            case ScanCode::Delete: return KeyCode::Delete;
            case ScanCode::CapsLock: return KeyCode::CapsLock;
            case ScanCode::F1: return KeyCode::F1;
            case ScanCode::F2: return KeyCode::F2;
            case ScanCode::F3: return KeyCode::F3;
            case ScanCode::F4: return KeyCode::F4;
            case ScanCode::F5: return KeyCode::F5;
            case ScanCode::F6: return KeyCode::F6;
            case ScanCode::F7: return KeyCode::F7;
            case ScanCode::F8: return KeyCode::F8;
            case ScanCode::F9: return KeyCode::F9;
            case ScanCode::F10: return KeyCode::F10;
            case ScanCode::F11: return KeyCode::F11;
            case ScanCode::F12: return KeyCode::F12;
            case ScanCode::PrintScreen: return KeyCode::PrintScreen;
            case ScanCode::ScrollLock: return KeyCode::ScrollLock;
            case ScanCode::Pause: return KeyCode::Pause;
            case ScanCode::Insert: return KeyCode::Insert;
            case ScanCode::Home: return KeyCode::Home;
            case ScanCode::PageUp: return KeyCode::PageUp;
            case ScanCode::End: return KeyCode::End;
            case ScanCode::PageDown: return KeyCode::PageDown;
            case ScanCode::RightArrow: return KeyCode::RightArrow;
            case ScanCode::LeftArrow: return KeyCode::LeftArrow;
            case ScanCode::DownArrow: return KeyCode::DownArrow;
            case ScanCode::UpArrow: return KeyCode::UpArrow;
            case ScanCode::NumlockClear: return KeyCode::NumlockClear;
            case ScanCode::KeyPad_Divide: return KeyCode::KeyPad_Divide;
            case ScanCode::KeyPad_Multiply: return KeyCode::KeyPad_Multiply;
            case ScanCode::KeyPad_Minus: return KeyCode::KeyPad_Minus;
            case ScanCode::KeyPad_Plus: return KeyCode::KeyPad_Plus;
            case ScanCode::KeyPad_Enter: return KeyCode::KeyPad_Enter;
            case ScanCode::KeyPad_Num1: return KeyCode::KeyPad_Num1;
            case ScanCode::KeyPad_Num2: return KeyCode::KeyPad_Num2;
            case ScanCode::KeyPad_Num3: return KeyCode::KeyPad_Num3;
            case ScanCode::KeyPad_Num4: return KeyCode::KeyPad_Num4;
            case ScanCode::KeyPad_Num5: return KeyCode::KeyPad_Num5;
            case ScanCode::KeyPad_Num6: return KeyCode::KeyPad_Num6;
            case ScanCode::KeyPad_Num7: return KeyCode::KeyPad_Num7;
            case ScanCode::KeyPad_Num8: return KeyCode::KeyPad_Num8;
            case ScanCode::KeyPad_Num9: return KeyCode::KeyPad_Num9;
            case ScanCode::KeyPad_Num0: return KeyCode::KeyPad_Num0;
            case ScanCode::KeyPad_Period: return KeyCode::KeyPad_Period;
            case ScanCode::Power: return KeyCode::Power;
            case ScanCode::KeyPad_Equals: return KeyCode::KeyPad_Equals;
            case ScanCode::F13: return KeyCode::F13;
            case ScanCode::F14: return KeyCode::F14;
            case ScanCode::F15: return KeyCode::F15;
            case ScanCode::F16: return KeyCode::F16;
            case ScanCode::F17: return KeyCode::F17;
            case ScanCode::F18: return KeyCode::F18;
            case ScanCode::F19: return KeyCode::F19;
            case ScanCode::F20: return KeyCode::F20;
            case ScanCode::F21: return KeyCode::F21;
            case ScanCode::F22: return KeyCode::F22;
            case ScanCode::F23: return KeyCode::F23;
            case ScanCode::F24: return KeyCode::F24;
            case ScanCode::Mute: return KeyCode::Mute;
            case ScanCode::VolumeUp: return KeyCode::VolumeUp;
            case ScanCode::VolumeDown: return KeyCode::VolumeDown;
            case ScanCode::KeyPad_Comma: return KeyCode::KeyPad_Comma;
            case ScanCode::LeftControl: return KeyCode::LeftControl;
            case ScanCode::LeftShift: return KeyCode::LeftShift;
            case ScanCode::LeftAlt: return KeyCode::LeftAlt;
            case ScanCode::LeftOSCommand: return KeyCode::LeftOSCommand;
            case ScanCode::RightControl: return KeyCode::RightControl;
            case ScanCode::RightShift: return KeyCode::RightShift;
            case ScanCode::RightAlt: return KeyCode::RightAlt;
            case ScanCode::RightOSCommand: return KeyCode::RightOSCommand;
            case ScanCode::Sleep: return KeyCode::Sleep;
            case ScanCode::WakeUp: return KeyCode::WakeUp;
            case ScanCode::Media_NextTrack: return KeyCode::Media_NextTrack;
            case ScanCode::Media_PreviousTrack: return KeyCode::Media_PreviousTrack;
            case ScanCode::Media_Stop: return KeyCode::Media_Stop;
            case ScanCode::Media_Eject: return KeyCode::Media_Eject;
            case ScanCode::Media_PlayPause: return KeyCode::Media_PlayPause;
            case ScanCode::Media_Select: return KeyCode::Media_Select;
            default: return KeyCode::Unknown;
            }
        }
    } // namespace

    KeyCode GetKeyCodeFromDefaultMapping(ScanCode scanCode, KeyMod keyModState)
    {
        if (scanCode <= ScanCode::Unknown || scanCode > ScanCode::Count)
        {
            Assert(false , "Unsupported KeyCode (out of bounds)");
            return KeyCode::Unknown;
        }

        if (scanCode < ScanCode::A)
        {
            return KeyCode::Unknown;
        }

        // Handles A-Z characters
        if (scanCode <= ScanCode::Z)
        {
            const auto index          = scanCode - ScanCode::A;
            bool       isShiftPressed = (keyModState & KeyMod::Shift) != KeyMod::None;
            if ((keyModState & KeyMod::CapsLock) != KeyMod::None)
            {
                isShiftPressed = !isShiftPressed;
            }

            if (isShiftPressed)
            {
                return ToEnum<KeyCode>(static_cast<uint32>('A') + index);
            }
            return ToEnum<KeyCode>(static_cast<uint32>('a') + index);
        }

        // Handles Num1 to Num0
        if (scanCode <= ScanCode::Num0)
        {
            const auto index          = scanCode - ScanCode::Num1;
            const bool isShiftPressed = (keyModState & KeyMod::Shift) != KeyMod::None;
            if (isShiftPressed)
            {
                return ShiftedDefaultSymbols[index];
            }
            return UnshiftedDefaultSymbols[index];
        }

        // Handle everything else (characters that do not convert to ASCII code)
        return GetNonPrintableKeys(scanCode);
    }
} // namespace Juliet
