#include <platform/input.h>
#include <platform/window.h>
#include <SDL.h>
#include <cstring>
#include <algorithm>

namespace extra2d {

InputSvc& InputSvc::inst() {
    static InputSvc instance;
    return instance;
}

bool InputSvc::init() {
    if (isInited()) return true;
    
    std::memset(keyState_.data(), 0, KEY_COUNT);
    std::memset(keyPrev_.data(), 0, KEY_COUNT);
    std::memset(mouseState_.data(), 0, static_cast<size_t>(MouseBtn::Count));
    std::memset(mousePrev_.data(), 0, static_cast<size_t>(MouseBtn::Count));
    
    for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
        gamepads_[i] = nullptr;
        std::memset(padState_[i].data(), 0, static_cast<size_t>(GamepadBtn::Count));
        std::memset(padPrev_[i].data(), 0, static_cast<size_t>(GamepadBtn::Count));
    }
    
    activeTouches_.clear();
    activeTouches_.reserve(MAX_TOUCHES);
    endedTouches_.clear();
    
    int32 numJoysticks = SDL_NumJoysticks();
    for (int32 i = 0; i < numJoysticks && i < MAX_GAMEPADS; ++i) {
        if (SDL_IsGameController(i)) {
            openGamepad(i);
        }
    }
    
    state_ = SvcState::Inited;
    return true;
}

void InputSvc::shutdown() {
    for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
        closeGamepad(i);
    }
    activeTouches_.clear();
    endedTouches_.clear();
    state_ = SvcState::Shutdown;
}

void InputSvc::update(float dt) {
    std::memcpy(keyPrev_.data(), keyState_.data(), KEY_COUNT);
    std::memcpy(mousePrev_.data(), mouseState_.data(), static_cast<size_t>(MouseBtn::Count));
    
    for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
        if (gamepads_[i]) {
            std::memcpy(padPrev_[i].data(), padState_[i].data(), static_cast<size_t>(GamepadBtn::Count));
        }
    }
    
    mouseWheel_ = 0;
    
    const uint8* state = SDL_GetKeyboardState(nullptr);
    std::memcpy(keyState_.data(), state, KEY_COUNT);
    
    uint32 btnState = SDL_GetMouseState(&mouseX_, &mouseY_);
    mouseState_[static_cast<size_t>(MouseBtn::Left)] = (btnState & SDL_BUTTON_LMASK) ? 1 : 0;
    mouseState_[static_cast<size_t>(MouseBtn::Middle)] = (btnState & SDL_BUTTON_MMASK) ? 1 : 0;
    mouseState_[static_cast<size_t>(MouseBtn::Right)] = (btnState & SDL_BUTTON_RMASK) ? 1 : 0;
    mouseState_[static_cast<size_t>(MouseBtn::X1)] = (btnState & SDL_BUTTON_X1MASK) ? 1 : 0;
    mouseState_[static_cast<size_t>(MouseBtn::X2)] = (btnState & SDL_BUTTON_X2MASK) ? 1 : 0;
    
    for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
        if (gamepads_[i]) {
            SDL_GameController* gc = gamepads_[i];
            padState_[i][static_cast<size_t>(GamepadBtn::A)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_A);
            padState_[i][static_cast<size_t>(GamepadBtn::B)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_B);
            padState_[i][static_cast<size_t>(GamepadBtn::X)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_X);
            padState_[i][static_cast<size_t>(GamepadBtn::Y)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_Y);
            padState_[i][static_cast<size_t>(GamepadBtn::Back)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_BACK);
            padState_[i][static_cast<size_t>(GamepadBtn::Guide)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_GUIDE);
            padState_[i][static_cast<size_t>(GamepadBtn::Start)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_START);
            padState_[i][static_cast<size_t>(GamepadBtn::LeftStick)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_LEFTSTICK);
            padState_[i][static_cast<size_t>(GamepadBtn::RightStick)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_RIGHTSTICK);
            padState_[i][static_cast<size_t>(GamepadBtn::LeftShoulder)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
            padState_[i][static_cast<size_t>(GamepadBtn::RightShoulder)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
            padState_[i][static_cast<size_t>(GamepadBtn::DPadUp)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_UP);
            padState_[i][static_cast<size_t>(GamepadBtn::DPadDown)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_DOWN);
            padState_[i][static_cast<size_t>(GamepadBtn::DPadLeft)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_LEFT);
            padState_[i][static_cast<size_t>(GamepadBtn::DPadRight)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
        }
    }
    
    endedTouches_.clear();
    
    for (auto& touch : activeTouches_) {
        if (touch.state == TouchState::Ended || touch.state == TouchState::Cancelled) {
            endedTouches_.push_back(touch);
        }
    }
    
    activeTouches_.erase(
        std::remove_if(activeTouches_.begin(), activeTouches_.end(),
            [](const TouchPoint& t) {
                return t.state == TouchState::Ended || t.state == TouchState::Cancelled;
            }),
        activeTouches_.end());
    
    for (auto& touch : activeTouches_) {
        touch.prevX = touch.x;
        touch.prevY = touch.y;
        touch.deltaX = 0.0f;
        touch.deltaY = 0.0f;
        if (touch.state == TouchState::Began) {
            touch.state = TouchState::Moved;
        }
    }
}

void InputSvc::processEvent(const SDL_Event& evt) {
    switch (evt.type) {
        case SDL_KEYDOWN:
            if (evt.key.repeat == 0 && onKeyDown_) {
                onKeyDown_(static_cast<Key>(evt.key.keysym.scancode));
            }
            break;
        case SDL_KEYUP:
            if (onKeyUp_) {
                onKeyUp_(static_cast<Key>(evt.key.keysym.scancode));
            }
            break;
        case SDL_MOUSEBUTTONDOWN:
            if (onMouseDown_) {
                onMouseDown_(static_cast<MouseBtn>(evt.button.button - 1), evt.button.x, evt.button.y);
            }
            break;
        case SDL_MOUSEBUTTONUP:
            if (onMouseUp_) {
                onMouseUp_(static_cast<MouseBtn>(evt.button.button - 1), evt.button.x, evt.button.y);
            }
            break;
        case SDL_MOUSEWHEEL:
            mouseWheel_ = evt.wheel.y;
            break;
        case SDL_CONTROLLERDEVICEADDED:
            openGamepad(evt.cdevice.which);
            break;
        case SDL_CONTROLLERDEVICEREMOVED:
            for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
                if (gamepads_[i] && SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamepads_[i])) == evt.cdevice.which) {
                    closeGamepad(i);
                    break;
                }
            }
            break;
        case SDL_FINGERDOWN:
            processTouchDown(evt.tfinger);
            break;
        case SDL_FINGERUP:
            processTouchUp(evt.tfinger);
            break;
        case SDL_FINGERMOTION:
            processTouchMotion(evt.tfinger);
            break;
    }
}

void InputSvc::processTouchDown(const SDL_TouchFingerEvent& evt) {
    if (static_cast<int32>(activeTouches_.size()) >= MAX_TOUCHES) return;
    
    int32 winW = 0, winH = 0;
    SDL_GetWindowSize(SDL_GetWindowFromID(evt.windowID), &winW, &winH);
    
    TouchPoint touch;
    touch.id = static_cast<int64>(evt.fingerId);
    touch.x = evt.x * static_cast<float>(winW);
    touch.y = evt.y * static_cast<float>(winH);
    touch.prevX = touch.x;
    touch.prevY = touch.y;
    touch.deltaX = 0.0f;
    touch.deltaY = 0.0f;
    touch.state = TouchState::Began;
    touch.pressure = evt.pressure;
    
    activeTouches_.push_back(touch);
    
    if (onTouchBegan_) {
        onTouchBegan_(touch);
    }
}

void InputSvc::processTouchUp(const SDL_TouchFingerEvent& evt) {
    int64 fingerId = static_cast<int64>(evt.fingerId);
    
    for (auto& touch : activeTouches_) {
        if (touch.id == fingerId) {
            int32 winW = 0, winH = 0;
            SDL_GetWindowSize(SDL_GetWindowFromID(evt.windowID), &winW, &winH);
            
            touch.x = evt.x * static_cast<float>(winW);
            touch.y = evt.y * static_cast<float>(winH);
            touch.deltaX = touch.x - touch.prevX;
            touch.deltaY = touch.y - touch.prevY;
            touch.state = TouchState::Ended;
            touch.pressure = evt.pressure;
            
            if (onTouchEnded_) {
                onTouchEnded_(touch);
            }
            break;
        }
    }
}

void InputSvc::processTouchMotion(const SDL_TouchFingerEvent& evt) {
    int64 fingerId = static_cast<int64>(evt.fingerId);
    
    for (auto& touch : activeTouches_) {
        if (touch.id == fingerId) {
            int32 winW = 0, winH = 0;
            SDL_GetWindowSize(SDL_GetWindowFromID(evt.windowID), &winW, &winH);
            
            touch.x = evt.x * static_cast<float>(winW);
            touch.y = evt.y * static_cast<float>(winH);
            touch.deltaX = touch.x - touch.prevX;
            touch.deltaY = touch.y - touch.prevY;
            touch.state = TouchState::Moved;
            touch.pressure = evt.pressure;
            
            if (onTouchMoved_) {
                onTouchMoved_(touch);
            }
            break;
        }
    }
}

void InputSvc::openGamepad(int32 idx) {
    if (idx < 0 || idx >= MAX_GAMEPADS) return;
    if (gamepads_[idx]) return;
    
    SDL_GameController* gc = SDL_GameControllerOpen(idx);
    if (gc) {
        gamepads_[idx] = gc;
        std::memset(padState_[idx].data(), 0, static_cast<size_t>(GamepadBtn::Count));
        std::memset(padPrev_[idx].data(), 0, static_cast<size_t>(GamepadBtn::Count));
    }
}

void InputSvc::closeGamepad(int32 idx) {
    if (idx < 0 || idx >= MAX_GAMEPADS) return;
    if (gamepads_[idx]) {
        SDL_GameControllerClose(gamepads_[idx]);
        gamepads_[idx] = nullptr;
    }
}

bool InputSvc::isKeyDown(Key key) const {
    if (key < 0 || key >= KEY_COUNT) return false;
    return keyState_[key] != 0;
}

bool InputSvc::isKeyPressed(Key key) const {
    if (key < 0 || key >= KEY_COUNT) return false;
    return keyState_[key] != 0 && keyPrev_[key] == 0;
}

bool InputSvc::isKeyReleased(Key key) const {
    if (key < 0 || key >= KEY_COUNT) return false;
    return keyState_[key] == 0 && keyPrev_[key] != 0;
}

void InputSvc::getMousePos(int32& x, int32& y) const {
    x = mouseX_;
    y = mouseY_;
}

void InputSvc::getMousePos(float& x, float& y) const {
    x = static_cast<float>(mouseX_);
    y = static_cast<float>(mouseY_);
}

bool InputSvc::isMouseBtnDown(MouseBtn btn) const {
    size_t idx = static_cast<size_t>(btn);
    if (idx >= static_cast<size_t>(MouseBtn::Count)) return false;
    return mouseState_[idx] != 0;
}

bool InputSvc::isMouseBtnPressed(MouseBtn btn) const {
    size_t idx = static_cast<size_t>(btn);
    if (idx >= static_cast<size_t>(MouseBtn::Count)) return false;
    return mouseState_[idx] != 0 && mousePrev_[idx] == 0;
}

bool InputSvc::isMouseBtnReleased(MouseBtn btn) const {
    size_t idx = static_cast<size_t>(btn);
    if (idx >= static_cast<size_t>(MouseBtn::Count)) return false;
    return mouseState_[idx] == 0 && mousePrev_[idx] != 0;
}

int32 InputSvc::getMouseWheel() const {
    return mouseWheel_;
}

int32 InputSvc::touchCount() const {
    return static_cast<int32>(activeTouches_.size());
}

const TouchPoint* InputSvc::getTouch(int32 idx) const {
    if (idx < 0 || idx >= static_cast<int32>(activeTouches_.size())) return nullptr;
    return &activeTouches_[idx];
}

const TouchPoint* InputSvc::getTouchById(int64 id) const {
    for (const auto& touch : activeTouches_) {
        if (touch.id == id) return &touch;
    }
    return nullptr;
}

int32 InputSvc::gamepadCount() const {
    int32 count = 0;
    for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
        if (gamepads_[i]) ++count;
    }
    return count;
}

bool InputSvc::isGamepadBtnDown(int32 idx, GamepadBtn btn) const {
    if (idx < 0 || idx >= MAX_GAMEPADS) return false;
    if (!gamepads_[idx]) return false;
    size_t b = static_cast<size_t>(btn);
    if (b >= static_cast<size_t>(GamepadBtn::Count)) return false;
    return padState_[idx][b] != 0;
}

bool InputSvc::isGamepadBtnPressed(int32 idx, GamepadBtn btn) const {
    if (idx < 0 || idx >= MAX_GAMEPADS) return false;
    if (!gamepads_[idx]) return false;
    size_t b = static_cast<size_t>(btn);
    if (b >= static_cast<size_t>(GamepadBtn::Count)) return false;
    return padState_[idx][b] != 0 && padPrev_[idx][b] == 0;
}

float InputSvc::getGamepadAxis(int32 idx, GamepadAxis axis) const {
    if (idx < 0 || idx >= MAX_GAMEPADS) return 0.0f;
    if (!gamepads_[idx]) return 0.0f;
    
    SDL_GameControllerAxis sdlAxis = SDL_CONTROLLER_AXIS_INVALID;
    switch (axis) {
        case GamepadAxis::LeftX: sdlAxis = SDL_CONTROLLER_AXIS_LEFTX; break;
        case GamepadAxis::LeftY: sdlAxis = SDL_CONTROLLER_AXIS_LEFTY; break;
        case GamepadAxis::RightX: sdlAxis = SDL_CONTROLLER_AXIS_RIGHTX; break;
        case GamepadAxis::RightY: sdlAxis = SDL_CONTROLLER_AXIS_RIGHTY; break;
        case GamepadAxis::TriggerLeft: sdlAxis = SDL_CONTROLLER_AXIS_TRIGGERLEFT; break;
        case GamepadAxis::TriggerRight: sdlAxis = SDL_CONTROLLER_AXIS_TRIGGERRIGHT; break;
        default: return 0.0f;
    }
    
    int16 value = SDL_GameControllerGetAxis(gamepads_[idx], sdlAxis);
    return static_cast<float>(value) / 32767.0f;
}

} // namespace extra2d
