/**
 * @file input.c
 *
 * @brief Handle all the keybindings and input.
 */

#include "lephisto.h"
#include "log.h"
#include "player.h"
#include "pause.h"
#include "toolkit.h"
#include "menu.h"
#include "board.h"
#include "map.h"
#include "escort.h"
#include "land.h"
#include "input.h"

#define KEY_PRESS   ( 1.) /**< Key is pressed. */
#define KEY_RELEASE (-1.) /**< Key is released. */

/* Keybind structure. */
typedef struct Keybind_ {
  char* name;       /**< Keybinding name, taken from keybindNames[] */
  KeybindType type; /**< type, defined in player.h. */
  SDLKey key;       /**< Key/axis/button event number. */
  double reverse;   /**< 1. if normal, -1 if reversed, only useful for joystick axis. */
  SDLMod mod;       /**< Key modifiers (where applicable). */
} Keybind;

static Keybind** input_keybinds; /**< Contains the players keybindings. */


/* Name of each keybinding. */
const char* keybindNames[] = {
	/* Movement. */
  "accel", "left", "right", "reverse", "afterburn",
  /* Targetting. */
  "target", "target_prev", "target_nearest", "target_hostile",
  /* Fighting. */
  "primary", "face", "board",
  /* Escorts. */
  "e_attack", "e_hold", "e_return", "e_clear",
  /* Secondary weapons. */
  "secondary", "secondary_next",
  /* Space Navigation. */
  "autonav", "target_planet", "land", "thyperspace","starmap", "jump",
  /* Communication. */
  "hail",
  /* Misc. */
  "mapzoomin", "mapzoomout", "screenshot", "pause", "menu", "info",
  "end" /* Must terminate at the end. */
};

/* Accel hacks. */
static unsigned int input_accelLast  = 0;       /**< Used to see if double tap. */
unsigned int input_afterburnSensibility = 200;  /**< ms between taps to afterburn. */

/* From player.c */
extern double player_turn;

/**
 * @fn void input_setDefault(void)
 *
 * @brief Set the default input keys.
 */
void input_setDefault(void) {
  /* Movement. */
  input_setKeybind("accel",           KEYBIND_KEYBOARD, SDLK_w,           KMOD_ALL,   0);
  input_setKeybind("afterburn",       KEYBIND_KEYBOARD, SDLK_UNKNOWN,     KMOD_ALL,   0);
  input_setKeybind("left",            KEYBIND_KEYBOARD, SDLK_a,           KMOD_ALL,   0);
  input_setKeybind("right",           KEYBIND_KEYBOARD, SDLK_d,           KMOD_ALL,   0);
  input_setKeybind("reverse",         KEYBIND_KEYBOARD, SDLK_s,           KMOD_ALL,   0);
  /* Targetting. */
  input_setKeybind("target",          KEYBIND_KEYBOARD, SDLK_TAB,         KMOD_NONE,  0);
  input_setKeybind("target_prev",     KEYBIND_KEYBOARD, SDLK_TAB,         KMOD_RCTRL, 0);
  input_setKeybind("target_nearest",  KEYBIND_KEYBOARD, SDLK_t,           KMOD_NONE,  0);
  input_setKeybind("target_hostile",  KEYBIND_KEYBOARD, SDLK_r,           KMOD_NONE,  0);
  /* Combat. */
  input_setKeybind("primary",         KEYBIND_KEYBOARD, SDLK_SPACE,       KMOD_NONE,  0);
  input_setKeybind("face",            KEYBIND_KEYBOARD, SDLK_f,           KMOD_NONE,  0);
  input_setKeybind("board",           KEYBIND_KEYBOARD, SDLK_b,           KMOD_NONE,  0);
  /* Escorts. */
  input_setKeybind("e_attack",        KEYBIND_KEYBOARD, SDLK_1,           KMOD_NONE,  0);
  input_setKeybind("e_hold",          KEYBIND_KEYBOARD, SDLK_2,           KMOD_NONE,  0);
  input_setKeybind("e_return",        KEYBIND_KEYBOARD, SDLK_3,           KMOD_NONE,  0);
  input_setKeybind("e_clear",         KEYBIND_KEYBOARD, SDLK_4,           KMOD_NONE,  0);


  /* Secondary weapon. */
  input_setKeybind("secondary",       KEYBIND_KEYBOARD, SDLK_LALT,        KMOD_ALL,   0);
  input_setKeybind("secondary_next",  KEYBIND_KEYBOARD, SDLK_e,           KMOD_NONE,  0);
  /* Space */
  input_setKeybind("autonav",					KEYBIND_KEYBOARD, SDLK_j,					  KMOD_LCTRL, 0);
  input_setKeybind("target_planet",   KEYBIND_KEYBOARD, SDLK_p,           KMOD_NONE,  0);
  input_setKeybind("land",            KEYBIND_KEYBOARD, SDLK_l,           KMOD_NONE,  0);
  input_setKeybind("thyperspace",     KEYBIND_KEYBOARD, SDLK_h,           KMOD_NONE,  0);
  input_setKeybind("starmap",         KEYBIND_KEYBOARD, SDLK_m,           KMOD_NONE,  0);
  input_setKeybind("jump",            KEYBIND_KEYBOARD, SDLK_j,           KMOD_NONE,  0);
  /* Communication. */
  input_setKeybind("hail",            KEYBIND_KEYBOARD, SDLK_y,           KMOD_NONE,  0);
  /* Misc. */
  input_setKeybind("mapzoomin",       KEYBIND_KEYBOARD, SDLK_KP_PLUS,     KMOD_NONE,  0);
  input_setKeybind("mapzoomout",      KEYBIND_KEYBOARD, SDLK_KP_MINUS,    KMOD_NONE,  0);
  input_setKeybind("screenshot",      KEYBIND_KEYBOARD, SDLK_KP_MULTIPLY, KMOD_NONE,  0);
  input_setKeybind("pause",           KEYBIND_KEYBOARD, SDLK_F1,          KMOD_NONE,  0);
  input_setKeybind("menu",            KEYBIND_KEYBOARD, SDLK_ESCAPE,      KMOD_NONE,  0);
  input_setKeybind("info",            KEYBIND_KEYBOARD, SDLK_i,           KMOD_NONE,  0);
}

/**
 * @fn void input_init(void)
 *
 * @brief Initialize the input subsystem (does not set keys).
 */
void input_init(void) {
  Keybind* tmp;
  int i;
  for(i = 0; strcmp(keybindNames[i], "end"); i++); /* Get number of bindings. */
  input_keybinds = malloc(i*sizeof(Keybind*));

  /* Create a null keybinding for each. */
  for(i = 0; strcmp(keybindNames[i], "end"); i++) {
    tmp = MALLOC_L(Keybind);
    tmp->name = (char*)keybindNames[i];
    tmp->type = KEYBIND_NULL;
    tmp->key = SDLK_UNKNOWN;
    tmp->mod = KMOD_NONE;
    tmp->reverse = 1.;
    input_keybinds[i] = tmp;
  }
}

/**
 * @fn void input_exit(void)
 *
 * @brief Exit the input subsystem.
 */
void input_exit(void) {
  int i;
  for(i = 0; strcmp(keybindNames[i], "end"); i++)
    free(input_keybinds[i]);
  free(input_keybinds);
}

/**
 * @fn void input_setKeybind(char* keybind, KeybindType type, int key, int reverse)
 *
 * @brief Bind a key of type to action keybind.
 *    @param keybind The name of the keybind defined above.
 *    @param type The type of the keybind.
 *    @param key The key to bind to.
 *    @param mod Modifiers to check for.
 *    @param reverse Whether to reverse it or not.
 */
void input_setKeybind(char* keybind, KeybindType type, int key,
    SDLMod mod, int reverse) {
  int i;
  for(i = 0; strcmp(keybindNames[i], "end"); i++)
    if(strcmp(keybind, input_keybinds[i]->name)==0) {
      input_keybinds[i]->type = type;
      input_keybinds[i]->key = key;
      /* Non-keyboards get mod KMOD_ALL to always match. */
      input_keybinds[i]->mod = (type == KEYBIND_KEYBOARD) ? mod : KMOD_ALL;
      input_keybinds[i]->reverse = reverse ? -1. : 1.;
      return;
    }
  WARN("Unable to set keybind '%s', That command does not exist.", keybind);
}

/* @fn int input_getKeybind(char* keybind, KeybindType* type, int* reverse)
 *
 * @brief Get the value of a keybind.
 */
int input_getKeybind(char* keybind, KeybindType* type, SDLMod* mod, int* reverse) {
  int i;
  for(i = 0; strcmp(keybindNames[i], "end"); i++)
    if(strcmp(keybind, input_keybinds[i]->name)==0) {
      if(type != NULL) (*type) = input_keybinds[i]->type;
      if(mod != NULL)  (*mod)  = input_keybinds[i]->mod;
      if(reverse != NULL) (*reverse) = input_keybinds[i]->reverse;
      return input_keybinds[i]->key;
    }

  WARN("Unable to get keybinding '%s', that command doesn't exist", keybind);
  return -1;
}

/**
 * @fn static void input_key(int keynum, double value, int kabs)
 *
 * @brief Run the input command.
 *    @param keynum The index of the keybind.
 *    @param value The value of the keypress (defined above).
 *    @param abs Whether or not it's an absolute value (for the joystick).
 */
#define KEY(s) (strcmp(input_keybinds[keynum]->name, s)==0) /**< Shortcut for ease. */
#define INGAME() (!toolkit) /**< Make sure player is in game. */
#define NOHYP() \
  (player && !pilot_isFlag(player, PILOT_HYP_PREP) && \
  !pilot_isFlag(player, PILOT_HYP_BEGIN) && \
  !pilot_isFlag(player, PILOT_HYPERSPACE))  /**< Make sure player isn't jumping. */
#define NODEAD()    (player)  /**< Player isn't dead. */
#define NOLAND()    (!landed) /**< Player isn't landed. */
static void input_key(int keynum, double value, int kabs) {
  unsigned int t;

  /* Accelerating. */
  if(KEY("accel")) {
    if(kabs) {
      player_abortAutonav();
      player_accel(value);
    }
    else {
      /* Prevent it from getting stuck. */
      if(value == KEY_PRESS) {
        player_abortAutonav();
        player_accel(1.);
      }
      else if(value == KEY_RELEASE) player_accelOver();
    }

    /* Double tap accel = afterburn! */
    t = SDL_GetTicks();
    if((value == KEY_PRESS) && INGAME() && NOHYP() &&
        (t-input_accelLast <= input_afterburnSensibility))
      player_afterburn();
    else if((value == KEY_RELEASE) && player_isFlag(PLAYER_AFTERBURNER))
      player_afterburnOver();

    if(value == KEY_PRESS) input_accelLast = t;
  }
  else if(KEY("afterburn") && INGAME() && NOHYP()) {
    if(value == KEY_PRESS) player_afterburn();
    else if(value == KEY_RELEASE) player_afterburnOver();
  }
  /* Turning left. */
  else if(KEY("left")) {
    /* Set flags for facing correction. */
    if(value == KEY_PRESS) {
      player_abortAutonav();
      player_setFlag(PLAYER_TURN_LEFT);
    }
    else if(value == KEY_RELEASE) { player_rmFlag(PLAYER_TURN_LEFT); }

    if(kabs) { player_turn = -value; }
    else { player_turn -= value; }
    if(player_turn < -1.) player_turn = -1.; /* Make sure value is sane. */
  }
  /* Turning right. */
  else if(KEY("right")) {
    /* Set flags for facing correction. */
    if(value == KEY_PRESS) {
      player_abortAutonav();
      player_setFlag(PLAYER_TURN_RIGHT);
    }
    else if(value == KEY_RELEASE) { player_rmFlag(PLAYER_TURN_RIGHT); }

    if(kabs) { player_turn = value; }
    else { player_turn += value; }

    if(player_turn < -1.) { player_turn = -1.; } /* Make sure value is sane. */
  }
  /* Turn around to face vel. */
  else if(KEY("reverse")) {
    if(value == KEY_PRESS) {
      player_abortAutonav();
      player_setFlag(PLAYER_REVERSE);
    }
    else if(value == KEY_RELEASE) {
      player_rmFlag(PLAYER_REVERSE);
      player_turn = 0; /* Turning corrections. */
      if(player_isFlag(PLAYER_TURN_LEFT))  { player_turn -= 1; }
      if(player_isFlag(PLAYER_TURN_RIGHT)) { player_turn += 1; }
    }
  }
  /* Shoot primary weapon. BOOM BOOM. */
  else if(KEY("primary")) {
    if(value == KEY_PRESS) {
      player_abortAutonav();
      player_setFlag(PLAYER_PRIMARY);
    }
    else if(value == KEY_RELEASE) { player_rmFlag(PLAYER_PRIMARY); }
  }
  /* Targetting. */
  else if(INGAME() && NODEAD() && KEY("target")) {
    if(value == KEY_PRESS) player_targetNext();
  }
  else if(INGAME() && NODEAD() && KEY("target_prev")) {
    if(value == KEY_PRESS) player_targetPrev();
  }
  else if(INGAME() && NODEAD() && KEY("target_nearest")) {
    if(value == KEY_PRESS) player_targetNearest();
  }
  else if(INGAME() && NODEAD() && KEY("target_hostile")) {
    if(value == KEY_PRESS) player_targetHostile();
  }
  /* Face the target. */
  else if(KEY("face")) {
    if(value == KEY_PRESS) {
      player_abortAutonav();
      player_setFlag(PLAYER_FACE);
    }
    else if(value == KEY_RELEASE) {
      player_rmFlag(PLAYER_FACE);

      /* Turning corrections. */
      player_turn = 0;
      if(player_isFlag(PLAYER_TURN_LEFT))  { player_turn -= 1; }
      if(player_isFlag(PLAYER_TURN_RIGHT)) { player_turn += 1; }
    }
  }
  /* Board those ships. */
  else if(KEY("board") && INGAME() && NOHYP()) {
    if(value == KEY_PRESS) {
      player_abortAutonav();
      player_board();
    }
  }
  /* Escorts. */
  else if(INGAME() && NODEAD() && KEY("e_attack")) {
    if(value == KEY_PRESS) escorts_attack(player);
  }
  else if(INGAME() && NODEAD() && KEY("e_hold")) {
    if(value == KEY_PRESS) escorts_hold(player);
  }
  else if(INGAME() && NODEAD() && KEY("e_return")) {
    if(value == KEY_PRESS) escorts_return(player);
  }
  else if(INGAME() && NODEAD() && KEY("e_clear")) {
    if(value == KEY_PRESS) escorts_clear(player);
  }
  /* Shooting secondary weapon. */
  else if(KEY("secondary") && NOHYP()) {
    if(value == KEY_PRESS) { player_setFlag(PLAYER_SECONDARY); }
    else if(value == KEY_RELEASE) { player_rmFlag(PLAYER_SECONDARY); }
  }
  /* Selecting secondary weapon. */
  else if(KEY("secondary_next") && INGAME()) {
    if(value == KEY_PRESS) player_secondaryNext();
  }
  /* Space. */
  else if(KEY("autonav") && INGAME() && NOHYP()) {
  	if(value == KEY_PRESS) player_startAutonav();
  }
  /* Target planet (cycles just like target). */
  else if(KEY("target_planet") && INGAME() && NOHYP()) {
    if(value == KEY_PRESS) player_targetPlanet();
  }
  /* Target nearest planet or attempt to land. */
  else if(KEY("land") && INGAME() && NOHYP()) {
    if(value == KEY_PRESS) {
      player_abortAutonav();
      player_land();
    }
  }
  else if(KEY("thyperspace") && NOHYP() && NOLAND() && NODEAD()) {
    if(value == KEY_PRESS) {
      player_abortAutonav();
      player_targetHyperspace();
    }
  }
  else if(KEY("starmap") && NOHYP() && NODEAD()) {
    if(value == KEY_PRESS) map_open();
  }
  else if(KEY("jump") && INGAME()) {
    if(value == KEY_PRESS) {
      player_abortAutonav();
      player_jump();
    }
  }
  /* Communication. */
  else if(KEY("hail") && INGAME() && NOHYP()) {
    if(value == KEY_PRESS) {
      player_hail();
    }
  }
  /* Zoom in. */
  else if(KEY("mapzoomin") && INGAME()) {
    if(value == KEY_PRESS) player_setRadarRel(1);
  }
  /* Zoom out. */
  else if(KEY("mapzoomout") && INGAME()) {
    if(value == KEY_PRESS) player_setRadarRel(-1);
  }
  /* Take a screenshot. */
  else if(KEY("screenshot") && INGAME()) {
    if(value == KEY_PRESS) player_screenshot();
  }
  /* Pause the game. */
  else if(KEY("pause") && NOHYP()) {
    if(value == KEY_PRESS) {
      if(!toolkit) {
        if(paused) unpause_game();
      } else pause_game();
    }
  }
  /* Opens a menu. */
  else if(KEY("menu")) {
    if(value == KEY_PRESS) menu_small();
  }
  /* Show pilot information. */
  else if(KEY("info") && NOHYP()) {
    if(value == KEY_PRESS) menu_info();
  }
}
#undef KEY

/* --Events-- */

static void input_joyaxis(const unsigned int axis, const int value);
static void input_joyevent(int event, const unsigned int button);
static void input_keyevent(int event, SDLKey key, SDLMod mod);

/* Joystick. */

/* Axis. */
static void input_joyaxis(const unsigned int axis, const int value) {
  int i;
  for(i = 0; strcmp(keybindNames[i], "end"); i++)
    if(input_keybinds[i]->type == KEYBIND_JAXIS &&
       input_keybinds[i]->key == axis) 
      input_key(i, -(input_keybinds[i]->reverse) * (double)value / 32767., 1);
}

/* Joystick event. */
static void input_joyevent(int event, const unsigned int button) {
  int i;
  for(i = 0; strcmp(keybindNames[i], "end");i++)
    if(input_keybinds[i]->type == KEYBIND_JBUTTON &&
       input_keybinds[i]->key == button)
      input_key(i, event, 0);
}

/* Keyboard. */

/* Key event. */
static void input_keyevent(int event, SDLKey key, SDLMod mod) {
  int i;

  mod &= ~(KMOD_CAPS | KMOD_NUM | KMOD_MODE); /* We want to ignore "global" modifiers. */

  for(i = 0; strcmp(keybindNames[i], "end"); i++)
    if((input_keybinds[i]->type == KEYBIND_KEYBOARD) &&
        (input_keybinds[i]->key == key) &&
        ((input_keybinds[i]->mod == mod) || (input_keybinds[i]->mod == KMOD_ALL)))
      input_key(i, event, 0);
}


/**
 * @fn void input_handle(SDL_Event* event)
 *
 * @brief Handle global input.
 *
 * Basically seperates the event types.
 *    @param event Incoming SDL_Event.
 */
void input_handle(SDL_Event* event) {
  /* Pause the game if it is unfocused. */
  if(event->type == SDL_ACTIVEEVENT) {
    if(event->active.state != SDL_APPMOUSEFOCUS) {
      /* We don't need mouse focus. */
      if((event->active.gain == 0) && !paused) pause_game();
      else if((event->active.gain == 1) && paused) unpause_game();
      return;
    }
  }

  if(toolkit)
    /* Toolkit is handled seperately. */
    if(toolkit_input(event))
      return; /* We don't process it if toolkit grabs it. */

  switch(event->type) {
  case SDL_JOYAXISMOTION:
    input_joyaxis(event->jaxis.axis, event->jaxis.value);
    break;
  case SDL_JOYBUTTONDOWN:
    input_joyevent(KEY_PRESS, event->jbutton.button);
    break;
  case SDL_JOYBUTTONUP:
    input_joyevent(KEY_RELEASE, event->jbutton.button);
    break;
  case SDL_KEYDOWN:
    input_keyevent(KEY_PRESS, event->key.keysym.sym, event->key.keysym.mod);
    break;
  case SDL_KEYUP:
    input_keyevent(KEY_RELEASE, event->key.keysym.sym, event->key.keysym.mod);
    break;
  }
}