#include <string.h>
#include <limits.h>

#include "lephisto.h"
#include "log.h"
#include "pack.h"
#include "xml.h"
#include "toolkit.h"
#include "ship.h"

#define XML_ID    "Ships" /* XML section identifier. */
#define XML_SHIP  "ship"

#define SHIP_DATA   "../dat/ship.xml"
#define SHIP_GFX    "../gfx/ship/"
#define SHIP_EXT    ".png"
#define SHIP_TARGET "_target"

#define VIEW_WIDTH 300
#define VIEW_HEIGHT 300

#define BUTTON_WIDTH 80
#define BUTTON_HEIGHT 30

static Ship* ship_stack = NULL;
static int ship_nstack = 0;

static Ship* ship_parse(xmlNodePtr parent);
static void ship_view_close(char* btn);

/* Get a ship based on it's name. */
Ship* ship_get(const char* name) {
  Ship* tmp = ship_stack;
  int i;

  for(i = 0; i < ship_nstack; i++)
    if(strcmp((tmp+i)->name, name)==0) break;

  if(i == ship_nstack) /* Ship doesn't exist, game will probably crash now. */
    WARN("Ship %s does not exist", name);

  return tmp+i;
}

/* Get the ship's classname. */
static char* ship_classes[] = {
  "NULL",
  "Civialian Light", "Civilian Medium", "Civilian Heavy"
  "Military Light", "Military Medium", "Military Heavy"
  "Robotic Light", "Robotic Medium", "Robotic Heavy"
  "Hybrid Light", "Hybrid Medium", "Hybrid Heavy"

};

/* Return all the ships in text form. */
char** ship_getTech(int* n, const int* tech, const int techmax) {
  int i, j, k, num, price;
  char** shipnames;
  Ship** ships;
  
  /* Get availabble ships for tech. */
  ships = malloc(sizeof(Ship*) * ship_nstack);
  num = 0;
  for(i = 0; i < ship_nstack; i++) {
    if(ship_stack[i].tech <= tech[0]) { /* Check vs base tech. */
      ships[num] = &ship_stack[i];
      num++;
    } else {
      for(j = 0; j < techmax; j++)
        if(tech[j] == ship_stack[i].tech) { /* Check vs special tech. */
          ships[num] = &ship_stack[i];
          num++;
        }
    }
  }

  /* Now sort by price. */
  *n = 0;
  price = -1;
  shipnames = malloc(sizeof(char*) * num);
  /* Until we fill the new stack. */
  for(i = 0; i < num; i++) {
    /* Check for cheapest. */
    for(j = 0; j < num; j++) {
      /* Is cheapest? */
      if((price == -1) || (ships[price]->price > ships[j]->price)) {
        /* Check if already in stack. */
        for(k = 0; k < (*n); k++)
          if(strcmp(shipnames[k], ships[j]->name)==0)
            break;

        /* Not in stack and therefore is cheapest. */
        if(k == (*n))
          price = j;
      }
    }
    /* Add the current cheapest to stack. */
    shipnames[i] = strdup(ships[price]->name);
    (*n)++;
    price = -1;
  }

  /* Cleanup */
  free(ships);

  return shipnames;
}

char* ship_class(Ship* s) {
  return ship_classes[s->class];
}

static Ship* ship_parse(xmlNodePtr parent) {
  xmlNodePtr cur, node;
  Ship* tmp = CALLOC_L(Ship);
  ShipOutfit* otmp, *ocur;

  char str[PATH_MAX] = "\0";
  char* stmp;

  xmlr_attr(parent, "name", tmp->name);
  if(tmp->name == NULL) WARN("Ship in "SHIP_DATA" has invalid or no name");

  node = parent->xmlChildrenNode;

  do {
    /* Load all the data. */
    if(xml_isNode(node,"GFX")) {
      snprintf(str, strlen(xml_get(node)) +
               sizeof(SHIP_GFX) + sizeof(SHIP_EXT),
               SHIP_GFX"%s"SHIP_EXT, xml_get(node));
      tmp->gfx_space = gl_newSprite(str, 6, 6);
      /* Target. */
      snprintf(str, strlen(xml_get(node)) +
               sizeof(SHIP_GFX)+sizeof(SHIP_TARGET)+sizeof(SHIP_EXT),
               SHIP_GFX"%s"SHIP_TARGET SHIP_EXT, xml_get(node));
      tmp->gfx_target = gl_newImage(str);

    }
    xmlr_strd(node, "GUI", tmp->gui);
    if(xml_isNode(node, "sound"))
      tmp->sound = sound_get(xml_get(node));
    xmlr_int(node, "class", tmp->class);
    xmlr_int(node, "price", tmp->price);
    xmlr_int(node, "tech", tmp->tech);
    xmlr_strd(node, "fabricator", tmp->fabricator);
    xmlr_strd(node, "description", tmp->description);
    if(xml_isNode(node, "movement")) {
      cur = node->children;
      do {
        xmlr_int(cur, "thrust", tmp->thrust);
        xmlr_int(cur, "turn", tmp->turn);
        xmlr_int(cur, "speed", tmp->speed);
      } while(xml_nextNode(cur));
    }
    else if(xml_isNode(node, "health")) {
      cur = node->children;
      do {
        xmlr_float(cur, "armour", tmp->armour);
        xmlr_float(cur, "shield", tmp->shield);
        xmlr_float(cur, "energy", tmp->energy);
        if(xml_isNode(cur, "armour_regen"))
          tmp->armour_regen = (double)(xml_getInt(cur))/60.0;
        else if(xml_isNode(cur, "shield_regen"))
          tmp->shield_regen = (double)(xml_getInt(cur))/60.0;
        else if(xml_isNode(cur, "energy_regen"))
          tmp->energy_regen = (double)(xml_getInt(cur))/60.0;
      } while(xml_nextNode(cur));
    }
    else if(xml_isNode(node, "characteristics")) {
      cur = node->children;
      do {
        xmlr_int(cur, "crew", tmp->crew);
        xmlr_float(cur, "mass", tmp->mass);
        xmlr_int(cur, "fuel", tmp->fuel);
        xmlr_int(cur, "cap_weapon", tmp->cap_weapon);
        xmlr_int(cur, "cap_cargo", tmp->cap_cargo);
      } while(xml_nextNode(cur));
    }
    else if(xml_isNode(node, "outfits")) {
      cur = node->children;
      do {
        if(xml_isNode(cur, "outfit")) {
          otmp = MALLOC_L(ShipOutfit);
          otmp->data = outfit_get(xml_get(cur));
          stmp = xml_nodeProp(cur, "quantity");
          if(!stmp)
            WARN("Ship '%s' is missing tag 'quantity for outfit '%s'",
                 tmp->name, otmp->data->name);
          otmp->quantity = atoi(stmp);
          free(stmp);
          otmp->next = NULL;

          if((ocur = tmp->outfit) == NULL) tmp->outfit = otmp;
          else {
            while(ocur->next) ocur = ocur->next;
            ocur->next = otmp;
          }
        }
      } while(xml_nextNode(cur));
    }
  } while(xml_nextNode(node));

  tmp->thrust *= tmp->mass; /* Helps keep number sane. */

#define MELEMENT(o,s) if(o) WARN("Ship '%s' missing '"s"' element", tmp->name)
  MELEMENT(tmp->name == NULL, "name");
  MELEMENT(tmp->gfx_space == NULL,  "GFX");
  MELEMENT(tmp->gui == NULL,        "GUI");
  MELEMENT(tmp->class==0,           "class");
  MELEMENT(tmp->price==0,           "price");
  MELEMENT(tmp->tech==0,            "tech");
  MELEMENT(tmp->fabricator==0,      "fabricator");
  MELEMENT(tmp->description==0,     "description");
  MELEMENT(tmp->thrust==0,          "thrust");
  MELEMENT(tmp->turn==0,            "turn");
  MELEMENT(tmp->speed==0,           "speed");
  MELEMENT(tmp->armour==0,          "armour");
  MELEMENT(tmp->armour_regen==0,    "armour_regen");
  MELEMENT(tmp->shield==0,          "shield");
  MELEMENT(tmp->shield_regen==0,    "shield_regen");
  MELEMENT(tmp->energy==0,          "energy");
  MELEMENT(tmp->energy_regen==0,    "energy_regen");
  MELEMENT(tmp->fuel==0,            "fuel");
  MELEMENT(tmp->crew==0,            "crew");
  MELEMENT(tmp->mass==0,            "mass");
  MELEMENT(tmp->cap_cargo==0,       "cap_cargo");
  MELEMENT(tmp->cap_weapon==0,      "cap_weapon");
#undef MELEMENT

  return tmp;
}

int ships_load(void) {
  uint32_t bufsize;
  char* buf = pack_readfile(DATA, SHIP_DATA, &bufsize);

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

  Ship* tmp = NULL;

  node = doc->xmlChildrenNode; /* Ships node. */
  if(strcmp((char*)node->name, XML_ID)) {
    ERR("Malformed "SHIP_DATA" file: missing root element '"XML_ID"'");
    return -1;
  }

  node = node->xmlChildrenNode; /* First ship node. */
  if(node == NULL) {
    ERR("Malformed "SHIP_DATA" file: Does not contain elements");
    return -1;
  }

  do {
    if(node->type == XML_NODE_START && strcmp((char*)node->name, XML_SHIP)==0) {
      tmp = ship_parse(node);
      ship_stack = realloc(ship_stack, sizeof(Ship)*(++ship_nstack));
      memcpy(ship_stack+ship_nstack-1, tmp, sizeof(Ship));
      free(tmp);
    }
  } while((node = node->next));

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

  DEBUG("Loaded %d ship%s", ship_nstack, (ship_nstack==1) ? "" : "s");

  return 0;
}

void ships_free(void) {
  ShipOutfit* so, *sot;
  int i;
  for(i = 0; i < ship_nstack; i++) {
    /* Free stored strings. */
    if((ship_stack+i)->name) free(ship_stack[i].name);
    if((ship_stack+i)->description) free(ship_stack[i].description);
    if((ship_stack+i)->gui) free(ship_stack[i].gui);
    if((ship_stack+i)->fabricator) free(ship_stack[i].fabricator);

    so = (ship_stack+i)->outfit;
    while(so) { /* free the ship outfit. */
      sot = so;
      so = so->next;
      free(sot);
    }
    gl_freeTexture((ship_stack+i)->gfx_space);
    gl_freeTexture((ship_stack+i)->gfx_target);
  }
  free(ship_stack);
  ship_stack = NULL;
}

/* Used to visualize the ships status. */
void ship_view(char* shipname) {
  Ship* s;
  char buf[1024];
  unsigned int wid;
  int h;
  s = ship_get(shipname);

  snprintf(buf, 1024,
                 "Name:\n"
                 "Class:\n"
                 "Crew:\n"
                 "Mass:\n"
                 "\n"
                 "Thrust:\n"
                 "Max Speed:\n"
                 "Turn:\n"
                 "\n"
                 "Shield:\n"
                 "Armour:\n"
                 "Energy:\n"
                 "\n"
                 "Weapon Space:\n"
                 "Cargo Space:\n"
                 "Fuel:\n");
  h = gl_printHeight(&gl_smallFont, VIEW_WIDTH, buf);

  wid = window_create(shipname, -1, -1, VIEW_WIDTH, h+60+BUTTON_HEIGHT);
  window_addText(wid, 20, -40, VIEW_WIDTH, h,
      0, "txtLabel", &gl_smallFont, &cDConsole, buf);

  snprintf(buf, 1024,
           "%s\n"
           "%s\n"
           "%d\n"
           "%d Tons\n"
           "\n"
           "%.2f MN\n"
           "%.2f M/s\n"
           "%.2f Grad/s\n"
           "\n"
           "%.2f MJ (%.2f MJ/s)\n)"
           "%.2f MJ (%.2f MJ/s)\n)"
           "%.2f MJ (%.2f MJ/s)\n)"
           "\n"
           "%d Tons\n"
           "%d Tons\n"
           "%d Units\n",
           s->name, ship_class(s), s->crew, s->mass,
           s->thrust/s->mass, s->speed, s->turn,
           s->shield, s->shield_regen, s->armour, s->armour_regen,
           s->energy, s->energy_regen,
           s->cap_weapon, s->cap_cargo, s->fuel);

  window_addText(wid, 120, -40, VIEW_WIDTH-140, h,
                 0, "txtProperties", &gl_smallFont, &cBlack, buf);

  /* Close the button. */
  snprintf(buf, 37, "close%s", shipname);
  window_addButton(wid, -20, 20,
                   BUTTON_WIDTH, BUTTON_HEIGHT,
                   buf, "Close", ship_view_close);
}

static void ship_view_close(char* btn) {
  window_destroy(window_get(btn+5)); /* "closefoo -> Foo" */
}