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

#include "lephisto.h"
#include "log.h"
#include "pack.h"
#include "xml.h"
#include "faction.h"

#define XML_FACTION_ID    "Factions" // XML section id.
#define XML_FACTION_TAG   "faction"
#define XML_ALLIANCE_ID   "Alliances"
#define XML_ALLIANCE_TAG  "alliance"
#define XML_ENEMIES_ID    "Enemies"
#define XML_ENEMIES_TAG   "enemies"

#define FACTION_DATA "../dat/faction.xml"

#define ALLIANCE_OFFSET 27182 // Special offset for alliances.

typedef struct Faction_ {
  char* name;

  int*  enemies;
  int   nenemies;
  int*  allies;
  int   nallies;
} Faction;

static Faction* faction_stack = NULL;
static int nfactions = 0;

// Save alliance.
typedef struct Alliance_ {
  char* name;
  int* factions;
  int nfactions;
} Alliance;

// Stack of alliances.
static Alliance* alliances = NULL;
static int nalliances = 0;

static Faction* faction_parse(xmlNodePtr parent);
static void alliance_parse(xmlNodePtr parent);
static void enemies_parse(xmlNodePtr parent);
static Alliance* alliance_get(char* name);

// Return the faction of name "name".
int faction_get(const char* name) {
  int i;
  for(i = 0; i < nfactions; i++)
    if(strcmp(faction_stack[i].name, name)==0)
      break;

  if(i != nfactions)
    return i;

  DEBUG("Faction '%s' not found in stack.", name);
  return -1;
}

// Return the id of an alliance.
int faction_getAlliance(char* name) {
  int i;
  for(i = 0; i < nalliances; i++)
    if(strcmp(alliances[i].name, name)==0)
      break;

  if(i != nalliances)
    return ALLIANCE_OFFSET + i;
  DEBUG("Alliance '%s' not found in stack.", name);
  return -1;
}

char* faction_name(int f) {
  return faction_stack[f].name;
}

// Return the alliance of name 'name'.
static Alliance* alliance_get(char* name) {
  int i;
  for(i = 0; i < nalliances; i++)
    if(strcmp(alliances[i].name, name)==0)
      break;

  if(i != nalliances)
    return alliances+i;

  return NULL;
}

// Return 1 if Faction a and b are enemies.
int areEnemies(int a, int b) {
  Faction* fa, *fb;
  int i = 0;
  
  if(a == b) return 0; // Luckily our factions aren't masochistic.

  // Handle a.
  if(faction_isFaction(a)) fa = &faction_stack[a];
  else {
    // a isn't valid.
    DEBUG("areEnemies: %d is an invalid faction/alliance", a);
    return 0;
  }

  // Handle b.
  if(faction_isFaction(b)) fb = &faction_stack[b];
  else {
    // b isn't valid.
    DEBUG("areEnemies: %d is an invalid faction/alliance", b);
    return 0;
  }

  if(fa && fb) {
    // Both are factions.
    for(i = 0; i < fa->nenemies; i++)
      if(fa->enemies[i] == b)
        return 1;
    for(i = 0; i < fb->nenemies; i++)
      if(fb->enemies[i] == a)
        return 1;
  }
  return 0;
}

// Return 1 if Faction a and b are allies.
int areAllies(int a, int b) {
  Faction* fa, *fb;
  int i = 0;

  // Handle a.
  if(faction_isFaction(a)) fa = &faction_stack[a];
  else {
    // b isn't valid.
    DEBUG("areEnemies: %d is an invalid faction/alliance", a);
    return 0;
  }

  // Handle b.
  if(faction_isFaction(b)) fb = &faction_stack[b];
  else {
    // b isn't valid.
    DEBUG("areEnemies: %d is an invalid faction/alliance", b);
    return 0;
  }

  if(a == b) return 0;
  
  if(fa && fb) {
    // Both are factions.
    for(i = 0; i < fa->nallies; i++)
      if(fa->allies[i] == b)
        return 1;

    for(i = 0; i < fb->nallies; i++)
      if(fb->allies[i] == a)
        return 1;
  }
  return 0;
}

// Is faction f part of alliance a?
int faction_ofAlliance(int f, int a) {
  int i;
  Alliance* aa;

  if(!faction_isFaction(f)) {
    DEBUG("faction_ofAlliance: invalid alliance '%d'", f);
    return 0;  
  }
  
  if(!faction_isAlliance(a)) {
    DEBUG("faction_ofAlliance: invalid alliance '%d'", a);
    return 0;
  }

  aa = &alliances[a];

  for(i = 0; i < aa->nfactions; i++)
    if(aa->factions[i] == f)
      return 1;

  return 0;
}

// Return true if a s an alliance.
int faction_isAlliance(int a) {
  if((a < ALLIANCE_OFFSET) || (a >= ALLIANCE_OFFSET + nalliances))
    return 0;

  return 1;
}

// Return true if f is a faction.
int faction_isFaction(int f) {
  if((f < 0) || (f >= nfactions))
    return 0;
  return 1;
}

// Parses a single faction, but does not set the allies/enemies.
static Faction* faction_parse(xmlNodePtr parent) {
  Faction* tmp = CALLOC_L(Faction);
  tmp->name = (char*)xmlGetProp(parent, (xmlChar*)"name");
  if(tmp->name == NULL)
    WARN("Faction from "FACTION_DATA" has invalid or no name");
  return tmp;
}

// We set allies/enemies here, in the faction_stack.
static void alliance_parse(xmlNodePtr parent) {
  Alliance* a;
  int* i, j, n, m;
  Faction* ft;
  xmlNodePtr node, cur;

  node = parent->xmlChildrenNode;

  do {
    if((node->type == XML_NODE_START) && (strcmp((char*)node->name,
                                                 XML_ALLIANCE_TAG)==0)) {
      // Allocate a new alliance.
      alliances = realloc(alliances, sizeof(Alliance)*(++nalliances));
      alliances[nalliances-1].name = (char*)xmlGetProp(node,(xmlChar*)"name");
      alliances[nalliances-1].factions = NULL;
      alliances[nalliances-1].nfactions = 0;

      // Parse the current alliance's allies.
      cur = node->xmlChildrenNode;
      do {
        if(strcmp((char*)cur->name, "ally")==0) {
          // Add the faction (and pointers to make things simple).
          a = alliances + nalliances-1;
          i = &a->nfactions;
          (*i)++;

          // Load the faction.
          a->factions = realloc(a->factions, (*i)*sizeof(int));
          a->factions[(*i)-1] = faction_get((char*)cur->children->content);

          if(a->factions[(*i)-1] == -1)
            WARN("Faction '%s' in alliance '%s' does not exist in "FACTION_DATA,
                 (char*)cur->children->content, a->name);
        }
      } while((cur = cur->next));

      // Set the crap needed by faction_stack.
      for(j = 0; j < (*i); j++) {
        ft = &faction_stack[a->factions[j]];
        ft->nallies += (*i)-1;
        ft->allies = realloc(ft->allies, (ft->nallies)*sizeof(int));
        for(n = 0, m = 0; n < (*i); n++, m++) {
          // Add as ally for all factions exept self.
          if(n == j) m--;
          else if(n != j)
            ft->allies[ft->nallies-(*i)+1+m] = a->factions[n];
        }
      }
    }
  } while((node = node->next));
}

static void enemies_parse(xmlNodePtr parent) {
  xmlNodePtr node, cur;
  int** f;
  Faction* ft;
  Alliance* a;
  int i, *j, n, m, x, y, z, e;
  char* type;

  node = parent->xmlChildrenNode;

  do {
    if((node->type == XML_NODE_START)
       && (strcmp((char*)node->name, XML_ENEMIES_TAG)==0)) {
      i = 0;
      f = NULL;
      j = NULL;

      cur = node->xmlChildrenNode;
      do {
        if(strcmp((char*)cur->name,"enemy")==0) {
          type = (char*)xmlGetProp(cur, (xmlChar*)"type");

          i++;
          j = realloc(j, sizeof(int)*i);
          f = realloc(f, sizeof(int*)*i);

          if(strcmp(type, "alliance")==0) {
            // Enemy thing is an alliance.
            a = alliance_get((char*)cur->children->content);
            if(a == NULL)
              WARN("Alliance %s not found in stack",
                   (char*)cur->children->content);
            j[i-1] = a->nfactions;
            f[i-1] = a->factions;
          }
          else if(strcmp(type,"faction")==0) {
            // Enemy thing is only a faction.
            j[i-1] = 1;
            f[i-1] = malloc(sizeof(int));
            f[i-1][0] = faction_get((char*)cur->children->content);
            if(f[i-1][0] == -1)
              WARN("Faction %s not found in stack",
                   (char*)cur->children->content);
          }
          free(type);
        }
      } while((cur = cur->next));
      // Now actually parse and load up the enemies.
      for(n = 0; n < i; n++) {        // Unsinged int.
        for(m = 0; m < j[n]; m++) {   // Unsigned int.
          // Faction.
          // Add all the faction enemies to nenemies and alloc.
          for(e = 0, x = 0; x < i; x++)
            if(x != n) e += j[x]; // Store the total enemies.
          // Now allocate the memory.
          ft = &faction_stack[f[n][m]];
          ft->nenemies += e;
          ft->enemies = realloc(ft->enemies, sizeof(int)*ft->nenemies);

          // Add the actualy enemies.
          for(x = 0, z = 0; x < i; x++)
            if(x != n)
              // Make sure it's not from the same group.
              if(x != n)
                for(y = 0; y < j[x]; y++, z++)
                  ft->enemies[ft->nenemies-e+z] = f[x][y];
        }
      }
      // Free al the temp memory.
      for(x = 0; x < i; x++)
        if(j[x]==1) free(f[x]); // Free the single malloced factions.
      free(f); // Free the rest.
      free(j);
    }
  } while((node = node->next));
}


// Load all the factions.
int factions_load(void) {
  uint32_t bufsize;
  char* buf = pack_readfile(DATA, FACTION_DATA, &bufsize);

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

  Faction* tmp = NULL;

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

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

  do {
    if(node->type == XML_NODE_START) {
      if(strcmp((char*)node->name, XML_FACTION_TAG)==0) {
        tmp = faction_parse(node);
        faction_stack = realloc(faction_stack, sizeof(Faction)*(++nfactions));
        memcpy(faction_stack+nfactions-1, tmp, sizeof(Faction));
        free(tmp);
      }
      else if(strcmp((char*)node->name, XML_ALLIANCE_ID)==0)
        alliance_parse(node);
      else if(strcmp((char*)node->name, XML_ENEMIES_ID)==0)
        enemies_parse(node);
    }
  } while((node = node->next));

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

  DEBUG("Loaded %d Faction%s", nfactions, (nfactions==1) ?"": "s");

  return 0;
}

void factions_free(void) {
  int i;
  // Free alliances.
  for(i = 0; i < nalliances; i++) {
    free(alliances[i].name);
    free(alliances[i].factions);
  }
  free(alliances);
  alliances = NULL;
  nalliances = 0;

  // Free factions.
  for(i = 0; i < nfactions; i++) {
    free(faction_stack[i].name);
    if(faction_stack[i].nallies > 0)  free(faction_stack[i].allies);
    if(faction_stack[i].nenemies > 0) free(faction_stack[i].enemies);
  }
  free(faction_stack);
  faction_stack = NULL;
  nfactions = 0;
}