/*  joystick.c: (c) 2002 sibn

    This file is part of GWiz.

    GWiz is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    GWiz is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with GWiz; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_ttf.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include "gwiz.h"
#include "prefsdat.h"
#include "joystick.h"
#include "menus.h"
#include "text.h"
#include "uiloop.h"

extern GwizApp gwiz;
GwizJoyConfigWindow gjcw;
int numjoysticks; /* used by almost every function.  Try to cut down math */

void InitGwizJoystick (void)
{
    atexit (QuitGwizJoystick);
    gwiz.joy.dev = NULL;
    if (gwiz.joy.enabled == FALSE)
	return; /* not enabled, so don't do any of this stuff. */
    numjoysticks = SDL_NumJoysticks();
    if (numjoysticks == 0)
	{
	    GwizLogEvent (__FILE__, __LINE__, "Joystick support requested %s",
			  " but no joysticks were found.");
	    gwiz.joy.enabled = FALSE;
	    return;
	}
    /* first try to load the last used joystick. */
    if ((numjoysticks >= gwiz.joy.whichdev) && (gwiz.joy.whichdev != -1))
	{
	    gwiz.joy.dev = SDL_JoystickOpen (gwiz.joy.whichdev);
	    if (gwiz.joy.dev == NULL)
		GwizLogEvent (__FILE__, __LINE__, "Joystick not connected: %d",
			      gwiz.joy.whichdev);
	    if (gwiz.joy.dev == NULL)
		{
		    gwiz.joy.enabled = FALSE;
		    return;
		}
	}
    if (gwiz.joy.dev != NULL)
	{
	    GwizLogEvent (__FILE__, __LINE__, "Joystick init: Successful: %s",
			  SDL_GetError());
	    return; /* we've got a joystick connected and ready to go */
	}
    /* Next likely case: There's exactly one joystick connected. Use that. */
    if (numjoysticks == 1)
	{
	    gwiz.joy.dev = SDL_JoystickOpen (0);
	    if (gwiz.joy.dev == NULL)
		{
		    GwizLogEvent (__FILE__, __LINE__,
				  "Only one joystick present, but it %s ",
				  "could not be opened.");
		    gwiz.joy.enabled = FALSE;
		    return;
		}
	    gwiz.joy.whichdev = 0;
	}
    if (gwiz.joy.dev != NULL)
	GwizLogEvent (__FILE__, __LINE__, "Joystick init: Successful: %s",
		      SDL_GetError());
}

void GwizOpenJoystick (int jsno)
{
    if (gwiz.joy.dev != NULL)
	{
	    SDL_JoystickClose (gwiz.joy.dev);
	    GwizLogEvent (__FILE__, __LINE__, "Successfully closed joystick");
	}

    gwiz.joy.dev = SDL_JoystickOpen (jsno);
    if (gwiz.joy.dev == NULL)
	{
	    GwizLogEvent (__FILE__, __LINE__, "Could not open joystick");
	    gwiz.joy.enabled = FALSE;
	}
    gwiz.joy.enabled = TRUE;
    gwiz.joy.whichdev = jsno;
}

void QuitGwizJoystick (void)
{
    if (gwiz.joy.dev == NULL)
	return;
    SDL_JoystickClose (gwiz.joy.dev);
    GwizLogEvent (__FILE__, __LINE__, "Successfully closed joystick");
}

void CheckJoy (void)
{
    numjoysticks = SDL_NumJoysticks();
    if ((gwiz.joy.act + gwiz.joy.cancel != 0) ||
	(gwiz.joy.enabled == FALSE))
	return;
    else 
	JoyConfig();
}

void JoyConfig (void)
{
    numjoysticks = SDL_NumJoysticks();
    PrepJoyConfig ();
    if (numjoysticks > 1)
	ChooseJoystick();
    gjcw.maxposition = 5;
    JoyConfigLoop();
    SDL_BlitSurface (gjcw.displace, NULL, gwiz.canvas, &gjcw.brect);
    SDL_FreeSurface (gjcw.displace);
    SDL_FreeSurface (gjcw.area);
}

void UpdateJoyMappingDisplay (void)
{
    SDL_Surface *label[11]; /* 5 labels, 5 maps, one quit message */
    SDL_Rect lrect;
    char btnname[6];
    int i;

    lrect.x = gwiz.tbord[0]->w + 1 + gwiz.cursor->w;
    lrect.y = gwiz.tbord[0]->h + 1;
    lrect.h = gwiz.font.height;

    label[0] = GwizRenderText ("Move Backward");
    label[1] = GwizRenderText ("Step Left");
    label[2] = GwizRenderText ("Step Right");
    label[3] = GwizRenderText ("Set \"Ok\" Button");
    label[4] = GwizRenderText ("Set \"Cancel\" Button");
    label[10] = GwizRenderText ("Exit Menu");

    for (i = 0; i < 5; i++)
	{
	    lrect.w = label[i]->w;
	    SDL_BlitSurface (label[i], NULL, gjcw.area, &lrect);
	    lrect.y += gwiz.font.height;
	}

    snprintf (btnname, sizeof(char)*6, "Joy%d", gwiz.joy.bkwd);
    label[5] = GwizRenderText (btnname);
    snprintf (btnname, sizeof(char)*6, "Joy%d", gwiz.joy.slft);
    label[6] = GwizRenderText (btnname);
    snprintf (btnname, sizeof(char)*6, "Joy%d", gwiz.joy.srgt);
    label[7] = GwizRenderText (btnname);
    snprintf (btnname, sizeof(char)*6, "Joy%d", gwiz.joy.act);
    label[8] = GwizRenderText (btnname);
    snprintf (btnname, sizeof(char)*6, "Joy%d", gwiz.joy.cancel);
    label[9] = GwizRenderText (btnname);

    lrect.x += gwiz.font.width*20;
    lrect.y = gwiz.tbord[0]->h + 1;

    for (i = 5; i < 10; i++)
	{
	    lrect.w = label[i]->w;
	    SDL_BlitSurface (label[i], NULL, gjcw.area, &lrect);
	    lrect.y += gwiz.font.height;
	}
    lrect.x = gwiz.tbord[0]->w + 1 + gwiz.cursor->w;
    lrect.w = label[10]->w;
    SDL_BlitSurface (label[10], NULL, gjcw.area, &lrect);

    for (i = 0; i < 11; i++)
	SDL_FreeSurface (label[i]);
}

void ChooseJoystick (void)
{
    SDL_Event event;
    numjoysticks = SDL_NumJoysticks();

    gjcw.maxposition = numjoysticks-1;
    gjcw.position = 0;
    SDL_BlitSurface (gjcw.joylist, NULL, gwiz.canvas, &gjcw.drect);
    if (gwiz.joy.dev != NULL)
	{
	    SDL_JoystickClose (gwiz.joy.dev);
	    gwiz.joy.dev = NULL;
	}

    while (SDL_WaitEvent (&event) != 0)
	{
	    SDL_Event *e = &event;
	    if (EventIsUp (e))
		MoveJoyConfigCursor (gjcw.joylist, TRUE);
	    if (EventIsDown (e))
		MoveJoyConfigCursor (gjcw.joylist, FALSE);
	    if (EventIsOk (e))
		GwizOpenJoystick (gjcw.position);
	    if (EventIsCancel (e))
		break;

	    if (gwiz.joy.dev != NULL)
		break;
	    SDL_BlitSurface (gjcw.joylist, NULL, gwiz.canvas, &gjcw.drect);
	    SDL_Flip (gwiz.canvas);
	}
    SDL_BlitSurface (gjcw.displace, NULL, gwiz.canvas, &gjcw.drect);
    SDL_FreeSurface (gjcw.joylist);
    SDL_FreeSurface (gjcw.displace);
}

void MoveJoyConfigCursor (SDL_Surface *surface, int goingup)
{
    int yoffset = gwiz.tbord[0]->h + 1 + gwiz.font.height/2 - gwiz.cursor->h/2;
    if (goingup)
	{
	    gjcw.position--;
	    if (gjcw.position < 0)
		gjcw.position = gjcw.maxposition;
	} else {
	    gjcw.position++;
	    if (gjcw.position > gjcw.maxposition)
		gjcw.position = 0;
	}
    SDL_FillRect (surface, &gjcw.crect, 0);
    gjcw.crect.y = yoffset + gjcw.position*gwiz.font.height;
    SDL_BlitSurface (gwiz.cursor, NULL, surface, &gjcw.crect);
}

int SetJoyCursorFromMouse (SDL_Event *event)
{
    if (event->type == SDL_MOUSEMOTION)
	{
	    int minx = gwiz.canvas->w/2 - gjcw.brect.w/2 + 
                       gwiz.tbord[0]->w + 1;
	    int miny = gwiz.canvas->h/2 - gjcw.brect.h/2 +
                       gwiz.tbord[0]->h + 1;
	    int maxx = minx + gwiz.font.width*25;
	    int maxy = miny + gwiz.font.height*6;
	    Uint16 mousex = event->motion.x;
	    Uint16 mousey = event->motion.y;
	    int i;
	    int j = -1;

	    if ((mousex < minx) || (mousex > maxx) ||
		(mousey < miny) || (mousey > maxy))
		return (FALSE);
	    
	    for (i = miny; i < maxy+gwiz.font.height; j++)
		if (i > mousey)
		    {
			gjcw.position = j;
			break;
		    }
		else
		    i += gwiz.font.height;
	    
	    MoveJoyConfigCursor (gjcw.area, TRUE);
	    MoveJoyConfigCursor (gjcw.area, FALSE);
	} else if (event->type == SDL_MOUSEBUTTONDOWN) {
	    switch (gjcw.position)
		{
		case 0:
		    gwiz.joy.bkwd = GetJoyButtonNo();
		    break;
		case 1:
		    gwiz.joy.slft = GetJoyButtonNo();
		    break;
		case 2:
		    gwiz.joy.srgt = GetJoyButtonNo();
		    break;
		case 3:
		    gwiz.joy.act = GetJoyButtonNo();
		    break;
		case 4:
		    gwiz.joy.cancel = GetJoyButtonNo();
		    break;
		case 5:
		    return (TRUE);
		    break;
		}	    
	}
    return (FALSE);
}

void PrepJoyConfig (void)
{
    SDL_Surface *names[numjoysticks];
    SDL_Rect nrect;
    int widest = 0;
    int i;

    nrect.x = gwiz.tbord[0]->w + 1 + gwiz.cursor->w;
    nrect.y = gwiz.tbord[0]->w + 1;
    nrect.h = gwiz.font.height;

    for (i = 0; i < numjoysticks; i++)
	{
	    const char *jname = SDL_JoystickName (i);
	    char *tmp = strdup (jname);
	    if (tmp != NULL)
		names[i] = GwizRenderText (tmp);
	    else
		names[i] = GwizRenderText ("Unnamed Device");
	    if (names[i]->w > widest)
		widest = names[i]->w;
	    Sfree (tmp);
	}
    gjcw.joylist = NewTextBox (widest + gwiz.cursor->w,
			       gwiz.font.height*numjoysticks);
    for (i = 0; i < numjoysticks; i++)
	{
	    nrect.w = names[i]->w;
	    SDL_BlitSurface (names[i], NULL, gjcw.joylist, &nrect);
	    nrect.y += gwiz.font.height;
	    SDL_FreeSurface (names[i]);
	}
    gjcw.drect.x = CenterHoriz(gwiz.canvas, gjcw.joylist);
    gjcw.drect.y = CenterVert (gwiz.canvas, gjcw.joylist);
    gjcw.drect.h = gjcw.joylist->h;
    gjcw.drect.w = gjcw.joylist->w;
    gjcw.crect.x = gwiz.tbord[0]->w + 1;
    gjcw.crect.y = gwiz.tbord[0]->h + 1 + gwiz.font.height/2 - 
	           gwiz.cursor->w/2;
    gjcw.crect.h = gwiz.cursor->h;
    gjcw.crect.w = gwiz.cursor->w;

    gjcw.displace = NewGwizSurface (gjcw.drect.w, gjcw.drect.h);
    SDL_BlitSurface (gwiz.canvas, &gjcw.drect, gjcw.displace, NULL);

    gjcw.area = NewTextBox (gwiz.font.width*25 + gwiz.cursor->w,
			    gwiz.font.height*6);

    gjcw.brect.x = CenterHoriz(gwiz.canvas, gjcw.area);
    gjcw.brect.y = CenterVert (gwiz.canvas, gjcw.area);
    gjcw.brect.h = gjcw.area->h;
    gjcw.brect.w = gjcw.area->w;

    SDL_BlitSurface (gwiz.cursor, NULL, gjcw.area, &gjcw.crect);
    SDL_BlitSurface (gwiz.cursor, NULL, gjcw.joylist, &gjcw.crect);

    UpdateJoyMappingDisplay();
}

void JoyConfigLoop (void)
{
    SDL_Event event;
    int breakout = FALSE;

    SDL_BlitSurface (gjcw.area, NULL, gwiz.canvas, &gjcw.brect);
    SDL_Flip (gwiz.canvas);

    while (SDL_WaitEvent (&event) != 0)
	{
	    SDL_Event *e = &event;
	    if (EventIsCancel (e))
		breakout = TRUE;
	    if (EventIsOk (e))
		switch (gjcw.position)
		    {
		    case 0:
			gwiz.joy.bkwd = GetJoyButtonNo();
			break;
		    case 1:
			gwiz.joy.slft = GetJoyButtonNo();
			break;
		    case 2:
			gwiz.joy.srgt = GetJoyButtonNo();
			break;
		    case 3:
			gwiz.joy.act = GetJoyButtonNo();
			break;
		    case 4:
			gwiz.joy.cancel = GetJoyButtonNo();
			break;
		    case 5:
			breakout = TRUE;
			break;
		    }
	    if (EventIsUp (e))
		MoveJoyConfigCursor (gjcw.area, TRUE);
	    if (EventIsDown (e))
		MoveJoyConfigCursor (gjcw.area, FALSE);

	    if (event.type & (SDL_MOUSEMOTION|SDL_MOUSEBUTTONDOWN))
		breakout = SetJoyCursorFromMouse (&event);

	    if (breakout)
		break;
	    UpdateJoyMappingDisplay();
	    SDL_BlitSurface (gjcw.area, NULL, gwiz.canvas, &gjcw.brect);
	    SDL_Flip (gwiz.canvas);
	}
}

int GetJoyButtonNo (void)
{
    SDL_Event event;
    SDL_Surface *instructions;
    SDL_Surface *displace;
    SDL_Rect drect;
    SDL_Rect trect;
    SDL_Surface *msgtop;
    SDL_Surface *msgbottom;

    msgtop = GwizRenderText ("Press a button on your joystick to");
    msgbottom = GwizRenderText ("configure, or [Escape] to cancel.");

    instructions = NewTextBox ((msgtop->w > msgbottom->w ? msgtop->w :
			       msgbottom->w), gwiz.font.height*2);
    displace = NewGwizSurface (instructions->w, instructions->h);

    trect.x = gwiz.tbord[0]->w + 1;
    trect.y = gwiz.tbord[0]->h + 1;
    trect.h = gwiz.font.height;
    trect.w = msgtop->w;
    SDL_BlitSurface (msgtop, NULL, instructions, &trect);
    trect.w = msgbottom->w;
    trect.y += gwiz.font.height;
    SDL_BlitSurface (msgbottom, NULL, instructions, &trect);
    drect.x = gwiz.canvas->w/2 - instructions->w/2;
    drect.y = gwiz.canvas->h - instructions->h;
    drect.h = instructions->h;
    drect.w = instructions->w;
    SDL_BlitSurface (gwiz.canvas, &drect, displace, NULL);
    SDL_BlitSurface (instructions, NULL, gwiz.canvas, &drect);
    SDL_Flip (gwiz.canvas);

    while (SDL_WaitEvent (&event) != 0)
	{
	    if (event.type == SDL_JOYBUTTONDOWN)
		break;
	    if (EventIsCancel (&event))
		break;
	}
    SDL_BlitSurface (displace, NULL, gwiz.canvas, &drect);
    SDL_Flip (gwiz.canvas);
    SDL_FreeSurface (instructions);
    SDL_FreeSurface (displace);
    SaveJoyConfig();
    if (event.type == SDL_JOYBUTTONDOWN)
	return (event.jbutton.button);
    else
	return (0);
}

void SaveJoyConfig (void)
{
    GwizJoystick joy;
    char *joypath = MakePath(gwiz.udata.gwiz, "/joyconfig");
    FILE *jcfg;

    memmove (&joy, &gwiz.joy, sizeof(GwizJoystick));

    joy.enabled = htonl (joy.enabled);
    joy.whichdev = htonl (SDL_JoystickIndex (gwiz.joy.dev));
    joy.fwd = htonl (joy.fwd);
    joy.bkwd = htonl (joy.bkwd);
    joy.lft = htonl (joy.lft);
    joy.rgt = htonl (joy.rgt);
    joy.fta = htonl (joy.fta);
    joy.slft = htonl (joy.slft);
    joy.srgt = htonl (joy.srgt);
    joy.act = htonl (joy.act);
    joy.cancel = htonl (joy.cancel);

    jcfg = fopen (joypath, "wb");
    if (jcfg == NULL)
	{
	    Sfree (joypath);
	    fprintf (stderr, "joystick.c: unable to open file %s", joypath);
	    return;
	}
    fwrite (&joy, sizeof(GwizJoystick), 1, jcfg);

    Sfree (joypath);
    fclose (jcfg);
}

void LoadJoyConfig (void)
{
    char *joypath = MakePath (gwiz.udata.gwiz, "/joyconfig");
    FILE *jcfg;

    jcfg = fopen (joypath, "rb");
    if (jcfg == NULL)
	{
	    SaveJoyConfig();
	    jcfg = fopen (joypath, "rb");
	    if (jcfg == NULL)
		BailCall ("joystick.c: unable to load file: ", joypath);
	}
    fread (&gwiz.joy, sizeof(GwizJoystick), 1, jcfg);

    gwiz.joy.enabled = ntohl (gwiz.joy.enabled);
    gwiz.joy.whichdev = ntohl (gwiz.joy.whichdev);
    gwiz.joy.fwd = ntohl (gwiz.joy.fwd);
    gwiz.joy.bkwd = ntohl (gwiz.joy.bkwd);
    gwiz.joy.lft = ntohl (gwiz.joy.lft);
    gwiz.joy.rgt = ntohl (gwiz.joy.rgt);
    gwiz.joy.fta = ntohl (gwiz.joy.fta);
    gwiz.joy.slft = ntohl (gwiz.joy.slft);
    gwiz.joy.srgt = ntohl (gwiz.joy.srgt);
    gwiz.joy.act = ntohl (gwiz.joy.act);
    gwiz.joy.cancel = ntohl (gwiz.joy.cancel);

    Sfree (joypath);
    fclose (jcfg);
}

int JoyAxisMotion (SDL_Event *event, int direction)
{
    if (gwiz.joy.enabled == FALSE)
	return (FALSE);
    switch (direction)
	{
	case NORTH:
	    if ((event->jaxis.axis == 1) &&
		(event->jaxis.value < gwiz.joy.fwd))
		return (TRUE);
	    break;
	case SOUTH:
	    if ((event->jaxis.axis == 1) &&
		(event->jaxis.value > gwiz.joy.fta)) /* "down" */
		return (TRUE);
	    break;
	case EAST:
	    if ((event->jaxis.axis == 0) &&
		(event->jaxis.value > gwiz.joy.rgt))
		return (TRUE);
	    break;
	case WEST:
	    if ((event->jaxis.axis == 0) &&
		(event->jaxis.value < gwiz.joy.lft))
		return (TRUE);
	    break;
	}
    return (FALSE);
}

Uint8 GetJoyButton (SDL_Event *event)
{
    if (gwiz.joy.enabled == FALSE)
	return (-1);
    if (event->jbutton.which == gwiz.joy.whichdev)
	{
	    if (event->jbutton.button == gwiz.joy.act)
		return (gwiz.joy.act);
	    if (event->jbutton.button == gwiz.joy.cancel)
		return (gwiz.joy.cancel);
	    if (event->jbutton.button == gwiz.joy.bkwd)
		return (gwiz.joy.bkwd);
	    if (event->jbutton.button == gwiz.joy.slft)
		return (gwiz.joy.slft);
	    if (event->jbutton.button == gwiz.joy.srgt)
		return (gwiz.joy.srgt);
	}
    return (-1);
}
