#include "input_unified.h"

namespace fn::input {

namespace {

constexpr float STICK_DEADZONE = 0.15f;
constexpr float STICK_MAX = 32767.0f;

float normalize_axis(int16_t v) {
    float f = (float)v / STICK_MAX;
    if (f > 1.0f) f = 1.0f;
    if (f < -1.0f) f = -1.0f;
    if (f > -STICK_DEADZONE && f < STICK_DEADZONE) return 0.0f;
    return f;
}

// Set logical button + rising-edge flag if transitioning false->true.
void set_btn(bool& held, bool& pressed, bool down) {
    if (down && !held) pressed = true;
    held = down;
}

void apply_key(InputState& s, SDL_Keycode key, bool down) {
    switch (key) {
        case SDLK_A: case SDLK_LEFT:  set_btn(s.left,  s.left_pressed,  down); break;
        case SDLK_D: case SDLK_RIGHT: set_btn(s.right, s.right_pressed, down); break;
        case SDLK_W: case SDLK_UP:    set_btn(s.up,    s.up_pressed,    down); break;
        case SDLK_S: case SDLK_DOWN:  set_btn(s.down,  s.down_pressed,  down); break;
        case SDLK_SPACE:  set_btn(s.action_a, s.a_pressed, down); break;
        case SDLK_RETURN: set_btn(s.start, s.start_pressed, down); break;
        case SDLK_ESCAPE: set_btn(s.back,  s.back_pressed,  down); break;
        default: break;
    }
}

void apply_gpad_button(InputState& s, Uint8 button, bool down) {
    switch (button) {
        case SDL_GAMEPAD_BUTTON_DPAD_LEFT:  set_btn(s.left,  s.left_pressed,  down); break;
        case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: set_btn(s.right, s.right_pressed, down); break;
        case SDL_GAMEPAD_BUTTON_DPAD_UP:    set_btn(s.up,    s.up_pressed,    down); break;
        case SDL_GAMEPAD_BUTTON_DPAD_DOWN:  set_btn(s.down,  s.down_pressed,  down); break;
        case SDL_GAMEPAD_BUTTON_SOUTH: set_btn(s.action_a, s.a_pressed, down); break;
        case SDL_GAMEPAD_BUTTON_EAST:  set_btn(s.action_b, s.b_pressed, down); break;
        case SDL_GAMEPAD_BUTTON_WEST:  set_btn(s.action_x, s.x_pressed, down); break;
        case SDL_GAMEPAD_BUTTON_NORTH: set_btn(s.action_y, s.y_pressed, down); break;
        case SDL_GAMEPAD_BUTTON_START: set_btn(s.start, s.start_pressed, down); break;
        case SDL_GAMEPAD_BUTTON_BACK:  set_btn(s.back,  s.back_pressed,  down); break;
        default: break;
    }
}

int find_touch_slot(InputState& s, int id) {
    for (int i = 0; i < s.touch_count; ++i) {
        if (s.touches[i].id == id) return i;
    }
    return -1;
}

}  // namespace

void input_begin_frame(InputState& s) {
    s.left_pressed = s.right_pressed = s.up_pressed = s.down_pressed = false;
    s.a_pressed = s.b_pressed = s.x_pressed = s.y_pressed = false;
    s.start_pressed = s.back_pressed = false;
    s.m_left_pressed = s.m_right_pressed = false;
}

void input_process_event(InputState& s, const SDL_Event* e) {
    if (!e) return;

    switch (e->type) {
        case SDL_EVENT_KEY_DOWN:
            if (!e->key.repeat) apply_key(s, e->key.key, true);
            break;
        case SDL_EVENT_KEY_UP:
            apply_key(s, e->key.key, false);
            break;

        case SDL_EVENT_MOUSE_MOTION:
            s.mx = e->motion.x;
            s.my = e->motion.y;
            break;
        case SDL_EVENT_MOUSE_BUTTON_DOWN:
            if (e->button.button == SDL_BUTTON_LEFT)  set_btn(s.m_left,  s.m_left_pressed,  true);
            if (e->button.button == SDL_BUTTON_RIGHT) set_btn(s.m_right, s.m_right_pressed, true);
            break;
        case SDL_EVENT_MOUSE_BUTTON_UP:
            if (e->button.button == SDL_BUTTON_LEFT)  set_btn(s.m_left,  s.m_left_pressed,  false);
            if (e->button.button == SDL_BUTTON_RIGHT) set_btn(s.m_right, s.m_right_pressed, false);
            break;

        case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
            apply_gpad_button(s, e->gbutton.button, true);
            break;
        case SDL_EVENT_GAMEPAD_BUTTON_UP:
            apply_gpad_button(s, e->gbutton.button, false);
            break;
        case SDL_EVENT_GAMEPAD_AXIS_MOTION: {
            float v = normalize_axis(e->gaxis.value);
            switch (e->gaxis.axis) {
                case SDL_GAMEPAD_AXIS_LEFTX:  s.lx = v; break;
                case SDL_GAMEPAD_AXIS_LEFTY:  s.ly = v; break;
                case SDL_GAMEPAD_AXIS_RIGHTX: s.rx = v; break;
                case SDL_GAMEPAD_AXIS_RIGHTY: s.ry = v; break;
                default: break;
            }
            break;
        }
        case SDL_EVENT_GAMEPAD_ADDED:
            SDL_OpenGamepad(e->gdevice.which);
            break;

        case SDL_EVENT_FINGER_DOWN: {
            if (s.touch_count < 8) {
                auto& t = s.touches[s.touch_count++];
                t.id = (int)e->tfinger.fingerID;
                t.x = e->tfinger.x;
                t.y = e->tfinger.y;
                t.pressed = true;
            }
            break;
        }
        case SDL_EVENT_FINGER_MOTION: {
            int idx = find_touch_slot(s, (int)e->tfinger.fingerID);
            if (idx >= 0) {
                s.touches[idx].x = e->tfinger.x;
                s.touches[idx].y = e->tfinger.y;
            }
            break;
        }
        case SDL_EVENT_FINGER_UP: {
            int idx = find_touch_slot(s, (int)e->tfinger.fingerID);
            if (idx >= 0) {
                // compact array
                for (int i = idx; i < s.touch_count - 1; ++i) {
                    s.touches[i] = s.touches[i + 1];
                }
                s.touch_count--;
                s.touches[s.touch_count] = {};
            }
            break;
        }

        default: break;
    }
}

}  // namespace fn::input
