#include "wx/sdljoy.h"
#include <SDL/SDL.h>
#include <SDL/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;
	    }
	}
    }
}
