#include "state.h"
#include "context.h"
#include "debug.h"
#include "xsutil.h"

HV* newState (pTHX) {
  return initState(aTHX_ newHV_mortal());
}

HV* initState (pTHX_ HV* state) {
  XSUTIL_HV_STORE_REF(state,   "schema",   (SV*)newHV_mortal());
  XSUTIL_HV_STORE_NOINC(state, "table",    &PL_sv_undef);
  XSUTIL_HV_STORE(state,       "context",  XSUTIL_NEW_SVIV_MORTAL(CONTEXT_GLOBAL));
  XSUTIL_HV_STORE(state,       "_context", XSUTIL_NEW_SVIV_MORTAL(CONTEXT_GLOBAL));
  XSUTIL_HV_STORE(state,       "_nest",    XSUTIL_NEW_SVIV_MORTAL(0));
  XSUTIL_HV_STORE(state,       "nest",     XSUTIL_NEW_SVIV_MORTAL(0));
  return state;
}

SV* _get_parser_context (pTHX_ HV* state) {
  SV** ssv = XSUTIL_HV_FETCH(state, "context");
  if (ssv) {
    return *ssv;
  }
  else {
    sv_dump((SV*)state);
    croak("Cannot get context.");
  }
}

SV* _get_parser_recent_context (pTHX_ HV* state) {
  SV** ssv = XSUTIL_HV_FETCH(state, "_context");
  if (ssv) {
    return *ssv;
  }
  else {
    sv_dump((SV*)state);
    croak("Cannot get recent context.");
  }
}

SV* _get_nest (pTHX_ HV* state) {
  SV** ssv = XSUTIL_HV_FETCH(state, "nest");
  if (ssv) {
    return *ssv;
  }
  else {
    sv_dump((SV*)state);
    croak("Cannot get nest.");
  }
}

SV* _get_recent_nest (pTHX_ HV* state) {
  SV** ssv = XSUTIL_HV_FETCH(state, "nest");
  if (ssv) {
    return *ssv;
  }
  else {
    sv_dump((SV*)state);
    croak("Cannot get nest.");
  }
}

void set_parser_context (pTHX_ HV* state, const IV context) {
  {
    SV* context_sv  = _get_parser_context(aTHX_ state);
    SV* _context_sv = _get_parser_recent_context(aTHX_ state);
    sv_setiv(_context_sv, SvIV(context_sv));
    sv_setiv(context_sv,  context);
    DEBUG_OUT("context: %d\n", (int)SvIV(context_sv));
  }
  {
    SV* nest_sv  = _get_nest(aTHX_ state);
    SV* _nest_sv = _get_recent_nest(aTHX_ state);
    sv_setiv(_nest_sv, SvIV(nest_sv));
    sv_setiv(nest_sv,  0);
    DEBUG_OUT("nest: %d\n", (int)SvIV(nest_sv));
  }
}

void restore_context (pTHX_ HV* state) {
  {
    SV* context_sv  = _get_parser_context(aTHX_ state);
    SV* _context_sv = _get_parser_recent_context(aTHX_ state);
    sv_setiv(context_sv,  SvIV(_context_sv));
    sv_setiv(_context_sv, 0);
    DEBUG_OUT("context: %d\n", (int)SvIV(context_sv));
  }
  {
    SV* nest_sv  = _get_nest(aTHX_ state);
    SV* _nest_sv = _get_recent_nest(aTHX_ state);
    sv_setiv(nest_sv,  SvIV(_nest_sv));
    sv_setiv(_nest_sv, 0);
    DEBUG_OUT("nest: %d\n", (int)SvIV(nest_sv));
  }
}

IV get_parser_context (pTHX_ HV* state) {
  return SvIV(_get_parser_context(aTHX_ state));
}

IV get_nest (pTHX_ HV* state) {
  return SvIV(_get_nest(aTHX_ state));
}

void incr_nest (pTHX_ HV* state) {
  SV* nest = _get_nest(aTHX_ state);
  sv_setiv(nest, SvIV(nest) + 1);
}

void decr_nest (pTHX_ HV* state) {
  SV* nest = _get_nest(aTHX_ state);
  sv_setiv(nest, SvIV(nest) - 1);
}

void set_table (pTHX_ HV* state, const char* name, const size_t length) {
  SV* table = get_table(aTHX_ state);
  DEBUG_OUT("table name: %.*s\n", (int)length, name);
  if (SvOK(table)) {
    sv_setpvn(table, name, length);
  }
  else {
    table = sv_2mortal(newSVpvn(name, length));
    XSUTIL_HV_STORE(state, "table", table);
  }
  get_or_create_schema(aTHX_ state, table);
}

SV* get_table (pTHX_ HV* state) {
  SV** ssv = XSUTIL_HV_FETCH(state, "table");
  if (ssv) {
    DEBUG_OUT("table name: %s\n", SvPV_nolen(*ssv));
    return *ssv;
  }
  else {
    return &PL_sv_undef;
  }
}

HV* get_current_schema (pTHX_ HV* state) {
  SV* table = get_table(aTHX_ state);
  return get_or_create_schema(aTHX_ state, table);
}

HV* get_or_create_schema (pTHX_ HV* state, SV* key) {
  SV** ssv = XSUTIL_HV_FETCH(state, "schema");
  if (!ssv) {
    sv_dump((SV*)state);
    croak("Cannot get schema.");
  }

  HV* schema  = (HV*)SvRV(*ssv);
  HE* entry   = XSUTIL_HV_FETCH_ENT(schema, key);
  if (entry) {
    DEBUG_OUT("get table: %s\n", SvPV_nolen(key));
    return (HV*) SvRV(HeVAL(entry));
  }
  else {
    DEBUG_OUT("create table: %s\n", SvPV_nolen(key));
    HV* table = newHV_mortal();
    XSUTIL_HV_STORE_ENT_REF(schema, key, (SV*)table);
    return table;
  }
}

AV* get_or_create_columns (pTHX_ HV* table) {
  SV** ssv = XSUTIL_HV_FETCH(table, "columns");
  if (ssv) {
    DEBUG_OUT("get columns\n");
    return (AV*)SvRV(*ssv);
  }
  else {
    DEBUG_OUT("create columns\n");
    AV* columns = newAV_mortal();
    XSUTIL_HV_STORE_REF(table, "columns", (SV*)columns);
    return columns;
  }
}