/* input.m,
 *
 * This file handles players' controls.
 */

#include <allegro.h>
#include <assert.h>
#include <objc/Object.h>
#include "common.h"
#include "input.h"
#include "player.h"

/* Using the tokens to read from config files. */
#include "seborrhea/token.h"


#define JOYSTICK_CFG		"joystick.cfg"
#define KEYBOARD_CFG		"keyboard.cfg"
#define MAX_SPEED		3.0
#define JOYSTICK_DEAD_ZONE	96


KeyboardController *menu_keyboard_controls;
JoystickController *joystick_controller[MAX_JOYSTICKS];
player_controls_t player_controls[MAX_PLAYERS];
BOOL joy_enabled = YES;

/*--------------------------------------------------------------*/

static void do_input_for_player(int pl)
{
    double xv = 0.0, yv = 0.0;
    BOOL fire = NO;

    /* Joystick, hopefully analog. */
    if (joy_enabled) {
	JoystickController *j = player_controls[pl].joystick;

        xv = [j xPosition];
        yv = [j yPosition];
        xv = xv * MAX_SPEED / JOYSTICK_DEAD_ZONE;
        yv = yv * MAX_SPEED / JOYSTICK_DEAD_ZONE;

	fire = [j held:RAID_KEY_FIRE];
    }

    /* Keyboard controls, if you are not lucky enough to possess a
       Sony Dual-Shock 2 joystick and playstation-to-usb adapter. */
    {
        KeyboardController *k = player_controls[pl].keyboard;

	if ([k held:RAID_KEY_LEFT])  xv -= MAX_SPEED;
	if ([k held:RAID_KEY_RIGHT]) xv += MAX_SPEED;
	if ([k held:RAID_KEY_UP])    yv -= MAX_SPEED;
	if ([k held:RAID_KEY_DOWN])  yv += MAX_SPEED;
	if ([k held:RAID_KEY_FIRE])  fire = YES;
    }

    [player[pl] increaseVelocity:xv :yv];

    if (fire)
        [player[pl] openFire];
    else
        [player[pl] haltFire];
}

void do_input(void)
{
    int pl;

    /* No need to poll since handled in poll_raid_keys() */
    for (pl = 0; pl < MAX_PLAYERS; pl++) {
	if (player[pl])
	    do_input_for_player(pl);
    }
}

/*--------------------------------------------------------------*/

void poll_raid_keys(int n_players)
{
    [menu_keyboard_controls poll];
    [player_controls[0].keyboard poll];
    [player_controls[1].keyboard poll];

    if (joy_enabled) {
	poll_joystick();

	if (n_players == 0) {		/* Poll all joysticks. */
	    int j;

	    for (j = 0; j < num_joysticks && joystick_controller[j]; j++)
		[joystick_controller[j] poll];
	}

	if ((n_players > 0) && (player_controls[0].joystick))
	    [player_controls[0].joystick poll];
	if ((n_players > 1) && (player_controls[1].joystick))
	    [player_controls[1].joystick poll];
    }
}

/* Start pressed: used to (un-)pause game. 
   Trigger by pressing START. */
BOOL start_pressed(void)
{
    /* All keyboard start buttons are the same, so just check one. */
    if ([player_controls[0].keyboard pressed:RAID_KEY_START])
	return YES;

    if (joy_enabled) {
	return ((player_controls[0].joystick && [player_controls[0].joystick pressed:RAID_KEY_START]) ||
		(player_controls[1].joystick && [player_controls[1].joystick pressed:RAID_KEY_START]));
    }

    return NO;
}

/* Start released: used to finish entering hiscore.
   Trigger by releasing ENTER or START. */
BOOL start_released(void)
{
    /* All keyboard start buttons are the same, so just check one. */
    if ([player_controls[0].keyboard released:RAID_KEY_START])
	return YES;

    if (joy_enabled) {
	return ((player_controls[0].joystick && [player_controls[0].joystick released:RAID_KEY_START]) ||
		(player_controls[1].joystick && [player_controls[1].joystick released:RAID_KEY_START]));
    }

    return NO;
}

/* Retry released: used for restarting.
   Trigger by pressing START or SPACEBAR. */
BOOL retry_released(void) { return (key[KEY_SPACE] || start_pressed()); }

/* Join pressed: used to join player 2. 
   Trigger by pressing START/FIRE for player 2 in 1 player mode. */
BOOL join_pressed(void)
{
    if (num_players >= 2)
	return NO;

    if ([player_controls[1].keyboard pressed:RAID_KEY_START] ||
	[player_controls[1].keyboard pressed:RAID_KEY_FIRE])
	return YES;

    if (joy_enabled && player_controls[1].joystick) {
	return ([player_controls[1].joystick pressed:RAID_KEY_START] ||
		[player_controls[1].joystick pressed:RAID_KEY_FIRE]);
    }

    return NO;
}

/* Okay released: used by menu.
   Trigger by releasing START (and/or FIRE on ANY joystick). */
BOOL okay_released(void)
{
    /* Enter, but not alt-enter. */
    if ([menu_keyboard_controls released:RAID_KEY_START] &&
	not (key_shifts & KB_ALT_FLAG))
	return YES;

    if (joy_enabled) {
	int j;

	for (j = 0; j < num_joysticks && joystick_controller[j]; j++) {
	    if ([joystick_controller[j] released:RAID_KEY_START] ||
		[joystick_controller[j] released:RAID_KEY_FIRE])
		return YES;
	}
    }

    return NO;
}

/* Menu released: used by menu, and to pause/leave game.
   Trigger by releasing ESC/MENU on ANY joystick. */
BOOL menu_released(void)
{
    /* All keyboard menu keys are the same, so just check one. */
    if ([menu_keyboard_controls released:RAID_KEY_MENU])
	return YES;

    if (joy_enabled) {
	int j;

	for (j = 0; j < num_joysticks && joystick_controller[j]; j++)
	    if ([joystick_controller[j] released:RAID_KEY_MENU])
		return YES;
    }

    return NO;
}

/*--------------------------------------------------------------*/
/* Abstract input controller.					*/
/*--------------------------------------------------------------*/

@implementation InputController
- init
{
    [super init];

    auto_repeat[RAID_KEY_UP]    = YES;
    auto_repeat[RAID_KEY_DOWN]  = YES;
    auto_repeat[RAID_KEY_LEFT]  = YES;
    auto_repeat[RAID_KEY_RIGHT] = YES;
    auto_repeat[RAID_KEY_START] = NO;
    auto_repeat[RAID_KEY_MENU]  = NO;
    auto_repeat[RAID_KEY_FIRE]  = NO;

    return self;
}

/* File seeking code snucked metafile code. */
- (BOOL) seekInFile:(const char *)fn :(FILE *)fp For:(const char *)seekee
{
    int parentheses_depth = 0;
    assert(fp && seekee);

    /* Go to the start. */
    rewind(fp);

    if (not expect_char(fp, '('))
	return NO;

    do {
	Token *tok;
	parentheses_depth++;

	while ((tok = read_token(fp))) {
	    if ([tok isType:TOKEN_SYMBOL]) {
		if ((parentheses_depth == 1) &&
		    streq([tok getSymbol], seekee)) {
		    /* We found it! */
		    [tok free];
		    return YES;
		}
	    }
	    [tok free];
	}

	while (expect_char(fp, ')')) {
	    parentheses_depth--;

	    if (parentheses_depth < 0)
		goto exit_without_finding_seekee;
	}
    } while (expect_char(fp, '('));

 exit_without_finding_seekee:
    if (parentheses_depth != 0) {
	fprintf(stderr, "Mismatching parentheses in %s.\n", fn);
    }
    return NO;
}

- (void) poll
{
    int i;

    for (i = 0; i < NUM_RAID_KEYS; i++)
	was_pressed[i] = pressed[i];

    [self pollUpdatePressed];

    /* Auto repeat. */
    for (i = 0; i < NUM_RAID_KEYS; i++) {
	if ([self pressed:i])
	    repeat_tics[i] = 4;
	elif ([self released:i])
	    repeat_tics[i] = 0;
	elif ([self held:i]) {
	    if (repeat_tics[i] > 0)
		repeat_tics[i]--;
	    elif (auto_repeat[i]) {
		was_pressed[i] = 0;
		repeat_tics[i] = 2;
	    }
	}
    }
}

- (void) pollUpdatePressed { /* replace me. */ }

- (BOOL) pressed:(raid_key_t)k
{
    return (pressed[k] && not was_pressed[k]);
}

- (BOOL) held:(raid_key_t)k { return pressed[k]; }

- (BOOL) released:(raid_key_t)k
{
    return (not pressed[k] && was_pressed[k]);
}

- (void) setKey:(raid_key_t)k To:(int)k_ {(void)k, (void)k_;}

- (int) getKey:(raid_key_t)k { (void)k; return 0; }
@end

/*--------------------------------------------------------------*/
/* Keyboard.							*/
/*--------------------------------------------------------------*/

@implementation KeyboardController
- (BOOL) readConfigFile:(FILE *)fp
{
    assert(fp);

    /* Commands are:
       navigation-keys <up> <down> <left> <right>
       fire-keys <key> [key]
    */

    while (expect_char(fp, '(')) {
	Token *tok = read_symbol(fp);
	const char *cmd;

	if (not tok)
	    return NO;

	cmd = [tok getSymbol];

	if (streq(cmd, "navigation-keys")) {
	    Token *nav_keys[4];
	    int k;

	    for (k = 0; k < 4; k++) {
		nav_keys[k] = read_fixnum(fp);
		if (!nav_keys[k])
		    break;
	    }

	    if (nav_keys[0] && nav_keys[1] && nav_keys[2] && nav_keys[3]) {
		k = [nav_keys[0] getFixnum];
		if (k < KEY_MAX) up = k;

		k = [nav_keys[1] getFixnum];
		if (k < KEY_MAX) down = k;

		k = [nav_keys[2] getFixnum];
		if (k < KEY_MAX) left = k;

		k = [nav_keys[3] getFixnum];
		if (k < KEY_MAX) right = k;
	    }
	    else {
		fprintf(stderr, "Buggy " KEYBOARD_CFG " file (navigation-keys).\n");
	    }

	    for (k = 0; k < 4; k++)
		[nav_keys[k] free];
	}

	else if (streq(cmd, "fire-keys")) {
	    Token *key1 = read_fixnum(fp);
	    Token *key2 = read_fixnum(fp);

	    if (key1) {
		int k = [key1 getFixnum];
		if (k < KEY_MAX) fire[0] = k;

		if (key2) {
		    k = [key2 getFixnum];
		    if (k < KEY_MAX) fire[1] = k;
		}
		else
		    fire[1] = 0;
	    }
	    else {
		fprintf(stderr, "Buggy " KEYBOARD_CFG " file (fire-keys).\n");
	    }

	    [key1 free];
	    [key2 free];
	}
	
	else
	    fprintf(stderr, "Unknown command (%s) in " KEYBOARD_CFG "\n", cmd);

	[tok free];
	if (not expect_char(fp, ')'))
	    return NO;
    }

    return YES;
}

- initWithConfigFile:(FILE *)fp :(const char *)section
{
    [self init];

    if (streq(section, "keyboard0")) {
	up      = KEY_UP;
	down    = KEY_DOWN;
	left    = KEY_LEFT;
	right   = KEY_RIGHT;
	fire[0] = KEY_LSHIFT;
	fire[1] = KEY_LCONTROL;
    }
    else {
	up      = KEY_8_PAD;
	down    = KEY_2_PAD;
	left    = KEY_4_PAD;
	right   = KEY_6_PAD;
	fire[0] = KEY_RSHIFT;
	fire[1] = KEY_RCONTROL;
    }

    if (fp && [self seekInFile:KEYBOARD_CFG :fp For:section]) {
	if (not [self readConfigFile:fp])
	    fprintf(stderr, "Buggy " KEYBOARD_CFG " file.  Aborted %s.\n",
		    section);
    }

    return self;
}

- (void) pollUpdatePressed
{
    pressed[RAID_KEY_UP]    = key[up];
    pressed[RAID_KEY_DOWN]  = key[down];
    pressed[RAID_KEY_LEFT]  = key[left];
    pressed[RAID_KEY_RIGHT] = key[right];
    pressed[RAID_KEY_START] = key[KEY_ENTER] || key[KEY_ENTER_PAD];
    pressed[RAID_KEY_MENU]  = key[KEY_ESC];
    pressed[RAID_KEY_FIRE]  = key[fire[0]] || key[fire[1]];
}

- (void) setKey:(raid_key_t)k To:(int)k_
{
    if (k == RAID_KEY_UP) up = k_;
    elif (k == RAID_KEY_DOWN) down = k_;
    elif (k == RAID_KEY_LEFT) left = k_;
    elif (k == RAID_KEY_RIGHT) right = k_;
    elif (k == RAID_KEY_FIRE) fire[0] = k_;
    elif (k == RAID_KEY_FIRE2) fire[1] = k_;

    if (fire[0] == fire[1])
	fire[1] = 0;
}

- (int) getKey:(raid_key_t)k
{
    if (k == RAID_KEY_UP) return up;
    elif (k == RAID_KEY_DOWN) return down;
    elif (k == RAID_KEY_LEFT) return left;
    elif (k == RAID_KEY_RIGHT) return right;
    elif (k == RAID_KEY_FIRE) return fire[0];
    elif (k == RAID_KEY_FIRE2) return fire[1];
    else
	return -1;
}
@end

/*--------------------------------------------------------------*/
/* Joysticks.							*/
/*--------------------------------------------------------------*/

@implementation JoystickController
static int guess_start_button(int joy_num)
{
    if (joy_enabled && joy_num < num_joysticks) {
	/* These values work for my gamepads :P */
	switch (joy[joy_num].num_buttons) {
	  case  4: return 1;	/* Triangle (top) */
	  case  8: return 5;
	  case 12: return 8;
	  case 24: printf("Oi!  24 joystick buttons detected.\n"); return 8;
	}
    }

    return 0;			/* Default. */
}

static int guess_menu_button(int joy_num) 
{
    if (joy_enabled && joy_num < num_joysticks) {
	switch (joy[joy_num].num_buttons) {
	  case  4: return 2;	/* Cross (bottom, bottom, bottom..hahaha) */
	  case  8: return 4;
	  case 12: return 9;
	}
    }

    return 1;			/* Default. */
}

- (void) resetToDefaults
{
    x_stick = 0; x_axis = 0;
    y_stick = 0; y_axis = 1;

    /* Menu. */
    hthresh = 75;
    vthresh = 75;

    start_b = guess_start_button(joy_num);
    menu_b  = guess_menu_button(joy_num);

#ifdef WINFF
    if (ff_joystick_has(joy_num)) {
	fprintf(stdout, "Joystick %d has force feedback.\n", joy_num+1);
	ff_player_hurt = ff_create_effect();
	ff_make_constant_force_effect(joy_num, ff_player_hurt, 5);
	ff_player_dying = ff_create_effect();
	ff_make_periodic_effect(joy_num, ff_player_dying);
    }
    else {
	ff_player_hurt = NULL;
	ff_player_dying = NULL;
    }
#endif
}

- (BOOL) readConfigFile:(FILE *)fp
{
    assert(fp);

    /* Commands are:
       button-config <start-button> <menu-button>
       primary-stick <stick>
       ignore-sticks <stick> ...
    */

    while (expect_char(fp, '(')) {
	Token *tok = read_symbol(fp);
	const char *cmd;

	if (not tok)
	    return NO;

	cmd = [tok getSymbol];

	if (streq(cmd, "button-config")) {
	    Token *but1 = read_fixnum(fp);
	    Token *but2 = read_fixnum(fp);

	    if (but1 && but2) {
		int b1 = [but1 getFixnum];
		int b2 = [but2 getFixnum];

		/* Keep the defaults if we get an error. */
		if (b1 < joy[joy_num].num_buttons)
		    start_b = b1;
		if (b2 < joy[joy_num].num_buttons)
		    menu_b = b2;
	    }
	    else {
		fprintf(stderr, "Buggy " JOYSTICK_CFG " file (button-config).\n");
	    }

	    [but1 free];
	    [but2 free];
	}

	else if (streq(cmd, "primary-stick")) {
	    Token *stick = read_fixnum(fp);

	    if (stick) {
		int s = [stick getFixnum];
		if (s < joy[joy_num].num_sticks) {
		    x_stick = s;
		    y_stick = s;
		}

		[stick free];
	    }
	    else {
		fprintf(stderr, "Buggy " JOYSTICK_CFG " file (primary-stick).\n");
	    }
	}

	else if (streq(cmd, "ignore-sticks")) {
	    Token *stick;

	    while ((stick = read_fixnum(fp))) {
		int s = [stick getFixnum];

		if (s < joy[joy_num].num_sticks)
		    ignore_stick[s] = YES;

		[stick free];
	    }
	}

	else
	    fprintf(stderr, "Unknown command (%s) in " JOYSTICK_CFG "\n", cmd);

	[tok free];
	if (not expect_char(fp, ')'))
	    return NO;
    }

    return YES;
}

- initWithJoystickNumber:(int)n ConfigFile:(FILE *)fp
{
    char stick_string[8];

    [self init];

    joy_num = n;

    fprintf(stdout, "Joystick %d has %d sticks, %d buttons.\n",
	    joy_num+1, joy[joy_num].num_sticks, joy[joy_num].num_buttons);

    [self resetToDefaults];

    snprintf(stick_string, sizeof stick_string, "joy%d", joy_num);
    if (fp && [self seekInFile:JOYSTICK_CFG :fp For:stick_string]) {
	if (not [self readConfigFile:fp])
	    fprintf(stderr, "Buggy " JOYSTICK_CFG " file.  Aborted joy%d.\n",
		    joy_num);
    }

    return self;
}

- (int) joystickNumber { return joy_num; }

- (void) pollUpdatePressed
{
    int b, s;

    if (not joy_enabled) {
	pressed[RAID_KEY_UP]    = NO;
	pressed[RAID_KEY_DOWN]  = NO;
	pressed[RAID_KEY_LEFT]  = NO;
	pressed[RAID_KEY_RIGHT] = NO;
	pressed[RAID_KEY_START] = NO;
	pressed[RAID_KEY_MENU]  = NO;
	pressed[RAID_KEY_FIRE]  = NO;
	return;
    }

    pressed[RAID_KEY_UP]    = NO;
    pressed[RAID_KEY_DOWN]  = NO;
    pressed[RAID_KEY_LEFT]  = NO;
    pressed[RAID_KEY_RIGHT] = NO;
    for (s = 0; s < joy[joy_num].num_sticks; s++) {
	/* Hack to fix our/my gamepads under Linux and Windows. */
	if (ignore_stick[s])
	    continue;

	if (joy[joy_num].stick[s].axis[y_axis].pos < -vthresh) pressed[RAID_KEY_UP]    = YES;
	if (joy[joy_num].stick[s].axis[y_axis].pos >  vthresh) pressed[RAID_KEY_DOWN]  = YES;
	if (joy[joy_num].stick[s].axis[x_axis].pos < -hthresh) pressed[RAID_KEY_LEFT]  = YES;
	if (joy[joy_num].stick[s].axis[x_axis].pos >  hthresh) pressed[RAID_KEY_RIGHT] = YES;
    }

    pressed[RAID_KEY_START] = joy[joy_num].button[start_b].b;
    pressed[RAID_KEY_MENU]  = joy[joy_num].button[menu_b].b;

    pressed[RAID_KEY_FIRE] = NO;
    for (b = 0; b < joy[joy_num].num_buttons; b++) {
	if (b == start_b || b == menu_b)
	    continue;

	if (joy[joy_num].button[b].b) {
	    pressed[RAID_KEY_FIRE] = YES;
	    break;
	}
    }
}

- (void) setKey:(raid_key_t)k To:(int)k_
{
    if (k == RAID_KEY_START) {
	if (menu_b == k_)
	    menu_b = start_b;

	start_b = k_;
    }
    elif (k == RAID_KEY_MENU) {
	if (start_b == k_)
	    start_b = menu_b;

	menu_b = k_;
    }
}

- (int) getKey:(raid_key_t)k
{
    if (k == RAID_KEY_START) return start_b;
    elif (k == RAID_KEY_MENU) return menu_b;
    else
	return -1;
}

- (int) xPosition { return joy[joy_num].stick[x_stick].axis[x_axis].pos; }
- (int) yPosition { return joy[joy_num].stick[y_stick].axis[y_axis].pos; }

#ifdef WINFF
- free
{
    if (ff_player_hurt) {
	ff_destroy_effect(ff_player_hurt);
	ff_player_hurt = NULL;
    }

    if (ff_player_dying) {
	ff_destroy_effect(ff_player_dying);
	ff_player_dying = NULL; 
    }

    return [super free];
}

- (void) rumbleHurt
{
    if (ff_player_dying)
	ff_play_effect(ff_player_dying);
}

- (void) rumbleDying
{
    if (ff_player_dying)
	ff_play_effect(ff_player_dying);
}
#endif
@end

/*--------------------------------------------------------------*/

int input_init(void)
{
    install_keyboard();
    set_keyboard_rate(0, 1);

    /* For reading config files. */
    set_token_reader_stdio();

    /* main.m sets joy_enabled=NO to disable joystick support.  If
       joy_enabled=YES, then attempt to install joystick. */
    if (joy_enabled) {
	if (install_joystick(JOY_TYPE_AUTODETECT) == 0) {
	    FILE *fp = NULL;
	    char filename[PATH_MAX];
	    int j;
	    
	    /* Try ~/.raidem/joystick.cfg */
	    {
		char *homedir = getenv(HOME_PATH_ENV);
		snprintf(filename, sizeof filename, "%s/%s/%s",
			 homedir, RAIDEM_CONFIG_PATHNAME, JOYSTICK_CFG);
		fp = fopen(filename, "r");
	    }

	    if (not fp) {
		snprintf(filename, sizeof filename, "%s/" JOYSTICK_CFG,
			 raid_binary_directory);
		fp = fopen(filename, "r");
	    }

	    fprintf(stdout, "%d joystick(s) detected.\n", num_joysticks);

	    for (j = 0; j < num_joysticks; j++) {
		joystick_controller[j] = [[JoystickController alloc]
					     initWithJoystickNumber:j
					     ConfigFile:fp];
	    }

	    if (fp)
		fclose(fp);
	}
	else
	    fprintf(stderr, "Warning: could not initialize joystick(s).\n");
    }

    {
	KeyboardController *k;
	FILE *fp = NULL;
	char filename[PATH_MAX];

	/* Try ~/.raidem/keyboard.cfg */
	{
	    char *homedir = getenv("HOME");
	    snprintf(filename, sizeof filename, "%s/%s/%s",
		     homedir, RAIDEM_CONFIG_PATHNAME, KEYBOARD_CFG);
	    fp = fopen(filename, "r");
	}

	if (not fp) {
	    snprintf(filename, sizeof filename, "%s/" KEYBOARD_CFG,
		     raid_binary_directory);
	    fp = fopen(filename, "r");
	}


	/* Player 1. */
	k = [[KeyboardController alloc] initWithConfigFile:fp :"keyboard0"];
	assert(k);
	player_controls[0].keyboard = k;
	player_controls[0].joystick = joystick_controller[0];


	/* Player 2. */
	k = [[KeyboardController alloc] initWithConfigFile:fp :"keyboard1"];
	assert(k);
	player_controls[1].keyboard = k;
	player_controls[1].joystick = joystick_controller[1];

	if (fp)
	    fclose(fp);
    }

    {					/* Menu controls. */
	menu_keyboard_controls = [KeyboardController new];
	assert(menu_keyboard_controls);
	[menu_keyboard_controls setKey:RAID_KEY_UP    To:KEY_UP];
	[menu_keyboard_controls setKey:RAID_KEY_DOWN  To:KEY_DOWN];
	[menu_keyboard_controls setKey:RAID_KEY_LEFT  To:KEY_LEFT];
	[menu_keyboard_controls setKey:RAID_KEY_RIGHT To:KEY_RIGHT];
    }

    return 0;
}

void input_shutdown(void)
{
    int j;

    menu_keyboard_controls = [menu_keyboard_controls free];
    player_controls[0].keyboard = [player_controls[0].keyboard free];
    player_controls[1].keyboard = [player_controls[1].keyboard free];

    for (j = 0; j < num_joysticks; j++)
	joystick_controller[j] = [joystick_controller[j] free];
}
