#include <stdint.h>
#include <string.h>
#include <malloc.h>

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

#include "lephisto.h"
#include "log.h"
#include "hook.h"
#include "pack.h"
#include "xml.h"
#include "mission.h"

#define XML_MISSION_ID    "Missions"  // XML section identifier.
#define XML_MISSION_TAG   "mission"

#define MISSION_DATA      "../dat/mission.xml"
#define MISSION_LUA_PATH  "../dat/missions/"

// Current player missions.
static unsigned int mission_id = 0;
Mission player_missions[MISSION_MAX];

// Mission stack.
static Mission* mission_stack = NULL; // Unmuteable after creation.
static int mission_nstack = 0;

// Extern.
extern int misn_run(Mission* misn, char* func);

// Static.
static void mission_cleanup(Mission* misn);
static void mission_free(MissionData* mission);
static int mission_location(char* loc);
static MissionData* mission_parse(const xmlNodePtr parent);

// Create a mission.
int mission_create(MissionData* misn) {
  int i;

  // Find last mission.
  for(i = 0; i < MISSION_MAX; i++)
    if(player_missions[i].data == NULL) break;

  // No missions left.
  if(i >= MISSION_MAX) return -1;

  player_missions[i].id = ++mission_id;
  player_missions[i].data = misn;

  // Init lua.
  player_missions[i].L = luaL_newstate();
  luaopen_string(player_missions[i].L); // String.format can be useful..
  misn_loadLibs(player_missions[i].L);  // Load our custom libraries.
  
  misn_run(&player_missions[i], "create");

  return 0;
}

// Clean up a mission.
static void mission_cleanup(Mission* misn) {
  hook_rmParent(misn->id); // Remove existing hooks.
  misn->data = NULL;
  if(misn->title) free(misn->title);
  if(misn->desc)  free(misn->desc);
  if(misn->reward) free(misn->reward);
  lua_close(misn->L);
}

// Free a mission.
static void mission_free(MissionData* mission) {
  if(mission->name) {
    free(mission->name);
    mission->name = NULL;
  }
  if(mission->avail.planet) {
    free(mission->avail.planet);
    mission->avail.planet = NULL;
  }
  if(mission->avail.system) {
    free(mission->avail.system);
    mission->avail.system = NULL;
  }
  if(mission->avail.factions) {
    free(mission->avail.factions);
    mission->avail.factions = NULL;
    mission->avail.nfactions = 0;
  }
}

// Return location based on string.
static int mission_location(char* loc) {
  if(strcmp(loc, "None")==0)            return MIS_AVAIL_NONE;
  else if(strcmp(loc, "Computer")==0)   return MIS_AVAIL_COMPUTER;
  else if(strcmp(loc, "Bar")==0)        return MIS_AVAIL_BAR;
  else if(strcmp(loc, "Outfit")==0)     return MIS_AVAIL_OUTFIT;
  else if(strcmp(loc, "Shipyard")==0)   return MIS_AVAIL_SHIPYARD;
  else if(strcmp(loc, "Land")==0)       return MIS_AVAIL_LAND;

  return -1;
}

// Parse a node of a mission.
static MissionData* mission_parse(const xmlNodePtr parent) {
  MissionData* tmp;
  xmlNodePtr cur, node;

  tmp = malloc(sizeof(MissionData));
  memset(tmp, 0, sizeof(MissionData));

  // Get the name.
  tmp->name = xml_nodeProp(parent, "name");
  if(tmp->name == NULL) WARN("Mission in "MISSION_DATA" has invalid or no name");

  node = parent->xmlChildrenNode;

  char str[PATH_MAX] = "\0";

  // Load all the data.
  do {
    if(xml_isNode(node, "lua")) {
      snprintf(str, PATH_MAX, MISSION_LUA_PATH"%s.lua", xml_get(node));
      tmp->lua = strdup(str);
      str[0] = '\0';
    }
    else if(xml_isNode(node, "avail")) {
      cur = node->children;
      do {
        if(xml_isNode(cur, "location"))
          tmp->avail.loc = mission_location(xml_get(cur));
        /*else if(xml_isNode(cur, "")) // Need to parse other thingies.
          tmp->u.amm.damage_shield = xml_getFloat(cur);*/
      } while((cur = cur->next));
    }
  } while((node = node->next));
#define MELEMENT(o,s) \
  if(o) WARN("Mission '%s' missing/invalid '"s"' element", tmp->name)
  MELEMENT(tmp->lua==NULL, "lua");
  MELEMENT(tmp->avail.loc==-1, "location");
#undef MELEMENT

  return tmp;
}

// Load/Free.
int missions_load(void) {
  uint32_t bufsize;
  char* buf = pack_readfile(DATA, MISSION_DATA, &bufsize);

  MissionData* tmp;

  xmlNodePtr node;
  xmlDocPtr doc = xmlParseMemory(buf, bufsize);

  node = doc->xmlChildrenNode;
  if(!xml_isNode(node, XML_MISSION_ID)) {
    ERR("Malformed '"MISSION_DATA"' file: missing root element '" \
          XML_MISSION_ID"'");
    return -1;
  }

  node = node->xmlChildrenNode; // First mission node.
  if(node == NULL) {
    ERR("Malformed '"MISSION_DATA"' file: does not contain elements");
    return -1;
  }

  do {
    if(xml_isNode(node, XML_MISSION_TAG)) {
      tmp = mission_parse(node);
      mission_stack = 
          realloc(mission_stack, sizeof(MissionData)*(++mission_nstack));
      memcpy(mission_stack+mission_nstack-1, tmp, sizeof(MissionData));
      free(tmp);
    }
  } while((node = node->next));

  xmlFreeDoc(doc);
  free(buf);
  xmlCleanupParser();

  DEBUG("Loaded %d Mission%s", mission_nstack, (mission_nstack==1) ? "" : "s");

  return 0;
}

void missions_free(void) {
  int i;

  // Free the mission data.
  for(i = 0; i < mission_nstack; i++)
    mission_free(&mission_stack[i]);
  free(mission_stack);
  mission_stack = NULL;
  mission_nstack = 0;
}

void missions_cleanup(void) {
  int i;

  for(i = 0; i < MISSION_MAX; i++)
    mission_cleanup(&player_missions[i]);
}