#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;
}

/* Return all the ships in text form. */
Ship** ship_getTech(int* n, const int* tech, const int techmax) {
  int i, j, k, num, price;
  Ship** result;
  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;
  result = malloc(sizeof(Ship*) * 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(result[k]->name, ships[j]->name)==0)
            break;

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

  /* Cleanup */
  free(ships);

  return result;
}

/* Get the ship's classname. */

char* ship_class(Ship* s) {
  switch(s->class) {
    case SHIP_CLASS_NULL:
      return "NULL";
    /* Civilian */
    case SHIP_CLASS_CIV_LIGHT:
      return "Civilian Light";
    case SHIP_CLASS_CIV_MEDIUM:
      return "Civilian Medium";
    case SHIP_CLASS_CIV_HEAVY:
      return "Civilian Heavy";
    /* Military. */
    case SHIP_CLASS_MIL_LIGHT:
      return "Military Light";
    case SHIP_CLASS_MIL_MEDIUM:
      return "Military Medium";
    case SHIP_CLASS_MIL_HEAVY:
      return "Military Heavy";
    /* Robotic. */
    case SHIP_CLASS_ROB_LIGHT:
      return "Robotic Light";
    case SHIP_CLASS_ROB_MEDIUM:
      return "Robotic Medium";
    case SHIP_CLASS_ROB_HEAVY:
      return "Robotic Heavy";
    /* Hybrid. */
    case SHIP_CLASS_HYB_LIGHT:
      return "Hybrid Light";
    case SHIP_CLASS_HYB_MEDIUM:
      return "Hybrid Medium";
    case SHIP_CLASS_HYB_HEAVY:
      return "Hybrid Heavy";
    default:
      return "Unknown";
  }
}

/**
 * @fn static ShipClass ship_classFromString(char* str)
 *
 * @brief Get the machine ship class identifier from a human readable string.
 *    @param str String to extract ship class identifier from.
 */
static ShipClass ship_classFromString(char* str) {
  /* Civilian. */
  if(strcmp(str, "civ light")==0)
    return SHIP_CLASS_CIV_LIGHT;
  if(strcmp(str, "civ medium")==0)
    return SHIP_CLASS_CIV_MEDIUM;
  if(strcmp(str, "civ heavy")==0)
    return SHIP_CLASS_CIV_HEAVY;
  /* Military. */
  if(strcmp(str, "mil light")==0)
    return SHIP_CLASS_MIL_LIGHT;
  if(strcmp(str, "mil medium")==0)
    return SHIP_CLASS_MIL_MEDIUM;
  if(strcmp(str, "mil heavy")==0)
    return SHIP_CLASS_MIL_HEAVY;
  /* Robotic. */
  if(strcmp(str, "rob light")==0)
    return SHIP_CLASS_ROB_LIGHT;
  if(strcmp(str, "rob medium")==0)
    return SHIP_CLASS_ROB_MEDIUM;
  if(strcmp(str, "rob heavy")==0)
    return SHIP_CLASS_ROB_HEAVY;
  /* Hybrid. */
  if(strcmp(str, "hyb light")==0)
    return SHIP_CLASS_HYB_LIGHT;
  if(strcmp(str, "hyb medium")==0)
    return SHIP_CLASS_HYB_MEDIUM;
  if(strcmp(str, "hyb heavy")==0)
    return SHIP_CLASS_HYB_HEAVY;

  /* Unknown. */
  return SHIP_CLASS_NULL;
}

/**
 * @fn int ship_basePrice(Ship* s)
 *
 * @brief Get the ships base price (no outfits).
 */
int ship_basePrice(Ship* s) {
  int price;
  ShipOutfit* o;

  /* Base price is ships price minus it's outfits. */
  price = s->price;
  for(o = s->outfit; o != NULL; o = o->next)
    price -= o->quantity * o->data->price;

  return price;
}

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")) {

      /* Load the base graphic. */
      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);

      xmlr_attr(node, "target", stmp);
      if(stmp != NULL) {
        snprintf(str, strlen(stmp) +
            sizeof(SHIP_GFX)+sizeof(SHIP_TARGET)+sizeof(SHIP_EXT),
            SHIP_GFX"%s"SHIP_TARGET SHIP_EXT, stmp);
        tmp->gfx_target = gl_newImage(str);
        free(stmp);
      } else { /* Load standard target graphic. */
        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));
      continue;
    }
    if(xml_isNode(node, "class")) {
      tmp->class = ship_classFromString(xml_get(node));
    }
    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));
      continue;
    }
    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));
      continue;
    }
    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));
      continue;
    }
    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));
      continue;
    }
  } 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==SHIP_CLASS_NULL, "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"                     /* Name. */
           "%s\n"                     /* Class. */
           "%d\n"                     /* Crew. */
           "%d Tons\n"                /* Mass. */
           "\n"
           "%.2f MN/ton\n"            /* Thrust. */
           "%.2f M/s\n"               /* Speed. */
           "%.2f Grad/s\n"            /* Turn. */
           "\n"
           "%.2f MJ (%.2f MJ/s)\n)"   /* Shield. */
           "%.2f MJ (%.2f MJ/s)\n)"   /* Armour. */
           "%.2f MJ (%.2f MJ/s)\n)"   /* Energy. */
           "\n"
           "%d Tons\n"                /* Weapon. */
           "%d Tons\n"                /* Cargo. */
           "%d Units\n",              /* Fuel. */
           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" */
}

