/**
 * @file faction.c
 *
 * @brief Handle the Lephisto factions.
 */

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

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

#define XML_FACTION_ID      "Factions" /* XML section id. */
#define XML_FACTION_TAG     "faction"

#define FACTION_DATA        "../dat/faction.xml" /**< Faction xml file. */
#define FACTION_LOGO_PATH   "../gfx/logo/"       /**< Path to logo gfx. */

#define PLAYER_ALLY   70 /**< Above this, player is considered ally. */

/**
 * @struct Faction.
 *
 * @brief Represents a faction.
 */
typedef struct Faction_ {
  char* name;       /**< Normal name. */
  char* longname;   /**< Long name. */

  /* Graphics. */
  glTexture* logo_small; /**< Small logo. */

  /* Enemies. */
  int*  enemies;    /**< Enemies by ID of the faction. */
  int   nenemies;   /**< Number of enemies. */

  /* Allies. */
  int*  allies;     /**< Allies by ID of the faction. */
  int   nallies;    /**< Number of allies. */

  int player;       /**< Standing with player - from -100 to 100. */
} Faction;

static Faction* faction_stack = NULL;   /**< Faction stack. */
static int faction_nstack = 0;          /**< Number of factions in the faction stack. */

/* Static. */
static int faction_isFaction(int f);
static void faction_sanitizePlayer(Faction* faction);
static Faction* faction_parse(xmlNodePtr parent);
static void faction_parseSocial(xmlNodePtr parent);
/* Extern. */
int pfaction_save(xmlTextWriterPtr writer);
int pfaction_load(xmlNodePtr parent);

/**
 * @fn int faction_get(const char* name)
 *
 * @brief Get a faction ID by name.
 *    @param name Name of the faction to seek.
 *    @return ID of the faction.
 */
int faction_get(const char* name) {
  int i;
  for(i = 0; i < faction_nstack; i++)
    if(strcmp(faction_stack[i].name, name)==0)
      break;

  if(i != faction_nstack)
    return i;

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

/**
 * @fn char* faction_name(int f)
 *
 * @brief Get a factions short name.
 *    @param f Faction to get the name of.
 *    @return Name of the faction.
 */
char* faction_name(int f) {
  if((f < 0) || (f >= faction_nstack)) {
    WARN("Faction id '%d' is invalid.", f);
    return NULL;
  }
  return faction_stack[f].name;
}

/**
 * @fn char* faction_longname(int f)
 *
 * @brief Get the factions long name (formal).
 *    @param f Faction to get the name of.
 *    @return The factions long name.
 */
char* faction_longname(int f) {
  if((f < 0) || (f >= faction_nstack)) {
    WARN("Faction id '%d' is invalid.", f);
    return NULL;
  }
  if(faction_stack[f].longname != NULL)
    return faction_stack[f].longname;
  return faction_stack[f].name;
}

/**
 * @fn glTexture* faction_logoSmall(int f)
 *
 * @brief Get the factions small logo.
 *    @param f Faction to get the logo of.
 *    @return The factions small logo image.
 */
glTexture* faction_logoSmall(int f) {
  if((f < 0) || (f >= faction_nstack)) {
    WARN("Faction id '%d' is invalid.", f);
    return NULL;
  }
  return faction_stack[f].logo_small;
}

/**
 * @fn static void faction_sanitizePlayer(Faction* faction)
 *
 * @brief Sanitizes player faction standing.
 *    @param faction Faction to sanitize.
 */
static void faction_sanitizePlayer(Faction* faction) {
  if(faction->player > 100)
    faction->player = 100;
  else if(faction->player < -100)
    faction->player = -100;
}

/**
 * @fn void faction_modPlayer(int f, int mod)
 *
 * @brief Modifies the players standing with a faction.
 *
 * Affects enemies and allies too.
 *    @param f Faction to modify players standing.
 *    @param mod Modifier to modify by.
 *
 * @sa faction_modPlayerRaw
 */
void faction_modPlayer(int f, int mod) {
  int i;
  Faction* faction, *ally, *enemy;

  if(!faction_isFaction(f)) {
    WARN("%d is an invalid faction.", f);
    return;
  }

  faction = &faction_stack[f];

  /* Faction in question gets direct increment. */
  faction->player += mod;
  faction_sanitizePlayer(faction);

  /* Now mod allies to a lesser degree. */
  for(i = 0; i < faction->nallies; i++) {
    ally = &faction_stack[faction->allies[i]];

    ally->player += RNG(0, (mod*3)/4);
    faction_sanitizePlayer(ally);
  }

  /* Now mod enemies. */
  for(i = 0; i < faction->nenemies; i++) {
    enemy = &faction_stack[faction->enemies[i]];

    enemy->player -= MIN(1, RNG(0, (mod*3)/4));
    faction_sanitizePlayer(enemy);
  }
}

/**
 * @fn void faction_modPlayerRaw(int f, int mod)
 *
 * @brief Modifies the players standing without affecting others.
 *    @param f Faction whose standing to modify.
 *    @param mod Amount to modify standing by.
 *
 * @sa faction_modPlayer
 */
void faction_modPlayerRaw(int f, int mod) {
  Faction* faction;

  if(!faction_isFaction(f)) {
    WARN("%d is an invalid faction.", f);
    return;
  }

  faction = &faction_stack[f];

  faction->player += mod;
  faction_sanitizePlayer(faction);
}

/**
 * @fn int faction_getPlayer(int f)
 *
 * @brief Get the players standing with a faction.
 *    @param f Faction to get standing from.
 *    @return The standing the player has with the faction.
 */
int faction_getPlayer(int f) {
  if(faction_isFaction(f)) {
    return faction_stack[f].player;
  } else {
    WARN("%d is an invalid faction.", f);
    return -1000;
  }
}

/**
 * @fn glColour* faction_getColour(int f)
 *
 * @brief Get the colour of the faction basewd on its standing with the player.
 * 
 * Used to unify the colour checks all over.
 *    @param f Faction to get the colour of based on players standing.
 *    @return Pointer to the colour.
 */
glColour* faction_getColour(int f) {
  if(f < 0) return &cInert;
  else if(areAllies(FACTION_PLAYER, f))   return &cFriend;
  else if(areEnemies(FACTION_PLAYER, f))  return &cHostile;
  else return &cNeutral;
}

/**
 * @fn char* faction_getStanding(int mod)
 *
 * @brief Get the players standing in human readable form.
 *    @param mod Players standing.
 *    @return Human readable players standing.
 */
static char* player_standings[] = {
  "Hero",     /* 0 */
  "Admired",
  "Great",
  "Good",
  "Decent",
  "Wanted",   /* 5 */
  "Outlaw",
  "Criminal",
  "Enemy"
};

#define STANDING(m, i) if(mod >= m) return player_standings[i];
char* faction_getStanding(int mod) {
  STANDING( 90, 0);
  STANDING( 70, 1);
  STANDING( 50, 2);
  STANDING( 30, 3);
  STANDING( 0,  4);
  STANDING(-15, 5);
  STANDING(-30, 6);
  STANDING(-50, 7);
  return player_standings[8];

}
#undef STANDING

/**
 * @fn int areEnemies(int a, int b)
 *
 * @brief Check whether two factions are enemies.
 *    @param a Faction A.
 *    @param b Faction B.
 *    @return 1 if A and B are enemies, 0 otherwise.
 */
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 {
      WARN("areEnemies: %d is an invalid faction.", b);
      return 0;
    }
  }
  if(b == FACTION_PLAYER) {
    if(faction_isFaction(a)) {
      if(faction_stack[a].player < 0)
        return 1;
      else return 0;
    } else {
      WARN("areEnemies: %d is an invalid faction.", a);
      return 0;
    }
  }

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

  /* Handle b. */
  if(faction_isFaction(b)) fb = &faction_stack[b];
  else {
    /* b isn't valid. */
    WARN("areEnemies: %d is an invalid faction.", 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;
}

/**
 * @fn int areAllies(int a, int b)
 *
 * @brief Check whether two factions are allies or not.
 *    @param a Faction A.
 *    @param b Faction B.
 *    @return 1 if A and B are allies, 0 otherwise.
 */
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 {
      WARN("%d is an invalid faction.", b);
      return 0;
    }
  }
  if(b == FACTION_PLAYER) {
    if(faction_isFaction(a)) {
      if(faction_stack[a].player > PLAYER_ALLY) return 1;
      else return 0;
    } else {
      WARN("%d is an invalid faction.", 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. */
    WARN("%d is an invalid faction.", a);
    return 0;
  }

  /* Handle b. */
  if(faction_isFaction(b)) fb = &faction_stack[b];
  else {
    /* b isn't valid. */
    WARN("%d is an invalid faction.", 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;
}

/**
 * @fn static int faction_isFaction(int f)
 *
 * @brief Check whether or not a faction is valid.
 *    @param f Faction to check for validity.
 *    @return 1 if faction is valid, 0 otherwise.
 */
static int faction_isFaction(int f) {
  if((f < 0) || (f >= faction_nstack))
    return 0;
  return 1;
}

/**
 * @fn static Faction* faction_parse(xmlNodePtr parent)
 *
 * @brief Parses a single faction, but doesn't set the allies/enemies bit.
 *    @param parent Parent node to extract faction from.
 *    @return Faction created from parent node.
 */
static Faction* faction_parse(xmlNodePtr parent) {
  xmlNodePtr node;
  int player;
  Faction* tmp;
  char buf[PATH_MAX];

  tmp = CALLOC_L(Faction);

  tmp->name = xml_nodeProp(parent, "name");
  if(tmp->name == NULL)
    WARN("Faction from "FACTION_DATA" has invalid or no name");

  player = 0;
  node = parent->xmlChildrenNode;
  do {
    /* Can be 0 or negative, so we have to take that into account. */
    if(xml_isNode(node, "player")) {
      tmp->player = xml_getInt(node);
      player = 1;
      continue;
    }
    
    xmlr_strd(node, "longname", tmp->longname);

    if(xml_isNode(node, "logo")) {
      snprintf(buf, PATH_MAX, FACTION_LOGO_PATH"%s_small.png", xml_get(node));
      tmp->logo_small = gl_newImage(buf);
      continue;
    }
  } while(xml_nextNode(node));

  if(player == 0) WARN("Faction '%s' missing player tag", tmp->name);

  return tmp;
}

/* Parse the social tidbits: Allies and enemies. */
static void faction_parseSocial(xmlNodePtr parent) {
  xmlNodePtr node, cur;
  char* buf;
  Faction* base;
  int mod;

  buf = xml_nodeProp(parent, "name");
  base = &faction_stack[faction_get(buf)];
  free(buf);

  node = parent->xmlChildrenNode;
  do {
    if(xml_isNode(node, "allies")) {
      cur = node->xmlChildrenNode;
      do {
        if(xml_isNode(cur, "ally")) {
          mod = faction_get(xml_get(cur));
          base->nallies++;
          base->allies = realloc(base->allies, sizeof(int)*base->nallies);
          base->allies[base->nallies-1] = mod;
        }
      } while(xml_nextNode(cur));
    }

    /* Grab the enemies. */
    if(xml_isNode(node, "enemies")) {
      cur = node->xmlChildrenNode;
      do {
        if(xml_isNode(cur, "enemy")) {
          mod = faction_get(xml_get(cur));
          base->nenemies++;
          base->enemies = realloc(base->enemies, sizeof(int)*base->nenemies);
          base->enemies[base->nenemies-1] = mod;
        }
      } while(xml_nextNode(cur));
    }
  } while(xml_nextNode(node));
}

/**
 * @fn int factions_load(void)
 *
 * @brief Loads up all the factions from the data file.
 *    @return 0 on success.
 */
int factions_load(void) {
  uint32_t bufsize;
  char* buf = pack_readfile(DATA, FACTION_DATA, &bufsize);

  xmlNodePtr factions, 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;
  }

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

  /* Player faction is hardcoded. */
  faction_stack = malloc(sizeof(Faction));
  memset(faction_stack, 0, sizeof(Faction));
  faction_stack[0].name     = strdup("Player");
  faction_nstack++;

  /* First pass. */
  node = factions;
  do {
    if(xml_isNode(node, XML_FACTION_TAG)) {
      tmp = faction_parse(node);
      faction_stack = realloc(faction_stack, sizeof(Faction)*(++faction_nstack));
      memcpy(faction_stack + faction_nstack - 1, tmp, sizeof(Faction));
      free(tmp);
    }
  } while(xml_nextNode(node));

  /* Second pass - Set allies and enemies. */
  node = factions;
  do {
    if (xml_isNode(node,XML_FACTION_TAG))
      faction_parseSocial(node);
  } while (xml_nextNode(node));

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

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

  return 0;
}

/**
 * @fn void factions_free(void)
 *
 * @brief Frees the factions.
 */
void factions_free(void) {
  int i;

  /* Free factions. */
  for(i = 0; i < faction_nstack; i++) {
    free(faction_stack[i].name);
    if(faction_stack[i].longname != NULL)   free(faction_stack[i].longname);
    if(faction_stack[i].logo_small != NULL) gl_freeTexture(faction_stack[i].logo_small);
    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;
  faction_nstack = 0;
}

/**
 * @fn int pfaction_save(xmlTextWriterPtr writer)
 *
 * @brief Save players standings with the factions.
 *    @param writer The XML writer to use.
 *    @return 0 on success.
 */
int pfaction_save(xmlTextWriterPtr writer) {
  int i;

  xmlw_startElem(writer, "factions");

  /* Player is faction 0. */
  for(i = 1; i < faction_nstack; 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;
}

/**
 * @fn int pfaction_load(xmlNodePtr parent)
 *
 * @brief Load up the players faction standings.
 *    @param parent Parent xml node to read from.
 *    @return 0 on success.
 */
int pfaction_load(xmlNodePtr parent) {
  xmlNodePtr node, cur;
  char* str;
  int faction;

  node = parent->xmlChildrenNode;

  do {
    if(xml_isNode(node, "factions")) {
      cur = node->xmlChildrenNode;
      do {
          if(xml_isNode(cur, "factions")) {
            xmlr_attr(cur, "name", str);
            faction = faction_get(str);
            if(faction != -1) /* Faction is valid. */
              faction_stack[faction].player = xml_getInt(cur);
            free(str);
          }
      } while(xml_nextNode(cur));
    }
  } while(xml_nextNode(node));

  return 0;
}

