%{
#include <cstdio>
#include <vector>
#include <map>
#include <string>

#include <cstore/cstore.hpp>
#include <cnode/cnode.hpp>
#include "cparse.hpp"
#include "cparse_def.h"

/* to enable tracing, define ENABLE_PARSER_TRACE. may also want to invoke
 * bison with "-v" (by changing Makefile.am).
 */
#undef ENABLE_PARSER_TRACE
#ifdef ENABLE_PARSER_TRACE
#define YYDEBUG 1
#endif // ENABLE_PARSER_TRACE

using namespace std;
using namespace cnode;

// stuff from lex
extern "C" {
extern int cparse_lineno;
extern char *cparse_text;
int cparse_lex();
void cparse_set_in(FILE *fin);
}

static void
cparse_error(const char *s)
{
  printf("Invalid config file (%s): error at line %d, text [%s]\n",
         s, cparse_lineno, cparse_text);
}

static int ndeact = 0;
static char *ncomment = NULL;
static char *nname = NULL;
static char *nval = NULL;

// XXX optimize: use unordered_map with non-vector
static map<vector<string>, CfgNode *> node_map;
static Cstore *cstore = NULL;
static CfgNode *cur_node = NULL;
static CfgNode *cur_parent = NULL;
static vector<CfgNode *> cur_path;
static vector<string> pcomps;
static vector<bool> pcomp_is_value;

static void
add_node()
{
  pcomps.push_back(nname);
  CfgNode *onode = NULL;
  if (node_map.find(pcomps) != node_map.end()) {
    onode = node_map[pcomps];
  }
  pcomps.pop_back();
  if (onode) {
    if (nval) {
      if (onode->isMulti()) {
        // a new value for a "multi node"
        onode->addMultiValue(nval);
        cur_node = onode;
      } else if (onode->isTag()) {
        // a new value for a "tag node"
        cur_node = new CfgNode(pcomps, nname, nval, ncomment, ndeact, cstore);
        onode->addChildNode(cur_node);
      } else {
        /* a new value for a single-value node => invalid?
         * for now, use the newer value.
         */
        cur_node = onode;
        cur_node->setValue(nval);
      }
    } else {
      // existing intermediate node => move current node pointer
      cur_node = onode;
    }
  } else {
    // new node
    cur_node = new CfgNode(pcomps, nname, nval, ncomment, ndeact, cstore);
    CfgNode *mapped_node = cur_node;
    if (cur_node->isTag() && cur_node->isValue()) {
      // tag value => need to add the "tag node" on top
      // (need to force "tag" if the node is invalid => tag_if_invalid)
      CfgNode *p = new CfgNode(pcomps, nname, NULL, NULL, ndeact, cstore,
                               true);
      p->addChildNode(cur_node);
      mapped_node = p;
    }
    cur_parent->addChildNode(mapped_node);
    pcomps.push_back(nname);
    node_map[pcomps] = mapped_node;
    pcomps.pop_back();
  }
}

static void
go_down()
{
  cur_path.push_back(cur_parent);
  cur_parent = cur_node;

  pcomps.push_back(nname);
  pcomp_is_value.push_back(false);
  if (nval) {
    pcomps.push_back(nval);
    pcomp_is_value.push_back(true);
  }
}

static void
go_up()
{
  cur_parent = cur_path.back();
  cur_path.pop_back();

  if (pcomp_is_value.back()) {
    pcomps.pop_back();
    pcomp_is_value.pop_back();
  }
  pcomps.pop_back();
  pcomp_is_value.pop_back();
}

%}

%token NODE
%token VALUE
%token COMMENT
%token LEFTB
%token RIGHTB
%token SYNTAX_ERROR

%%

input:    forest comment
;

forest:     /* empty */
          | forest tree
;

tree:       node {
              add_node();
            }
          | node {
              add_node();
            } LEFTB {
              go_down();
            } forest RIGHTB {
              go_up();
            }
;

node:       nodec {
              nval = NULL;
            }
          | nodec VALUE {
              nval = $2.str;
            }
;

nodec:      NODE {
              ncomment = NULL;
              nname = $1.str;
              ndeact = $1.deactivated;
            }
          | COMMENT NODE {
              ncomment = $1.str;
              nname = $2.str;
              ndeact = $2.deactivated;
            }
;

comment:    /* empty */
          | COMMENT comment
;

%%

CfgNode *
cparse::parse_file(FILE *fin, Cstore& cs)
{
  // for debug (see prologue)
#ifdef ENABLE_PARSER_TRACE
  cparse_debug = 1;
#endif // ENABLE_PARSER_TRACE

  // initial state
  cparse_set_in(fin);
  cstore = &cs;
  ndeact = 0;
  ncomment = NULL;
  nname = NULL;
  nval = NULL;
  node_map.clear();
  pcomps.clear();
  pcomp_is_value.clear();
  cur_path.clear();
  cur_node = NULL;
  cur_parent = new CfgNode(pcomps, nname, nval, ncomment, ndeact, cstore);

  if (cparse_parse() != 0) {
    // parsing failed
    return NULL;
  }
  if (cur_path.size() > 0) {
    // didn't return to top-level => invalid
    return NULL;
  }
  return cur_parent;
}