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

#include "log.h"
#include "lephisto.h"
#include "xml.h"
#include "hook.h"

#define HOOK_CHUNK  32  /* Size to grow by when out of space. */

/* The hook. */
typedef struct Hook_ {
  unsigned int id;      /* Unique id. */
  unsigned int parent;  /* Mission it's connected to. */
  char* func;           /* Function returned. */
  char* stack;          /* Stack it's a part of. */

  int delete;           /* Indicates it should be deleted when possible. */
} Hook;

/* The stack. */
static unsigned int     hook_id             = 0; /* Unique hook id. */
static Hook*            hook_stack          = NULL;
static int              hook_mstack         = 0;
static int              hook_nstack         = 0;
static int              hook_runningstack   = 0;  /* Check if stack is running. */

/* Extern. */
extern int misn_run(Mission* misn, char* func);
/* Intern. */
static int hook_run(Hook* hook);
static void hook_free(Hook* h);
static int hook_needSave(Hook* h);
static int hook_parse(xmlNodePtr base);
/* Extern. */
int hook_save(xmlTextWriterPtr writer);
int hook_load(xmlNodePtr parent);

static int hook_run(Hook* hook) {
  int i;
  Mission* misn;

  if(hook->delete) return 0; /* Hook should be deleted not run. */

  /* Locate the mission. */
  for(i = 0; i < MISSION_MAX; i++)
    if(player_missions[i].id == hook->parent)
      break;
  if(i >= MISSION_MAX) {
    WARN("Trying to run hook with parent not in player mission stack: deleting");
    hook->delete = 1; /* So delete it. */
    return -1;
  }
  misn = &player_missions[i];

  if(misn_run(misn, hook->func) < 0)
    /* Error has accured. */
    WARN("Hook [%s] '%d' -> '%s' failed", hook->stack,
         hook->id, hook->func);

  return 0;
}

/* Add/Remove hooks. */
unsigned int hook_add(unsigned int parent, char* func, char* stack) {
  Hook* new_hook;

  /* If the memory must grow. */
  if(hook_nstack+1 > hook_mstack) {
    hook_mstack += HOOK_CHUNK;
    hook_stack = realloc(hook_stack, hook_mstack*sizeof(Hook));
  }

  /* Create the new hook. */
  new_hook = &hook_stack[hook_nstack];
  new_hook->id      = ++hook_id;
  new_hook->parent  = parent;
  new_hook->func    = strdup(func);
  new_hook->stack   = strdup(stack);
  new_hook->delete  = 0;

  hook_nstack++;

  return new_hook->id;
}

void hook_rm(unsigned int id) {
  int l, m, h;
  l = 0;
  h = hook_nstack-1;

  while(l <= h) {
    m = (l+h)/2;
    if(hook_stack[m].id > id) h = m-1;
    else if(hook_stack[m].id < id) l = m+1;
    else break;
  }

  /* Mark to delete, but do not delete yet, hooks are running. */
  if(hook_runningstack) {
    hook_stack[m].delete = 1;
    return;
  }

  /* Free the hook. */
  hook_free(&hook_stack[m]);

  /* Last hook, just clip the stack. */
  if(m == (hook_nstack-1)) {
    hook_nstack--;
    return;
  }

  /* Move it! */
  memmove(&hook_stack[m], &hook_stack[m+1], sizeof(Hook)*(hook_nstack-(m+1)));
  hook_nstack--;
}

void hook_rmParent(unsigned int parent) {
  int i;
  for(i = 0; i < hook_nstack; i++)
    if(parent == hook_stack[i].parent) {
      hook_rm(hook_stack[i].id);
      if(!hook_runningstack) i--;
    }
}

/* Run all hooks off the stack. */
int hooks_run(char* stack) {
  int i;

  hook_runningstack = 1; /* Running hooks. */
  for(i = 0; i < hook_nstack; i++)
    if((strcmp(stack, hook_stack[i].stack)==0) && !hook_stack[i].delete) {
      hook_run(&hook_stack[i]);
    }
  hook_runningstack = 0; /* Not running hooks anymore. */

  for(i = 0; i < hook_nstack; i++)
    if(hook_stack[i].delete) { /* Delete any that need deleting. */
      hook_rm(hook_stack[i].id);
      i--;
    }

  return 0;
}

/* Run a single hook by id. */
void hook_runID(unsigned int id) {
  int i;

  for(i = 0; i < hook_nstack; i++)
    if(hook_stack[i].id == id) {
      hook_run(&hook_stack[i]);
      return;
    }
  DEBUG("Attempting to run hook of id '%d' which is not in the stack", id);
}

/* Free a hook. */
static void hook_free(Hook* h) {
  if(h->func != NULL)   free(h->func);
  if(h->stack != NULL)  free(h->stack);
}

/* Clean upi after ourselves. */
void hook_cleanup(void) {
  int i;

  for(i = 0; i < hook_nstack; i++)
    hook_free(&hook_stack[i]);
  free(hook_stack);
  hook_stack  = NULL;
  /* Sane defaults just in case. */
  hook_nstack = 0;
  hook_mstack = 0;
}

/* Save the hooks. */
static int hook_needSave(Hook* h) {
  int i;
  char* nosave[] = {
    "death", "board", "disable", /* Pilot hooks. */
    "end" };

  for(i = 0; strcmp(nosave[i], "end") != 0; i++)
    if(strcmp(nosave[i], h->stack)==0) return 0;

  return 1;
}

int hook_save(xmlTextWriterPtr writer) {
  int i;
  Hook* h;

  xmlw_startElem(writer, "hooks");
  for(i = 0; i < hook_nstack; i++) {
    h = &hook_stack[i];

    if(!hook_needSave(h)) continue; /* No need to save it. */

    xmlw_startElem(writer, "hook");

    /*xmlw_attr(writer, "id", "%u", h->id);*/ /* I don't think it's needed. */
    xmlw_elem(writer, "parent", "%u", h->parent);
    xmlw_elem(writer, "func", "%s", h->func);
    xmlw_elem(writer, "stack", "%s", h->stack);

    xmlw_endElem(writer); /* Hook. */
  }
  xmlw_endElem(writer); /* Hooks. */

  return 0;
}

/* Load hooks for a player. */
int hook_load(xmlNodePtr parent) {
  xmlNodePtr node;

  hook_cleanup();

  node = parent->xmlChildrenNode;
  do {
    if(xml_isNode(node, "hooks"))
      hook_parse(node);
  } while(xml_nextNode(node));

  return 0;
}

static int hook_parse(xmlNodePtr base) {
  xmlNodePtr node, cur;
  char* func, *stack;
  unsigned int parent;

  node = base->xmlChildrenNode;
  do {
    if(xml_isNode(node, "hook")) {
      parent = 0;
      func = NULL;
      stack = NULL;

      cur = node->xmlChildrenNode;
      do {
        xmlr_long(cur, "parent", parent);
        xmlr_str(cur, "func", func);
        xmlr_str(cur, "stack", stack);
      } while(xml_nextNode(cur));

      if((parent == 0) || (func == NULL) || (stack == NULL)) {
        WARN("Invalid hook.");
        return -1;
      }
      hook_add(parent, func, stack);
    }
  } while(xml_nextNode(node));

  return 0;
}

