#include "wx/sdljoy.h"
#include "SDL.h"
#include <SDL_joystick.h>
#include <wx/window.h>

DEFINE_EVENT_TYPE(wxEVT_SDLJOY)

struct wxSDLJoyState {
    SDL_Joystick* dev;
    int nax, nhat, nbut;
    short* curval;
    ~wxSDLJoyState()
    {
        if (dev)
            SDL_JoystickClose(dev);
    }
};

wxSDLJoy::wxSDLJoy(bool analog)
    : wxTimer()
    , digital(!analog)
    , evthandler(0)
    , joystate(0)
    , nosticks(true)
{
    // Start up joystick if not already started
    // FIXME: check for errors
    SDL_Init(SDL_INIT_JOYSTICK);
    // but we'll have to manage it manually
    SDL_JoystickEventState(SDL_IGNORE);
    // now query joystick config and open joysticks
    // there is no way to reread this later (e.g. if joystick plugged in),
    // since SDL won't
    njoy = SDL_NumJoysticks();

    if (!njoy)
        return;

    joystate = new wxSDLJoyState_t[njoy];
    memset(joystate, 0, njoy * sizeof(*joystate));

    for (int i = 0; i < njoy; i++) {
        SDL_Joystick* dev = joystate[i].dev = SDL_JoystickOpen(i);
        int nctrl = 0;
        nctrl += joystate[i].nax = SDL_JoystickNumAxes(dev);
        nctrl += joystate[i].nhat = SDL_JoystickNumHats(dev);
        nctrl += joystate[i].nbut = SDL_JoystickNumButtons(dev);
        joystate[i].curval = new short[nctrl];
        memset(joystate[i].curval, 0, sizeof(short) * nctrl);
    }
}

wxSDLJoy::~wxSDLJoy()
{
    delete[] joystate;
    // SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}

wxEvtHandler* wxSDLJoy::Attach(wxEvtHandler* handler)
{
    wxEvtHandler* prev = evthandler;
    evthandler = handler;
    return prev;
}

void wxSDLJoy::Add(int joy)
{
    if (joy >= njoy || !njoy)
        return;

    if (joy < 0) {
        for (int i = 0; i < njoy; i++)
            Add(i);

        return;
    }

    if (!joystate[joy].dev)
        joystate[joy].dev = SDL_JoystickOpen(joy);

    if (nosticks && joystate[joy].dev) {
        Start(50);
        nosticks = false;
    }
}

void wxSDLJoy::Remove(int joy)
{
    if (joy >= njoy || !njoy)
        return;

    if (joy < 0) {
        for (int i = 0; i < njoy; i++)
            if (joystate[i].dev) {
                SDL_JoystickClose(joystate[i].dev);
                joystate[i].dev = NULL;
            }

        Stop();
        nosticks = true;
        return;
    }

    if (!joystate[joy].dev)
        return;

    SDL_JoystickClose(joystate[joy].dev);
    joystate[joy].dev = NULL;

    for (int i = 0; i < njoy; i++)
        if (joystate[i].dev)
            return;

    Stop();
    nosticks = true;
}

void wxSDLJoy::Notify()
{
    if (nosticks)
        return;

    SDL_JoystickUpdate();
    wxEvtHandler* handler = evthandler ? evthandler : wxWindow::FindFocus();

    for (int i = 0; i < njoy; i++) {
        SDL_Joystick* dev = joystate[i].dev;

        if (dev) {
            int nax = joystate[i].nax, nhat = joystate[i].nhat,
                nbut = joystate[i].nbut;
            int ctrl = 0;
            short val;

            for (int j = 0; j < nax; j++) {
                val = SDL_JoystickGetAxis(dev, j);

                if (digital) {
                    if (val > 0x3fff)
                        val = 0x7fff;
                    else if (val <= -0x3fff)
                        val = -0x7fff;
                    else
                        val = 0;
                }

                if (handler && val != joystate[i].curval[j]) {
                    wxSDLJoyEvent ev(wxEVT_SDLJOY, GetId());
                    ev.joy = i;
                    ev.ctrl_type = WXSDLJOY_AXIS;
                    ev.ctrl_idx = j;
                    ev.ctrl_val = val;
                    ev.prev_val = joystate[i].curval[j];
                    handler->ProcessEvent(ev);
                }

                joystate[i].curval[j] = val;
            }

            for (int j = 0; j < nhat; j++) {
                val = SDL_JoystickGetHat(dev, j);

                if (handler && val != joystate[i].curval[nax + j]) {
                    wxSDLJoyEvent ev(wxEVT_SDLJOY, GetId());
                    ev.joy = i;
                    ev.ctrl_type = WXSDLJOY_HAT;
                    ev.ctrl_idx = j;
                    ev.ctrl_val = val;
                    ev.prev_val = joystate[i].curval[nax + j];
                    handler->ProcessEvent(ev);
                }

                joystate[i].curval[nax + j] = val;
            }

            for (int j = 0; j < nbut; j++) {
                val = SDL_JoystickGetButton(dev, j);

                if (handler && val != joystate[i].curval[nax + nhat + j]) {
                    wxSDLJoyEvent ev(wxEVT_SDLJOY, GetId());
                    ev.joy = i;
                    ev.ctrl_type = WXSDLJOY_BUTTON;
                    ev.ctrl_idx = j;
                    ev.ctrl_val = val;
                    ev.prev_val = joystate[i].curval[nax + nhat + j];
                    handler->ProcessEvent(ev);
                }

                joystate[i].curval[nax + nhat + j] = val;
            }
        }
    }
}
