#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 PLAYER_ALLY   70 /* Above this, player is considered ally. */

#define ALLIANCE_OFFSET 27182 /* Special offset for alliances. */

typedef struct Faction_ {
  char* name;

  int*  enemies;
  int   nenemies;
  int*  allies;
  int   nallies;

  int player; /* Standing with player - from -100 to 100. */
} 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. */
static Faction* faction_parse(xmlNodePtr parent);
static void alliance_parse(xmlNodePtr parent);
static void enemies_parse(xmlNodePtr parent);
static Alliance* alliance_get(char* name);
/* Extern. */
int pfaction_save(xmlTextWriterPtr writer);
int pfaction_load(xmlNodePtr parent);

/* 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;
}

void faction_modPlayer(int f, int mod) {
  if(faction_isFaction(f)) {
    faction_stack[f].player += mod;
  } else {
    DEBUG("%d is an invalid faction/alliance", f);
    return;
  }
}

int faction_getPlayer(int f) {
  if(faction_isFaction(f)) {
    return faction_stack[f].player;
  } else {
    DEBUG("%d is an invalid faction/alliance", f);
    return -1000;
  }
}

/* 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. */

  /* Player handled seperately. */
  if(a == FACTION_PLAYER) {
    if(faction_isFaction(b)) {
      if(faction_stack[b].player < 0)
        return 1;
      else return 0;
    } else {
      DEBUG("areEnemies: %d is an invalid faction/alliance", b);
      return 0;
    }
  }
  if(b == FACTION_PLAYER) {
    if(faction_isFaction(a)) {
      if(faction_stack[a].player < 0)
        return 1;
      else return 0;
    } else {
      DEBUG("areEnemies: %d is an invalid faction/alliance", a);
      return 0;
    }
  }

  /* 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;

  /* We assume player becomes allies with high rating. */
  if(a == FACTION_PLAYER) {
    if(faction_isFaction(b)) {
      if(faction_stack[b].player > PLAYER_ALLY) return 1;
      else return 0;
    } else {
      DEBUG("%d is an invalid faction/alliance", b);
      return 0;
    }
  }
  if(b == FACTION_PLAYER) {
    if(faction_isFaction(a)) {
      if(faction_stack[a].player > PLAYER_ALLY) return 1;
      else return 0;
    } else {
      DEBUG("%d is an invalid faction/alliance", a);
      return 0;
    }
  }

  /* D'aww. Player has no allies. */
  if((a == FACTION_PLAYER) || (b == FACTION_PLAYER))
    return 0;

  /* Handle a. */
  if(faction_isFaction(a)) fa = &faction_stack[a];
  else {
    /* b isn't valid. */
    DEBUG("%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("%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-ALLIANCE_OFFSET];

  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) {
  xmlNodePtr node;
  int player;
  Faction* tmp;

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

  player = 0;
  node = parent->xmlChildrenNode;
  do {
    if(xml_isNode(node, "player")) {
      tmp->player = xml_getInt(node);
      player = 1;
    }
  } while(xml_nextNode(node));

  if(player == 0) DEBUG("Faction '%s' missing player tag", tmp->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(!xml_isNode(node, 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;
  }

  /* Player faction is hardcoded. */
  faction_stack = malloc(sizeof(Faction));
  faction_stack[0].name     = strdup("Player");
  faction_stack[0].nallies  = 0;
  faction_stack[0].nenemies = 0;
  nfactions++;

  do {
    if(xml_isNode(node, XML_FACTION_TAG)) {
      tmp = faction_parse(node);
      faction_stack = realloc(faction_stack, sizeof(Faction)*(++nfactions));
      memcpy(faction_stack + nfactions - 1, tmp, sizeof(Faction));
      free(tmp);
    }
    else if(xml_isNode(node, XML_ALLIANCE_ID))
      alliance_parse(node);
    else if(xml_isNode(node, XML_ENEMIES_ID))
      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;
}

int pfaction_save(xmlTextWriterPtr writer) {
  int i;

  xmlw_startElem(writer, "factions");

  /* Player is faction 0. */
  for(i = 1; i < nfactions; i++) {
    xmlw_startElem(writer, "faction");

    xmlw_attr(writer, "name", "%s", faction_stack[i].name);
    xmlw_str(writer, "%d", faction_stack[i].player);

    xmlw_endElem(writer); /* Faction. */
  }

  xmlw_endElem(writer); /* Faction. */

  return 0;
}

int pfaction_load(xmlNodePtr parent) {
  xmlNodePtr node, cur;
  char* str;

  node = parent->xmlChildrenNode;

  do {
    if(xml_isNode(node, "factions")) {
      cur = node->xmlChildrenNode;
      do {
          if(xml_isNode(cur, "factions")) {
            xmlr_attr(cur, "name", str);
            faction_stack[faction_get(str)].player = xml_getInt(cur);
            free(str);
          }
      } while(xml_nextNode(cur));
    }
  } while(xml_nextNode(node));

  return 0;
}

