#include "Python.h"
#include "state_machine.h"
#include "debug.h"

typedef struct {
  StateId transitions[NUM_EVENTS];
  StateTransitionHandler handler;
  void *params;
  StateHandlerFree destruct;
} StateEntry;

struct StateTableStruct {
  /* The first member must be userData so the the StateTable_GetUserData
     macro works. */
  void *userData;
  StateId current;
  int size;
  int allocated;
  StateEntry *states;
};

#define INITIAL_STATES 20

#ifdef DEBUG_STATE_TABLE
static char *state_names[INITIAL_STATES] = {
  "ERROR_STATE",
  "START_STATE",
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  "PARSE_STREAM_STATE",
  "START_ELEMENT_CALLBACK",
  "END_ELEMENT_CALLBACK",
  "CHARACTER_DATA_CALLBACK",
  "COMMENT_CALLBACK",
  "PI_CALLBACK",
  "START_NS_SCOPE_CALLBACK",
  "END_NS_SCOPE_CALLBACK",
};

static char *event_names[NUM_EVENTS] = {
  "ERROR_EVENT",
  "PARSE_RESUME_EVENT",
  "START_ELEMENT_EVENT",
  "END_ELEMENT_EVENT",
  "CHARACTER_DATA_EVENT",
  "COMMENT_EVENT",
  "PI_EVENT",
  "START_NS_SCOPE_EVENT",
  "END_NS_SCOPE_EVENT",
  "XPTR_MATCH_EVENT",
  "XPTR_CLOSE_EVENT",
};

static char *get_state_name(StateId state) {
  static int maxlen = 0;
  char *name, *result;
  int i;

  if (maxlen == 0) {
    for (i = 0; i < INITIAL_STATES; i++) {
      if (state_names[i]) {
        int len = strlen(state_names[i]);
        if (len > maxlen) maxlen = len;
      }
    }
  }

  result = (char *) malloc(maxlen+1);
  if (state >= INITIAL_STATES) {
    name = (char *) malloc(maxlen+1);
    sprintf(name, "XPTR_STATE_%d", state - INITIAL_STATES);
    sprintf(result, "%-*s", maxlen, name);
    free(name);
  } else if (state_names[state]) {
    sprintf(result, "%-*s", maxlen, state_names[state]);
  } else {
    sprintf(result, "%-*d", maxlen, state);
  }

  return result;
}

#endif

#ifdef DEBUG_STATE_TABLE
static void errorHandler(StateTable *table, void *params)
{
  fprintf(stderr,"Pass through Error Handler\n");
}
#else
#define errorHandler NULL
#endif


StateTable *StateTable_New(void *userData)
{
  StateTable *table = PyMem_New(StateTable, 1);
  if (table != NULL) {
    table->userData = userData;
    table->current = 0;
    table->size = 0;
    table->allocated = INITIAL_STATES;
    if ((table->states = PyMem_New(StateEntry, INITIAL_STATES)) == NULL) {
      PyErr_NoMemory();
      return NULL;
    }
    memset(table->states, 0, INITIAL_STATES * sizeof(StateEntry));

    /* Add the error state */
    if (!StateTable_AddStateWithHandler(table, ERROR_STATE, errorHandler)) {
      StateTable_Del(table);
      return NULL;
    }

    /* Add the initial state */
    if (!StateTable_AddState(table, START_STATE)) {
      StateTable_Del(table);
      return NULL;
    }
  }

  return table;
}


void StateTable_Del(StateTable *table)
{
  int i;
  StateEntry *state = table->states;

  for (i = 0; i < table->size; i++) {
    if (state->destruct != NULL)
      state->destruct(state->params);
    state++;
  }

  PyMem_Del(table->states);
  PyMem_Del(table);
}


int StateTable_AddStateWithHandlerParams(StateTable *table, StateId newstate,
                                         StateTransitionHandler handler,
                                         void *params,
                                         StateHandlerFree destruct)
{
  StateEntry *states;
  size_t new_allocated;
  int allocated, newsize;

#ifdef DEBUG_STATE_TABLE
  fprintf(stderr, "StateTable: new state %s\n", get_state_name(newstate));
#endif
  
  if (newstate > INT_MAX) {
    PyErr_SetString(PyExc_OverflowError, "cannot add more states to table");
    return 0;
  }

  /* Bypass realloc() when a previous overallocation is large enough
     to accommodate the newsize.
  */
  allocated = table->allocated;
  states = table->states;
  if (newstate >= allocated) {
    /* This over-allocates proportional to the list size, making room
     * for additional growth.  The over-allocation is mild, but is
     * enough to give linear-time amortized behavior over a long
     * sequence of appends() in the presence of a poorly-performing
     * system realloc().
     * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
     */
    newsize = newstate + 1;
    new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6) + newsize;
    if (PyMem_Resize(states, StateEntry, new_allocated) == NULL) {
      PyErr_NoMemory();
      return 0;
    }
    memset(states + allocated, 0,
           (new_allocated - allocated) * sizeof(StateEntry));

    table->allocated = new_allocated;
    table->states = states;
    table->size = newsize;
  } else if (newstate >= table->size) {
    table->size = newstate + 1;
  }
  
  /* initialize the new state */
  memset(states[newstate].transitions, ERROR_STATE, 
         sizeof(StateId) * NUM_EVENTS);

  states[newstate].handler = handler;
  states[newstate].params = params;
  states[newstate].destruct = destruct;

  /* add a transition from this state to the error state */
  return StateTable_AddTransition(table, newstate, ERROR_EVENT, ERROR_STATE);
}


int StateTable_AddTransition(StateTable *table, StateId from, EventId event, 
                             StateId to)
{
  StateEntry *states = table->states;

#ifdef DEBUG_STATE_TABLE
  fprintf(stderr, "StateTable(%p): add transition from %s to %s on event %s\n", 
          table, get_state_name(from), get_state_name(to), event_names[event]);
#endif
  if (from > table->size) {
    PyErr_Format(PyExc_RuntimeError, "Initial state %d is undefined", from);
    return 0;
  }
  else if (to > table->size) {
    PyErr_Format(PyExc_RuntimeError, "Final state %d is undefined", to);
    return 0;
  }

  /* blindly replace existing entry in transitions table */
  states[from].transitions[event] = to;
  
  return 1;
}


StateId StateTable_Transit(StateTable *table, EventId event)
{
  StateId new_state_id;
  StateId current = table->current;
  StateEntry *states = table->states;

  /* find the state this event transitions to starting from the end */
  new_state_id = states[current].transitions[event];

#ifdef DEBUG_STATE_TABLE
  fprintf(stderr, "StateTable(%p): transition from %s to %s on event %s\n",
          table, get_state_name(current), get_state_name(new_state_id),
          event_names[event]);
#endif
  
  table->current = new_state_id;
  if (states[new_state_id].handler) {
    states[new_state_id].handler(table, states[new_state_id].params);
  }

  /* return the current state as the handler may have done a transition */
  return table->current;
}


StateId StateTable_GetState(StateTable *table)
{
  return table->current;
}


int StateTable_SetState(StateTable *table, StateId state)
{
  if (state < 0 || state > table->size) {
    PyErr_Format(PyExc_ValueError, "state %d out of bounds", state);
    return 0;
  }
  table->current = state;
  return 1;
}


void _StateTable_SignalError(StateTable *table, char *filename, int lineno)
{
  if (!PyErr_Occurred()) {
    PyErr_Format(PyExc_SystemError, "%s:%d: Error signaled without exception",
                 filename, lineno);
  }
#if defined(DEBUG_PARSER) || defined(DEBUG_STATE_TABLE)
  fprintf(stderr, "Error signaled in %s:%d\n", filename, lineno);
#endif

  StateTable_Transit(table, ERROR_EVENT);
}
