#include "lauxlib.h"

#include "log.h"
#include "lephisto.h"
#include "rng.h"
#include "ltime.h"
#include "toolkit.h"
#include "space.h"
#include "land.h"
#include "map.h"
#include "pack.h"
#include "lluadef.h"
#include "llua.h"

static int llua_packfileLoader(lua_State* L);

/* -- Libraries. -- */

/* Lephisto. */
static int lephisto_lang(lua_State* L);
static const luaL_reg lephisto_methods[] = {
  { "lang", lephisto_lang },
  { 0, 0 }
};

/* Space. */
static int space_getPlanet(lua_State* L);
static int space_getSystem(lua_State* L);
static int space_landName(lua_State* L);
static int space_systemName(lua_State* L);
static int space_jumpDist(lua_State* L);
static const luaL_reg space_methods[] = {
  { "getPlanet",    space_getPlanet   },
  { "getSystem",    space_getSystem   },
  { "landName",     space_landName    },
  { "system",       space_systemName  },
  { "jumpDist",     space_jumpDist    },
  { 0, 0 }
};

/* Time. */
static int time_get(lua_State* L);
static int time_str(lua_State* L);
static int time_units(lua_State* L);
static const luaL_reg time_methods[] = {
  { "get",    time_get    },
  { "str",    time_str    },
  { "units",  time_units  },
  {0, 0}
};

/* RND. */
static int rnd_int(lua_State*L);
static const luaL_reg rnd_methods[] = {
  { "int",    rnd_int },
  { 0, 0 }
};

/* Toolkit. */
static int tk_msg(lua_State* L);
static int tk_yesno(lua_State* L);
static int tk_input(lua_State* L);
static const luaL_reg tk_methods[] = {
  { "msg",    tk_msg    },
  { "yesno",  tk_yesno  },
  { "input",  tk_input  },
  { 0, 0 }
};

/* Wrapper around luaL_newstate. */
lua_State* llua_newState(void) {
  lua_State* L;
  /* Try to create the new state. */
  L = luaL_newstate();
  if(L == NULL) {
    WARN("Failed to create new lua state.");
    return NULL;
  }

  return L;
}

/* Open a lua library. */
int llua_load(lua_State* L, lua_CFunction f) {
  lua_pushcfunction(L, f);
  if(lua_pcall(L, 0, 0, 0))
    WARN("llua include error: %s", lua_tostring(L, 1));

  return 0;
}

/* Load specially modified basic stuff. */
int llua_loadBasic(lua_State* L) {
  int i;
  /* Unsafe functions. */
  char* override[] = {
    "collectgarbage",
    "dofile",
    "getfenv",
    "getmetatable",
    "load",
    "loadfile",
    "loadstring",
    "rawequal",
    "rawget",
    "rawset",
    "setfenv",
    "setmetatable",
    "END"
  };

  llua_load(L, luaopen_base); /* Open base. */

  /* Replace non-safe functions. */
  for(i = 0; strcmp(override[i], "END") != 0; i++) {
    lua_pushnil(L);
    lua_setglobal(L, override[i]);
  }

  /* Add our own. */
  lua_register(L, "include", llua_packfileLoader);

  return 0;
}

static int llua_packfileLoader(lua_State* L) {
  char* buf, *filename;
  uint32_t bufsize;

  LLUA_MIN_ARGS(1);

  if(!lua_isstring(L, 1)) {
    LLUA_INVALID_PARAMETER();
    return 0;
  }

  filename = (char*) lua_tostring(L, 1);

  /* Try to locate the data. */
  buf = pack_readfile(DATA, filename, &bufsize);
  if(buf == NULL) {
    lua_pushfstring(L, "%s not found in packfile %s", filename, DATA);
    return 1;
  }

  /* Run the buffer. */
  if(luaL_dobuffer(L, buf, bufsize, filename) != 0) {
    /* Will push the current error from the dobuffer. */
    return 1;
  }

  /* Cleanup. */
  free(buf);
  return 0;
}

/* Individual libraries. */
int lua_loadLephisto(lua_State* L) {
  luaL_register(L, "lephisto", lephisto_methods);
  return 0;
}

int lua_loadSpace(lua_State* L, int readonly) {
  (void)readonly;
  luaL_register(L, "space", space_methods);
  return 0;
}

int lua_loadTime(lua_State* L, int readonly) {
  (void)readonly;
  luaL_register(L, "time", time_methods);
  return 0;
}

int lua_loadRnd(lua_State* L) {
  luaL_register(L, "rnd", rnd_methods);
  return 0;
}

int lua_loadTk(lua_State* L) {
  luaL_register(L, "tk", tk_methods);
  return 0;
}

/* -- Lephisto. -- */
static int lephisto_lang(lua_State* L) {
  /* TODO: multilanguage stuff. */
  lua_pushstring(L, "en");
  return 1;
}

/* -- Space. -- */
static int space_getPlanet(lua_State* L) {
  int i;
  int *factions;
  int nfactions;
  char** planets;
  int nplanets;
  char* rndplanet;

  if(lua_gettop(L) == 0) {
    /* Get random planet. */
    lua_pushstring(L, space_getRndPlanet());
    return 1;
  }
  else if(lua_isnumber(L, 1)) {
    i = lua_tonumber(L, 1);
    planets = space_getFactionPlanet(&nplanets, &i, 1);
  }
  else if(lua_isstring(L, 1)) {
    i = faction_get((char*) lua_tostring(L, 1));
    planets = space_getFactionPlanet(&nplanets, &i, 1);
  }
  else if(lua_istable(L, 1)) {
    /* Load up the table. */
    lua_pushnil(L);
    nfactions = (int) lua_gettop(L);
    factions = malloc(sizeof(int) * nfactions);
    i = 0;
    while(lua_next(L, -2) != 0) {
      factions[i++] = (int) lua_tonumber(L, -1);
      lua_pop(L, 1);
    }
    /* Get the planets. */
    planets = space_getFactionPlanet(&nplanets, factions, nfactions);
    free(factions);
  }
  else return 0; /* Nothing useful. */

  /* Choose random planet. */
  if(nplanets == 0) {
    /* No suitable planet. */
    free(planets);
    return 0;
  }

  rndplanet = planets[RNG(0, nplanets-1)];
  free(planets);

  lua_pushstring(L, rndplanet);
  return 1;
}

static int space_getSystem(lua_State* L) {
  LLUA_MIN_ARGS(1);
  char* planetname, *sysname;

  if(lua_isstring(L, 1)) planetname = (char*) lua_tostring(L, 1);
  else return 0;

  sysname = planet_getSystem(planetname);
  lua_pushstring(L, sysname);
  return 1;
}

static int space_landName(lua_State* L) {
  if(landed) {
    lua_pushstring(L, land_planet->name);
    return 1;
  }
  return 0;
}

static int space_systemName(lua_State* L) {
  lua_pushstring(L, cur_system->name);
  return 1;
}

static int space_jumpDist(lua_State* L) {
  LLUA_MIN_ARGS(1);
  StarSystem** s;
  int jumps;
  char* start, *goal;

  if(lua_isstring(L, 1))
    start = (char*)lua_tostring(L, 1);
  else LLUA_INVALID_PARAMETER();

  if((lua_gettop(L) > 1) && lua_isstring(L, 2))
    goal = (char*) lua_tostring(L, 2);
  else
    goal = cur_system->name;

  s = map_getJumpPath(&jumps, start, goal, 1);
  free(s);

  lua_pushnumber(L, jumps);
  return 1;
}

/* -- Time. -- */
static int time_get(lua_State* L) {
  lua_pushnumber(L, ltime_get());
  return 1;
}

static int time_str(lua_State* L) {
  char* lt;
  if((lua_gettop(L) > 0) && (lua_isnumber(L, 1)))
    lt = ltime_pretty((unsigned int) lua_tonumber(L, 1));
  else
    lt = ltime_pretty(ltime_get());
  lua_pushstring(L, lt);
  free(lt);
  return 1;
}

static int time_units(lua_State* L) {
  if((lua_gettop(L) > 0) && (lua_isnumber(L, 1)))
    lua_pushnumber(L, (unsigned int) lua_tonumber(L, 1) * LTIME_UNIT_LENGTH);
  else
    lua_pushnumber(L, LTIME_UNIT_LENGTH);
  return 1;
}

/* -- RND. -- */
static int rnd_int(lua_State* L) {
  int o;

  o = lua_gettop(L);

  if(o == 0) lua_pushnumber(L, RNGF()); /* Random double o <= x <= 1. */
  else if(o == 1) {
    /* Random int 0 <= x <= param. */
    if(lua_isnumber(L, 1))
      lua_pushnumber(L, RNG(0, (int)lua_tonumber(L, 1)));
    else return 0;
  }
  else if(o >= 2) {
    /* Random int param 1 <= x <= param 2. */
    if(lua_isnumber(L, 1) && lua_isnumber(L, 2))
      lua_pushnumber(L, RNG((int)lua_tonumber(L, 1), (int)lua_tonumber(L, 2)));
    else return 0;
  }
  else LLUA_INVALID_PARAMETER();
  
  /* Unless it's returned 0 already it'll always return param. */
  return 1;
}

/* -- Toolkit. -- */

static int tk_msg(lua_State* L) {
  char* title, *str;
  LLUA_MIN_ARGS(2);

  if(lua_isstring(L, 1)) title = (char*) lua_tostring(L, 1);
  else LLUA_INVALID_PARAMETER();
  if(lua_isstring(L, 2)) str = (char*) lua_tostring(L, 2);
  else LLUA_INVALID_PARAMETER();

  dialogue_msg(title, str);
  return 0;
}

static int tk_yesno(lua_State* L) {
  int ret;
  char* title, *str;
  LLUA_MIN_ARGS(2);

  if(lua_isstring(L, 1)) title = (char*) lua_tostring(L, 1);
  else LLUA_INVALID_PARAMETER();
  if(lua_isstring(L, 2)) str = (char*) lua_tostring(L, 2);
  else LLUA_INVALID_PARAMETER();

  ret = dialogue_YesNo(title, str);
  lua_pushboolean(L, ret);
  return 1;
}

static int tk_input(lua_State* L) {
  char* title, *str;
  int min, max;
  LLUA_MIN_ARGS(4);

  if(lua_isstring(L, 1)) title = (char*) lua_tostring(L, 1);
  else LLUA_INVALID_PARAMETER();
  if(lua_isnumber(L, 2)) min = (int) lua_tonumber(L, 2);
  else LLUA_INVALID_PARAMETER();
  if(lua_isnumber(L, 3)) max = (int) lua_tonumber(L, 3);
  else LLUA_INVALID_PARAMETER();
  if(lua_isstring(L, 4)) str = (char*) lua_tostring(L, 4);
  else LLUA_INVALID_PARAMETER();

  dialogue_input(title, min, max, str);
  return 0;
}