#include <SDL.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <unistd.h>
#include <string.h>

#include "def.h"
#include "log.h"
#include "physics.h"
#include "opengl.h"
#include "ship.h"
#include "pilot.h"
#include "player.h"
#include "joystick.h"
#include "space.h"
#include "rng.h"
#include "ai.h"
#include "pilot.h"

#define WINDOW_CAPTION  "Lephisto"
#define CONF_FILE       "conf"
#define MINIMUM_FPS     0.5

extern const char* keybindNames[]; // Keybindings.

static int quit = 0; // Primary loop.
static unsigned int time = 0; // Calculate FPS and movement.

// Prototypes.

static void print_usage(char** argv);
static void display_fps(const double dt);

// Update.
static void update_all(void);

// Usage.
static void print_usage(char** argv) {
  LOG("USAGE: %s [-f] [-j n | -J s] [-hv]", argv[0]);
  LOG("Options are:");
  LOG("\t-f   - Fullscreen");
  LOG("\t-w n - Set width to (n)");
  LOG("\t-h n - Set height to (n)");
  LOG("\t-j n - Use joystick (n)");
  LOG("\t-J s - Use joystick whose name contains (s)");
  LOG("\t-h   - Display this message and exit.");
  LOG("\t-v   - Print the version and exit");
}

int main(int argc, char** argv) {
  int i;
  // Initialize SDL for possible warnings.
  SDL_Init(0);
  // Default values..
  gl_screen.w = 800;
  gl_screen.h = 640;
  gl_screen.fullscreen = 0;
  // Joystick.
  int indjoystick = -1;
  char* namjoystick = NULL;

  // input.
  input_init();
  input_setKeybind("accel",  KEYBIND_KEYBOARD, SDLK_w,      0);
  input_setKeybind("left",   KEYBIND_KEYBOARD, SDLK_a,      0);
  input_setKeybind("right",  KEYBIND_KEYBOARD, SDLK_d,      0);

  // Use Lua to parse configuration file.
  lua_State* L = luaL_newstate();
  if(luaL_dofile(L, CONF_FILE) == 0) { // Conf file exists.
    // OpenGL properties.
    lua_getglobal(L, "width");
    if(lua_isnumber(L, -1))
      gl_screen.w = (int)lua_tonumber(L, -1);
    lua_getglobal(L, "height");
    if(lua_isnumber(L, -1))
      gl_screen.h = (int)lua_tonumber(L, -1);
    lua_getglobal(L, "fullscreen");
    if(lua_isnumber(L, -1))
      if((int)lua_tonumber(L, -1) == 1)
        gl_screen.fullscreen = 1;

    // Joystick.
    lua_getglobal(L, "joystick");
    if(lua_isnumber(L, -1))
      indjoystick = (int)lua_tonumber(L, -1);
    else if(lua_isstring(L, -1))
      namjoystick = strdup((char*)lua_tostring(L, -1));

    // Grab the keybindings if there are any.
    char* str;
    int type, key, reverse;
    for(i = 0; keybindNames[i]; i++) {
      lua_getglobal(L, keybindNames[i]);
      str = NULL;
      key = -1;
      reverse = 0;
      if(lua_istable(L, -1)) { // It's a table alright.
        // Get the event type.
        lua_pushstring(L, "type");
        lua_gettable(L, -2);
        if(lua_isstring(L, -1))
          str = (char*)lua_tostring(L, -1);

        // Get the key.
        lua_pushstring(L, "key");
        lua_gettable(L, -3);
        if(lua_isnumber(L, -1))
          key = (int)lua_tonumber(L, -1);

        // Is it reversed? Only useful for axis.
        lua_pushstring(L, "reverse");
        lua_gettable(L, -4);
        if(lua_isnumber(L, -1))
          reverse = 1;

        if(key != -1 && str != NULL) { // Keybind is valid!
          // Get the type.
          if(strcmp(str, "null")==0)            type = KEYBIND_NULL;
          else if(strcmp(str, "keyboard")==0)   type = KEYBIND_KEYBOARD;
          else if(strcmp(str, "jaxis")==0)      type = KEYBIND_JAXIS;
          else if(strcmp(str, "jbutton")==0)    type = KEYBIND_JBUTTON;
          else {
            WARN("Unknown keybinding of type %s", str);
            continue;
          }
          // Set the keybind.
          input_setKeybind((char*)keybindNames[i], type, key, reverse);
        } else WARN("Malformed keybind in %s", CONF_FILE);
      }
    }
  }
  lua_close(L);

  // Parse arguments.
  int c = 0;
  while((c = getopt(argc, argv, "fJ:j:hv")) != -1) {
    switch(c) {
      case 'f':
        gl_screen.fullscreen = 1;
        break;
      case 'j':
        indjoystick = atoi(optarg);
        break;
      case 'J':
        namjoystick = strdup(optarg);
        break;
      case 'v':
        LOG("Lephisto: version %d.%d.%d\n", VMAJOR, VMINOR, VREV);
      case 'h':
        print_usage(argv);
        exit(EXIT_SUCCESS);
    }
  }

  // Random numbers.
  rng_init();

  if(gl_init()) {
    // Initializes video output.
    WARN("Error initializing video output, exiting...");
    exit(EXIT_FAILURE);
  }

  // Window.
  SDL_WM_SetCaption(WINDOW_CAPTION, NULL);

  // Input.
  if(indjoystick >= 0 || namjoystick != NULL) {
    if(joystick_init())
      WARN("Error initializing joystick input");
    if(namjoystick != NULL) {
      joystick_use(joystick_get(namjoystick));
      free(namjoystick);
    }
    else if(indjoystick >= 0)
      joystick_use(indjoystick);
  }

  // Misc.
  if(ai_init())
    WARN("Error initializing AI");

  gl_fontInit(NULL, "../gfx/fonts/FreeSans.ttf", 16);

  // Data loading.
  ships_load();

  // Testing.
  pilot_create(get_ship("Ship"), "Player", NULL, NULL, PILOT_PLAYER);
  gl_bindCamera(&player->solid->pos);
  space_init();

  pilot_create(get_ship("Miss. Test"), NULL, NULL, NULL, 0);

  time = SDL_GetTicks();

  // Main looops.
  SDL_Event event;
  // flushes the event loop, since I notices that when the joystick is loaded, it
  // creates button events that results in the player starting out accelerating.
  while(SDL_PollEvent(&event));
  while(!quit) {
    // Event loop.
    while(SDL_PollEvent(&event)) {
      if(event.type == SDL_QUIT) quit = 1; // Handle quit.
      input_handle(&event); // handles all the events the player keybinds.
    }
    update_all();
  }

  // Unload data.
  space_exit();   // Clean up the universe!!!
  pilots_free();  // Free the pilots, they where locked up D:
  ships_free();

  gl_freeFont(NULL);

  // Exit subsystems.
  ai_exit();        // Stop the Lua AI magicness.
  joystick_exit();  // Release joystick.
  input_exit();     // Clean up keybindings.
  gl_exit();        // Kills video output.
  exit(EXIT_SUCCESS);
}

// == Update everything. ==================================
// Blitting order. (layers)
//
//    BG  | Stars and planets.
//        | Background particles.
//  X
//    N   | NPC ships.
//        | Normal layer particles (above ships).
//  X
//    FG  | Player.
//        | Foreground particles.
//        | Text and GUI.
// ========================================================
static void update_all(void) {
  // dt in us.
  double dt = (double)(SDL_GetTicks() - time) / 1000.;
  time = SDL_GetTicks();
  
  if(dt > MINIMUM_FPS) {
    Vec2 pos;
    vect_cset(&pos, 10., (double)(gl_screen.h-40));
    gl_print(NULL, &pos, "FPS is really low! Skipping frames.");
    SDL_GL_SwapBuffers();
    return;
  }

  glClear(GL_COLOR_BUFFER_BIT);
  
  space_render(dt);
  pilots_update(dt);

  display_fps(dt);

  SDL_GL_SwapBuffers();
}


// Spit this out on display.
static double fps = 0.;
static double fps_cur = 0.;
static double fps_dt = 1.;
static void display_fps(const double dt) {
  fps_dt += dt;
  fps_cur += 1.;
  if(fps_dt > 1.) {
    fps = fps_cur / fps_dt;
    fps_dt = fps_cur = 0.;
  }
  Vec2 pos;
  vect_cset(&pos, 10., (double)(gl_screen.h-20));
  gl_print(NULL, &pos, "%3.2f", fps);
}