#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>

#include "llua.h"
#include "lauxlib.h"

#include "lephisto.h"
#include "log.h"
#include "player.h"
#include "opengl.h"
#include "input.h"
#include "music.h"
#include "nebulae.h"
#include "conf.h"

#define conf_loadInt(n,i) \
  lua_getglobal(L,n); \
  if(lua_isnumber(L, -1)) { \
  i = (int)lua_tonumber(L, -1); \
  lua_remove(L, -1); \
  }

#define conf_loadFloat(n,f) \
  lua_getglobal(L,n); \
  if(lua_isnumber(L, -1)) { \
  f = (double)lua_tonumber(L, -1); \
  lua_remove(L,-1);\
  }

#define conf_loadBool(n,b) \
  lua_getglobal(L,n); \
  if(lua_isnumber(L, -1)) \
  if((int)lua_tonumber(L, -1) == 1) { \
  b = 1; \
  lua_remove(L, -1); \
  } \

#define conf_loadString(n,s) \
  lua_getglobal(L,n); \
  if(lua_isstring(L, -1)) { \
  s = strdup((char*)lua_tostring(L, -1)); \
  lua_remove(L, -1); \
  }

/* Some crap from main. */
extern int nosound;
extern int show_fps;
extern int max_fps;
extern int indjoystick;
extern char* namjoystick;
/* From player.c */
extern const char* keybindNames[]; /* Keybindings. */
/* input.c. */
extern unsigned int input_afterburnSensibility;

static void print_usage(char** argv);

/* Print usage. */
static void print_usage(char** argv) {
  LOG("USAGE: %s [OPTION]", argv[0]);
  LOG("Options are:");
  LOG(" -f,     --fullscreen   - Fullscreen");
  LOG(" -F,     --fps          - Limit frames per second");
  LOG(" -d s,   --data s       - Set the data file to be s");
  LOG(" -W n                   - Set width to n");
  LOG(" -H n                   - Set height to n");
  LOG(" -j n,   --joystick n   - Use joystick (n)");
  LOG(" -J s,   --joystick s   - Use joystick whose name contains (s)");
  LOG(" -S,     --Sound        - Forces sound.");
  LOG(" -m f    --mvol f       - Set the music volume to f");
  LOG(" -s f    --svol f       - Set the sound volume to f");
  LOG(" -G                     - Regenerates the nebulae (slow)");
  LOG(" -h      --help         - Display this message and exit.");
  LOG(" -v                     - Print the version and exit");
}

/* Set the default configuration. */
void conf_setDefaults(void) {
  /* Global. */
  data = DATA_DEF;
  /* GL. */
  gl_screen.w = 800;
  gl_screen.h = 600;
  gl_screen.flags = 0;
  /* Openal. */
  nosound = 0;
  /* Joystick. */
  indjoystick = -1;
  namjoystick = NULL;
  /* Input. */
  input_setDefault();
}

/* Ok.. Parse a config file plox. */
int conf_loadConfig(const char* file) {
  int i = 0;
  double d = 0.;

  lua_State* L = llua_newState();
  if(luaL_dofile(L, file) == 0) {
    /* Conf file exists indeed. */
    /* Global. */
    conf_loadString("data", data);

    /* OpenGL properties.. */
    conf_loadInt("width", gl_screen.w);
    conf_loadInt("height", gl_screen.h);
    conf_loadBool("fullscreen", i);
    if(i) { gl_screen.flags |= OPENGL_FULLSCREEN; i = 0; }
    conf_loadBool("aa", i);
    if(i) {
      gl_screen.flags |= OPENGL_AA_POINT | OPENGL_AA_LINE || OPENGL_AA_POLYGON;
      i = 0;
    }
    conf_loadBool("aa_point", i);
    if(i) { gl_screen.flags |= OPENGL_AA_POINT; i = 0; }
    conf_loadBool("aa_line", i)
        if(i) { gl_screen.flags |= OPENGL_AA_LINE; i = 0; }
    conf_loadBool("aa_polygon", i);
    if(i) { gl_screen.flags |= OPENGL_AA_POLYGON; i = 0; }

    /* FPS. */
    conf_loadBool("showfps", show_fps);
    conf_loadInt("maxfps", max_fps);

    /* Input. */
    conf_loadInt("afterburn", input_afterburnSensibility);

    /* Sound. */
    conf_loadBool("nosound", i);
    nosound = i; i = 0;
    conf_loadFloat("sound", d);
    if(d) { sound_volume(d); d = 0.; }
    conf_loadFloat("music", d);
    if(d) { music_volume(d); d = 0.; }

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

    /* If there are any keybindings. Grab them. */
    char* str;
    int type, key, reverse;
    for(i = 0; strcmp(keybindNames[i], "end"); i++) {
      lua_getglobal(L, keybindNames[i]);
      str = NULL;
      key = -1;
      reverse = 0;
      if(lua_istable(L, -1)) {
        /* It's a gawd damn table!! */
        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 used for axis. */
        lua_pushstring(L, "reverse");
        lua_gettable(L, -4);
        if(lua_isnumber(L, -1))
          reverse = 1;

        if(key != -1 && str != NULL) {
          /* Then the 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", file);

        /* Clean up after table crap. */
        lua_remove(L,-1);
        lua_remove(L,-1);
        lua_remove(L,-1);
        lua_remove(L,-1);
      }
    }
  } else {
    /* Failed to load the config file.. */
    DEBUG("Config file '%s' not found.", file);
    lua_close(L);
    return 1;
  }
  lua_close(L);
  return 0;
}

/* Parse some CLI options. */
void conf_parseCLI(int argc, char** argv) {
  static struct option long_options[] = {
    { "fullscreen",     no_argument,        0,  'f' },
    { "fps",            required_argument,  0,  'F' },
    { "data",           required_argument,  0,  'd' },
    { "joystick",       required_argument,  0,  'j' },
    { "Joystick",       required_argument,  0,  'J' },
    { "width",          required_argument,  0,  'W' },
    { "width",          required_argument,  0,  'H' },
    { "sound",          no_argument,        0,  'S' },
    { "mvol",           required_argument,  0,  'm' },
    { "svol",           required_argument,  0,  's' },
    { "help",           no_argument,        0,  'h' },
    { "version",        no_argument,        0,  'v' },
    { NULL, 0, 0, 0 }
  };
  int option_index = 0;
  int c = 0;

  while((c = getopt_long(argc, argv,
          "fF:d:j:J:W:H:MSm:s:Ghv",
          long_options, &option_index)) != -1) {
    switch(c) {
    case 'f':
      gl_screen.flags |= OPENGL_FULLSCREEN;
      break;
    case 'F':
      max_fps = atoi(optarg);
      break;
    case 'd':
      data = strdup(optarg);
      break;
    case 'j':
      indjoystick = atoi(optarg);
      break;
    case 'J':
      namjoystick = strdup(optarg);
      break;
    case 'W':
      gl_screen.w = atoi(optarg);
      break;
    case 'H':
      gl_screen.h = atoi(optarg);
      break;
    case 'M':
      nosound = 1;
      break;
    case 'S':
      nosound = 0;
      break;
    case 'm':
      music_volume(atof(optarg));
      break;
    case 's':
      sound_volume(atof(optarg));
      break;
    case 'G':
      nebu_forceGenerate();
      break;
    case 'v':
      /* By now it has already displayed the version. */
      /*LOG(APPNAME": version %d.%d.%d", VMAJOR, VMINOR, VREV); */
      exit(EXIT_SUCCESS);
    case 'h':
      print_usage(argv);
      exit(EXIT_SUCCESS);
    }
  }
}

/* Saves the current configuration. */
int conf_saveConfig(void) {
  /* TODO: */
  return 0;
}