#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 "input.h"

#define KEY_PRESS   ( 1.)
#define KEY_RELEASE (-1.)

// Keybind structure.
typedef struct Keybind_ {
  char* name;       // Keybinding name, taken from keybindNames[]
  KeybindType type; // type, defined in player.h.
  unsigned int key; // Key/axis/button event number.
  double reverse;   // 1. if normal, -1 if reversed, only useful for joystick axis.
} Keybind;

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


// Name of each keybinding.
const char* keybindNames[] = {
  "accel", "left", "right", "reverse", // Movement.
  "primary", "target", "target_nearest", "face", "board", // Combat.
  "secondary", "secondary_next", // Secondary weapons.
  "target_planet", "land", "thyperspace","starmap", "jump", // Navigation.
  "mapzoomin", "mapzoomout", "screenshot", "pause", "menu", "info", // Misc.
  "end" }; // Must terminate at the end.

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

// From player.c
extern double player_turn;
extern double player_acc;
extern unsigned int player_target;
// Grabbed from main.c
extern int show_fps;


// Set the default input keys.
void input_setDefault(void) {
  // Movement.
  input_setKeybind("accel",           KEYBIND_KEYBOARD, SDLK_w,       0);
  input_setKeybind("left",            KEYBIND_KEYBOARD, SDLK_a,       0);
  input_setKeybind("right",           KEYBIND_KEYBOARD, SDLK_d,       0);
  input_setKeybind("reverse",         KEYBIND_KEYBOARD, SDLK_s,       0);
  // Combat.
  input_setKeybind("primary",         KEYBIND_KEYBOARD, SDLK_SPACE,   0);
  input_setKeybind("target",          KEYBIND_KEYBOARD, SDLK_TAB,     0);
  input_setKeybind("target_nearest",  KEYBIND_KEYBOARD, SDLK_r,       0);
  input_setKeybind("face",            KEYBIND_KEYBOARD, SDLK_f,       0);
  input_setKeybind("board",           KEYBIND_KEYBOARD, SDLK_b,       0);
  // Secondary weapon.
  input_setKeybind("secondary",       KEYBIND_KEYBOARD, SDLK_LSHIFT,  0);
  input_setKeybind("secondary_next",  KEYBIND_KEYBOARD, SDLK_e,       0);
  // Space
  input_setKeybind("target_planet",   KEYBIND_KEYBOARD, SDLK_p,       0);
  input_setKeybind("land",            KEYBIND_KEYBOARD, SDLK_l,       0);
  input_setKeybind("thyperspace",     KEYBIND_KEYBOARD, SDLK_h,       0);
  input_setKeybind("starmap",         KEYBIND_KEYBOARD, SDLK_m,       0);
  input_setKeybind("jump",            KEYBIND_KEYBOARD, SDLK_j,       0);

  // Misc.
  input_setKeybind("mapzoomin",       KEYBIND_KEYBOARD, SDLK_0,       0);
  input_setKeybind("mapzoomout",      KEYBIND_KEYBOARD, SDLK_9,       0);
  input_setKeybind("screenshot",      KEYBIND_KEYBOARD, SDLK_F12,     0);
  input_setKeybind("pause",           KEYBIND_KEYBOARD, SDLK_F1,      0);
  input_setKeybind("menu",            KEYBIND_KEYBOARD, SDLK_ESCAPE,  0);
  input_setKeybind("info",            KEYBIND_KEYBOARD, SDLK_i,       0);
}

// Initialization/exit functions (does not assign 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 = 0;
    tmp->reverse = 1.;
    input_keybinds[i] = tmp;
  }
}

void input_exit(void) {
  int i;
  for(i = 0; strcmp(keybindNames[i], "end"); i++)
    free(input_keybinds[i]);
  free(input_keybinds);
}

// Binds key of type [type] to action keybind.
void input_setKeybind(char* keybind, KeybindType type, int key, 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;
      input_keybinds[i]->reverse = reverse ? -1. : 1.;
      return;
    }
  WARN("Unable to set keybind '%s', That command does not exist.", keybind);
}

// == Run input method. ================================================
// keynum : Index of the input_keybinds keybind.
// value  : Value of keypress (defined above).
// abs    : Whether or not it's an abs value (For those pesky joysticks.
// =====================================================================
#define KEY(s)   (strcmp(input_keybinds[keynum]->name, s)==0)
#define INGAME() (!toolkit)
// We won't be having any more funny stuff from VLack..
#define NOHYP() \
  (player && !pilot_isFlag(player, PILOT_HYP_PREP) && \
  !pilot_isFlag(player, PILOT_HYP_BEGIN) && \
  !pilot_isFlag(player, PILOT_HYPERSPACE))
static void input_key(int keynum, double value, int abs) {
  unsigned int t;

  // Accelerating.
  if(KEY("accel")) {
    if(abs)player_acc = value;
    else player_acc += value;
    // Double tap accel = afterburn!
    t = SDL_GetTicks();
    if((value == KEY_PRESS) && (t-input_accelLast <= input_afterburnSensibility)) {
      player_afterburn();
    }
    else if((value == KEY_RELEASE) && player_isFlag(PLAYER_AFTERBURNER))
      player_afterburnOver();
    else
      player_acc = ABS(player_acc); // Make sure value is sane.

    if(value == KEY_PRESS) input_accelLast = t;
  }
  // Turning left.
  else if(KEY("left")) {
    // Set flags for facing correction.
    if(value == KEY_PRESS) { player_setFlag(PLAYER_TURN_LEFT); }
    else if(value == KEY_RELEASE) { player_rmFlag(PLAYER_TURN_LEFT); }

    if(abs) { 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_setFlag(PLAYER_TURN_RIGHT); }
    else if(value == KEY_RELEASE) { player_rmFlag(PLAYER_TURN_RIGHT); }

    if(abs) { 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_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_setFlag(PLAYER_PRIMARY); }
    else if(value == KEY_RELEASE) { player_rmFlag(PLAYER_PRIMARY); }
  }
  // Targetting.
  else if(KEY("target") && INGAME()) {
    if(value == KEY_PRESS) player_target = pilot_getNext(player_target);
  }
  else if(KEY("target_nearest") && INGAME()) {
    if(value == KEY_PRESS) player_target = pilot_getHostile();
  }
  // Face the target.
  else if(KEY("face")) {
    if(value == KEY_PRESS) { 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_board();
  }
  // Shooting secondary weapon.
  else if(KEY("secondary") && INGAME() && 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.
  // 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_land();
  }
  else if(KEY("thyperspace") && INGAME() && NOHYP()) {
    if(value == KEY_PRESS) player_targetHyperspace();
  }
  else if(KEY("starmap") && NOHYP()) {
    if(value == KEY_PRESS) map_open();
  }
  else if(KEY("jump") && INGAME()) {
    if(value == KEY_PRESS) player_jump();
  }
  // 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();
      } else pause();
    }
  }
  // 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_joydown(const unsigned int button);
static void input_joyup(const unsigned int button);
static void input_keydown(SDLKey key);
static void input_keyup(SDLKey key);

// 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);
      return;
    }
}

// Joystick button down.
static void input_joydown(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, KEY_RELEASE, 0);
      return;
    }
}

// Joystick button up.
static void input_joyup(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, KEY_RELEASE, 0);
      return;
    }
}

// Keyboard.

// Key down.
static void input_keydown(SDLKey key) {
  int i;
  for(i = 0; strcmp(keybindNames[i], "end"); i++)
    if(input_keybinds[i]->type == KEYBIND_KEYBOARD &&
       input_keybinds[i]->key == key) {
      input_key(i, KEY_PRESS, 0);
      return;
    }
}

// Key up.
static void input_keyup(SDLKey key) {
  int i;
  for(i = 0; strcmp(keybindNames[i], "end"); i++)
    if(input_keybinds[i]->type == KEYBIND_KEYBOARD &&
       input_keybinds[i]->key == key) {
      input_key(i, KEY_RELEASE, 0);
      return;
    }
}

// Global input.

// Just seperates the event types.
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();
      else if((event->active.gain == 1) && pause) unpause();
      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_joydown(event->jbutton.button);
    break;
  case SDL_JOYBUTTONUP:
    input_joyup(event->jbutton.button);
    break;
  case SDL_KEYDOWN:
    input_keydown(event->key.keysym.sym);
    break;
  case SDL_KEYUP:
    input_keyup(event->key.keysym.sym);
    break;
  }
}