#ifndef H_INPUT
#define H_INPUT

#include "core.h"
#include "utils.h"

#define MAX_PLAYERS            COUNT(Core::settings.controls)

#define INPUT_JOY_COUNT        4
#define INPUT_JOY_DZ_STICK     0.3f
#define INPUT_JOY_DZ_TRIGGER   0.01f

namespace Input {
    InputKey lastKey;
    bool down[ikMAX];
    bool state[MAX_PLAYERS][cMAX];
    ControlKey lastState[MAX_PLAYERS];

    struct Mouse {
        vec2 pos;
        struct {
            vec2 L, R, M;
        } start;
    } mouse;

    struct Joystick {
        vec2   L, R;
        float  LT, RT;
        JoyKey lastKey;
        bool   down[jkMAX];
    } joy[INPUT_JOY_COUNT];

    struct Touch {
        int  id;
        vec2 start;
        vec2 pos;
    } touch[6];

    struct HMD {
        mat4 head;
        mat4 eye[2];
        mat4 proj[2];
        mat4 controllers[2];
        vec3 zero;
        bool ready;
        bool state[cMAX];

        void setView(const mat4 &pL, const mat4 &pR, const mat4 &vL, const mat4 &vR) {
            proj[0] = pL;
            proj[1] = pR;
            eye[0]  = vL;
            eye[1]  = vR;
        }

        void reset() {
            eye[0].identity();
            eye[1].identity();
            proj[0].identity();
            proj[1].identity();
        }
    } hmd;

    enum TouchButton { bMove, bWeapon, bWalk, bAction, bJump, bInventory, bMAX };
    enum TouchZone   { zMove, zLook, zButton, zMAX };

    float       touchTimerVis, touchTimerTap;
    InputKey    touchKey[zMAX];

    TouchButton btn;
    vec2        btnPos[bMAX];
    bool        btnEnable[bMAX];
    float       btnRadius;
    bool        doubleTap;

    void setDown(InputKey key, bool value, int index = 0) {
        if (down[key] == value)
            return;

        if (value)
            switch (key) {
                case ikMouseL : mouse.start.L = mouse.pos;  break;
                case ikMouseR : mouse.start.R = mouse.pos;  break;
                case ikMouseM : mouse.start.M = mouse.pos;  break;
                case ikTouchA :
                case ikTouchB :
                case ikTouchC :
                case ikTouchD :
                case ikTouchE :
                case ikTouchF : touch[key - ikTouchA].start = touch[key - ikTouchA].pos; break;
                default       : ;
            }
        down[key] = value;

        if (value && key <= ikBack) {
            lastKey = key;
            touchTimerVis = 0.0f;
        }
    }

    void setPos(InputKey key, const vec2 &pos) {
        switch (key) {
            case ikMouseL :
            case ikMouseR :
            case ikMouseM : mouse.pos      = pos;         return;
            case ikTouchA :
            case ikTouchB :
            case ikTouchC :
            case ikTouchD :
            case ikTouchE :
            case ikTouchF : touch[key - ikTouchA].pos = pos; return;
            default       : return;
        }
    }

    void setJoyDown(int index, JoyKey key, bool value) {
        if (joy[index].down[key] == value)
            return;

        joy[index].down[key] = value;

        if (value) {
            joy[index].lastKey = key;
            touchTimerVis = 0.0f;
        }
    }

    void setJoyPos(int index, JoyKey key, const vec2 &pos) {
        switch (key) {
            case jkL   : joy[index].L   = pos;         return;
            case jkR   : joy[index].R   = pos;         return;
            case jkLT  : joy[index].LT  = pos.x;       break;
            case jkRT  : joy[index].RT  = pos.x;       break;
            default    : return;
        }
        setJoyDown(index, key, pos.x > EPS); // gamepad LT, RT auto-down state
    }

    void setJoyVibration(int playerIndex, float L, float R) {
        if (!Core::settings.controls[playerIndex].vibration)
            return;
        osJoyVibrate(Core::settings.controls[playerIndex].joyIndex, L, R);
    }
    
    void stopJoyVibration() {
        osJoyVibrate(Core::settings.controls[0].joyIndex, 0.0f, 0.0f);
        osJoyVibrate(Core::settings.controls[1].joyIndex, 0.0f, 0.0f);
    }

    InputKey getTouch(int id) {
        for (int i = 0; i < COUNT(touch); i++)
            if (down[ikTouchA + i] && touch[i].id == id)
                return InputKey(ikTouchA + i);

        for (int i = 0; i < COUNT(touch); i++)
            if (!down[ikTouchA + i]) {
                touch[i].id = id;
                return InputKey(ikTouchA + i);
            }

        return ikNone;
    }

    void reset() {
        memset(down,    0, sizeof(down));
        memset(&mouse,  0, sizeof(mouse));
        memset(&joy,    0, sizeof(joy));
        memset(&touch,  0, sizeof(touch));
        hmd.reset();
    }

    void init() {
        reset();
        hmd.ready        = false;
        hmd.zero         = vec3(INF, INF, INF);
        touchTimerVis    = 0.0f;
        touchTimerTap    = 0.0f;
        doubleTap        = false;
        touchKey[zMove]  = touchKey[zLook] = touchKey[zButton] = ikNone;
        memset(btnEnable, 1, sizeof(btnEnable));
    }

    bool checkTouchZone(TouchZone zone) {
        InputKey &t = touchKey[zone];
        if (t != ikNone && !down[t]) {
            t = ikNone;
            return true;
        }
        return false;
    }

    void getTouchDir(InputKey key, vec2 &dir) {
        vec2 delta = vec2(0.0f);
        if (key == ikNone)
            return;

        Touch &t = touch[key - ikTouchA];
        vec2 d = t.pos - t.start;                
        float len = d.length();
        if (len > EPS)
            delta = d * (min(len / 100.0f, 1.0f) / len);

        dir = delta;
    }

    void setState(int playerIndex, ControlKey key, bool down) {
        if (down && !state[playerIndex][key])
            lastState[playerIndex] = key;
        state[playerIndex][key] = down;
    }

    void update() {
        bool newState[MAX_PLAYERS][cMAX];

        for (int j = 0; j < COUNT(Core::settings.controls); j++) {
            lastState[j] = cMAX;
            Core::Settings::Controls &ctrl = Core::settings.controls[j];
            for (int i = 0; i < cMAX; i++) {
                KeySet &c = ctrl.keys[i];
                newState[j][i] = (c.key != ikNone && down[c.key]) || (c.joy != jkNone && joy[ctrl.joyIndex].down[c.joy]) || hmd.state[i];
            }
        }

    // update touch controls
        if (touchTimerTap > 0.0f)
            touchTimerTap = max(0.0f, touchTimerTap - Core::deltaTime);

        if (touchKey[zMove] != ikNone || touchKey[zLook] != ikNone || touchKey[zButton] != ikNone)
            touchTimerVis = 30.0f;
        else
            if (touchTimerVis > 0.0f)
                touchTimerVis = max(0.0f, touchTimerVis - Core::deltaTime);

    // update buttons
        float offset = Core::height * 0.25f;
        float radius = offset; 
        vec2  center = vec2(Core::width - offset * 0.7f, Core::height - offset * 0.7f);

        btnRadius          = Core::height * (25.0f / 1080.0f);
        btnPos[bWeapon]    = center;
        btnPos[bJump]      = center + vec2(cosf(-PI * 0.5f), sinf(-PI * 0.5f)) * radius;
        btnPos[bAction]    = center + vec2(cosf(-PI * 3.0f / 4.0f), sinf(-PI * 3.0f / 4.0f)) * radius;
        btnPos[bWalk]      = center + vec2(cosf(-PI), sinf(-PI)) * radius;
        btnPos[bInventory] = vec2(Core::width - btnRadius * 8.0f, btnRadius * 4.0f);

    // touch update
        Joystick &joy = Input::joy[Core::settings.controls[0].joyIndex];

        if (checkTouchZone(zMove))
            joy.L = vec2(0.0f);

        if (checkTouchZone(zLook))
            joy.L = vec2(0.0f);

        if (checkTouchZone(zButton))
            btn = bMove; // no active buttons == bNone

        if (doubleTap)
            doubleTap = false;

        float zoneSize = Core::width / 3.0f;

        for (int i = 0; i < COUNT(touch); i++) {
            InputKey key = InputKey(i + ikTouchA);

            if (!down[key]) continue;
            if (key == touchKey[zMove] || key == touchKey[zLook] || key == touchKey[zButton]) continue;

            int zone = clamp(int(touch[i].pos.x / zoneSize), 0, 2);
            InputKey &t = touchKey[zone];

            if (t == ikNone) {
                t = key;
                if (zone == zMove) {
                    if (touchTimerTap > 0.0f && touchTimerTap < 0.3f) { // 100 ms gap to reduce false positives (bad touch sensors)
                        doubleTap = true;
                        touchTimerTap = 0.0f;
                    } else
                        touchTimerTap = 0.3f;
                }
            }
        }

        newState[0][cLook] |= touchKey[zLook] != ikNone;

    // set active touches as gamepad controls
        getTouchDir(touchKey[zMove], joy.L);
        getTouchDir(touchKey[zLook], joy.R);

        if (touchKey[zButton] != ikNone) {
            vec2 pos = touch[touchKey[zButton] - ikTouchA].pos;

            btn = bMAX;
            float minDist = btnRadius * 8.0f;
            for (int i = 0; i < bMAX; i++)
                if (btnEnable[i]) {
                    float d = (pos - btnPos[i]).length();
                    if (d < minDist) {
                        minDist = d;
                        btn = TouchButton(i);
                    }
                }

            switch (btn) {
                case bWeapon    : newState[0][cWeapon   ] = true; break;
                case bWalk      : newState[0][cWalk     ] = true; break;
                case bAction    : newState[0][cAction   ] = true; break;
                case bJump      : newState[0][cJump     ] = true; break;
                case bInventory : newState[0][cInventory] = true; break;
                default         : ;
            }
        }

        if (doubleTap)
            newState[0][cRoll] = true;

        for (int j = 0; j < COUNT(Core::settings.controls); j++)
            for (int i = 0; i < cMAX; i++)
                setState(j, ControlKey(i), newState[j][i]);
    }
}

#endif
