/*
 * Configuration file loader
 *
 * $Id: load.c,v 1.1.1.1 2003/11/25 22:15:11 jharrell Exp $
 *
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

/* System includes */
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>
#include <syslog.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>

/* Local includes */
#include "load.h"
#include "lane.h"
#include "units.h"
#include "dump.h"
#include "mem.h"
#include "load_lex.h"

/* Type definitions */
typedef enum {
  VT_INT, VT_STR, VT_BOOL, VT_ADDR, VT_PVC
} VarType_t;

typedef struct {
  const Unit_t *unit;
  const char *name;
  VarType_t type;
  union {
    int intval;
    const char *strval;
    Bool_t boolval;
    const AtmAddr_t *addrval;
    const InitPvc_t *init;
  } val_u;
} Var_t;

typedef struct _VarList_t {
  Var_t *var;
  struct _VarList_t *next;
} VarList_t;

/* Local function prototypes */
static void load_init0(void);
static void load_init1(void);
static void load_dump(void);
static void load_release(void);

static Var_t *find_var(const Unit_t *unit, const char *varname);
static void add_var(Var_t *var);
static void load_vars(const char *filename);


/* Data */
#define BUFLEN 256
static const char *rcsid = "$Id: load.c,v 1.1.1.1 2003/11/25 22:15:11 jharrell Exp $";

static VarList_t *varlist;
extern char *var_file;

const Unit_t load_unit = {
  "load",
  &load_init0,
  &load_init1,
  &load_dump,
  &load_release
};

/* Functions */

/* Initialize local data */
static void
load_init0(void)
{
  varlist = NULL;
}

/* Initialization for data that needs other units */
static void
load_init1(void)
{
  set_var_str(&load_unit, "version", rcsid);
  /*
   * Load variables from file
   * For example:
   * [main]
   * debug=True
   * memdebug=True
   * [conn]
   * #S4, Join Timeout, s
   * S4=60
   */
  load_vars(var_file);

  Debug_unit(&load_unit, "Initialized.");
}

/* Dump status, local data etc. */
static void
load_dump(void)
{
  dump_vars(NULL);
}

/* Release allocated memory, close files etc. */
static void
load_release(void)
{
  VarList_t *tmp;
  LaneDestList_t *ltmp, *ltmp2;

  Debug_unit(&load_unit, "Releasing unit");
  for (tmp = varlist; tmp != NULL;) {
    Debug_unit(&load_unit, "Freeing var %s/%s", tmp->var->unit->name, tmp->var->name);
    assert(tmp->var != NULL);
    assert(tmp->var->name != NULL);
    varlist = varlist->next;
    if (tmp->var->type == VT_STR){
      assert(tmp->var->val_u.strval != NULL);
      mem_free(&load_unit, tmp->var->val_u.strval);
    }
    if (tmp->var->type == VT_ADDR){
      assert(tmp->var->val_u.addrval != NULL);
      mem_free(&load_unit, tmp->var->val_u.addrval);
    }
    if (tmp->var->type == VT_PVC){
      assert(tmp->var->val_u.init != NULL);
      assert(tmp->var->val_u.init->pvc != NULL);
      mem_free(&load_unit, tmp->var->val_u.init->pvc);
      assert(tmp->var->val_u.init->address != NULL);
      mem_free(&load_unit, tmp->var->val_u.init->address);
      ltmp = tmp->var->val_u.init->destinations;
      while (ltmp != NULL) {
	ltmp2 = ltmp->next;
	assert(ltmp->addr != NULL);
	mem_free(&load_unit, ltmp->addr);
	mem_free(&load_unit, ltmp);
	ltmp = ltmp2;
      }
      mem_free(&load_unit, tmp->var->val_u.init);
    }
    mem_free(&load_unit, tmp->var->name);
    mem_free(&load_unit, tmp->var);
    mem_free(&load_unit, tmp);
    tmp = varlist;
  }
}

static Var_t *
find_var(const Unit_t *unit, const char *varname)
{
  VarList_t *tmp;

  assert(unit != NULL);
  assert(unit->name != NULL);
  assert(varname != NULL);

  for (tmp = varlist; tmp != NULL; tmp = tmp->next) {
    assert(tmp->var != NULL);
    assert(tmp->var->unit != NULL);
    assert(tmp->var->unit->name != NULL);
    assert(tmp->var->name != NULL);
    if (strcmp(unit->name, tmp->var->unit->name) == 0 && strcmp(tmp->var->name, varname) == 0) {
      break;
    }
  }
  if (tmp) {
    return tmp->var;
  }
  else {
    return NULL;
  }
}

/* Get or initialize variable */
int
get_var_int(const Unit_t *unit, const char *varname)
{
  const Var_t *tmp;

  tmp = find_var(unit, varname);
  if (tmp) {
    assert(tmp->type == VT_INT);
    return tmp->val_u.intval;
  }
  else {
    return 0;
  }
}

const InitPvc_t *get_var_vcc(const Unit_t *unit, const char *varname)
{
  const Var_t *tmp;

  tmp=find_var(unit, varname);
  if(tmp) {
    assert(tmp->type == VT_PVC);
    return tmp->val_u.init;
  } else {
    return NULL;
  }
}

const char *
get_var_str(const Unit_t *unit, const char *varname)
{
  const Var_t *tmp;

  tmp=find_var(unit, varname);
  if (tmp) {
    assert(tmp->type == VT_STR);
    return tmp->val_u.strval;
  }
  else {
    return NULL;
  }
}

Bool_t
get_var_bool(const Unit_t *unit, const char *varname)
{
  const Var_t *tmp;

  tmp = find_var(unit, varname);
  if (tmp) {
    assert(tmp->type == VT_BOOL);
    return tmp->val_u.boolval;
  }
  else {
    return BL_FALSE;
  }
}

const AtmAddr_t *
get_var_addr(const Unit_t *unit, const char *varname)
{
  const Var_t *tmp;

  tmp = find_var(unit, varname);
  if (tmp) {
    assert(tmp->type == VT_ADDR);
    return tmp->val_u.addrval;
  }
  else {
    return NULL;
  }
}

static void
add_var(Var_t *var)
{
  VarList_t *tmpl;

  tmpl = (VarList_t *)mem_alloc(&load_unit, sizeof(VarList_t));
  tmpl->var = var;
  tmpl->next = varlist;
  varlist = tmpl;
}

/* Set or initialize variable */

void 
set_var_vcc(const Unit_t *unit, const char *varname, 
	    const InitPvc_t *vcc)
{
  Var_t *tmp;
  LaneDestList_t *ltmp, *ltmp2;

  assert(unit != NULL && unit->name != NULL && varname != NULL);
  tmp = find_var(unit, varname);
  if (tmp == NULL) {
    tmp = (Var_t *)mem_alloc(&load_unit, sizeof(Var_t));
    tmp->unit = unit;
    tmp->name = varname;
    tmp->val_u.init = vcc;
    tmp->type = VT_PVC;
    add_var(tmp);
  } else {
    assert(tmp->type == VT_PVC);
    mem_free(&load_unit, tmp->name);
    tmp->name = varname;
    assert(tmp->val_u.init->pvc != NULL);
    mem_free(&load_unit, tmp->val_u.init->pvc);
    assert(tmp->val_u.init->address != NULL);
    mem_free(&load_unit, tmp->val_u.init->address);
    ltmp = tmp->val_u.init->destinations;
    while(ltmp != NULL) {
      ltmp2 = ltmp->next;
      assert(ltmp->addr != NULL);
      mem_free(&load_unit, ltmp->addr);
      mem_free(&load_unit, ltmp);
      ltmp = ltmp2;
    }
    mem_free(&load_unit, tmp->val_u.init);
    tmp->val_u.init = vcc;
  }
}

void
set_var_int(const Unit_t *unit, const char *varname, int intval)
{
  Var_t *tmp;

  assert(unit != NULL && unit->name != NULL && varname != NULL);
  tmp = find_var(unit, varname);
  if (tmp == NULL) {
    tmp = (Var_t *)mem_alloc(&load_unit, sizeof(Var_t));
    tmp->unit = unit;
    tmp->name = varname;
    tmp->val_u.intval = intval;
    tmp->type = VT_INT;
    add_var(tmp);
  }
  else {
    assert(tmp->type == VT_INT);
    mem_free(&load_unit, tmp->name);
    tmp->name = varname;
    tmp->val_u.intval = intval;
  }
}

void
set_var_str(const Unit_t *unit, const char *varname, const char *strval)
{
  Var_t *tmp;

  assert(unit != NULL && unit->name != NULL && varname != NULL);
  tmp = find_var(unit, varname);
  if (tmp == NULL) {
    tmp = (Var_t *)mem_alloc(&load_unit, sizeof(Var_t));
    tmp->unit = unit;
    tmp->name = varname;
    tmp->val_u.strval = strval;
    tmp->type = VT_STR;
    add_var(tmp);
  } else {
    assert(tmp->type == VT_STR);
    mem_free(&load_unit, tmp->name);
    tmp->name = varname;
    mem_free(&load_unit, tmp->val_u.strval);
    tmp->val_u.strval = strval;
  }
}


void
set_var_addr(const Unit_t *unit, const char *varname, const AtmAddr_t *addr)
{
  Var_t *tmp;

  assert(unit != NULL && unit->name != NULL && varname != NULL);
  tmp = find_var(unit, varname);
  if (tmp == NULL) {
    tmp = (Var_t *)mem_alloc(&load_unit, sizeof(Var_t));
    tmp->unit = unit;
    tmp->name = varname;
    tmp->val_u.addrval = addr;
    tmp->type = VT_ADDR;
    add_var(tmp);
  }
  else {
    assert(tmp->type == VT_ADDR);
    mem_free(&load_unit, tmp->name);
    tmp->name = varname;
    mem_free(&load_unit, tmp->val_u.addrval);
    tmp->val_u.addrval = addr;
  }
}

void
set_var_bool(const Unit_t *unit, const char *varname, Bool_t boolval)
{
  Var_t *tmp;

  assert(unit != NULL && unit->name != NULL && varname != NULL);
  tmp = find_var(unit, varname);
  if (tmp == NULL) {
    tmp = (Var_t *)mem_alloc(&load_unit, sizeof(Var_t));
    tmp->unit = unit;
    tmp->name = varname;
    tmp->val_u.boolval = boolval;
    tmp->type = VT_BOOL;
    add_var(tmp);
  }
  else {
    mem_free(&load_unit, tmp->name);
    tmp->name = varname;
    assert(tmp->type == VT_BOOL);
    tmp->val_u.boolval = boolval;
  }
}

void
dump_vars(const Unit_t *unit)
{
  const VarList_t *tmp;
  LaneDestList_t *ltmp;

  Debug_unit(&load_unit, "Dumping variables");
  for (tmp = varlist; tmp != NULL; tmp = tmp->next) {
    assert(tmp->var != NULL);
    assert(tmp->var->unit != NULL);
    assert(tmp->var->unit->name != NULL);
    assert(tmp->var->name != NULL);
    if (unit == NULL || strcmp(unit->name, tmp->var->unit->name) == 0) {
      switch (tmp->var->type) {
      case VT_INT:
	Debug_unit(&load_unit, "%s/%s = %d", tmp->var->unit->name, tmp->var->name, tmp->var->val_u.intval);
	break;
      case VT_STR:
	Debug_unit(&load_unit, "%s/%s = \"%s\"", tmp->var->unit->name, tmp->var->name, tmp->var->val_u.strval);
	break;
      case VT_BOOL:
	Debug_unit(&load_unit, "%s/%s = %s", tmp->var->unit->name, tmp->var->name, tmp->var->val_u.boolval == BL_TRUE? "True" : "False" );
	break;
      case VT_ADDR:
	Debug_unit(&load_unit, "%s/%s =", tmp->var->unit->name, tmp->var->name);
	dump_atmaddr(tmp->var->val_u.addrval);
	break;
      case VT_PVC:
	Debug_unit(&load_unit,"%s/%s = %d,%d,%d with lecid:%d ",
		   tmp->var->unit->name, 
		   tmp->var->name, 
		   tmp->var->val_u.init->pvc->port,
		   tmp->var->val_u.init->pvc->vpi,
		   tmp->var->val_u.init->pvc->vci,
		   tmp->var->val_u.init->lecid);
	dump_atmaddr(tmp->var->val_u.init->address);
	ltmp = tmp->var->val_u.init->destinations;
	while(ltmp) {
	  dump_printf(EL_CONT,"\t");
	  dump_addr(ltmp->addr);
	  dump_printf(EL_CONT,"\n");
	  ltmp = ltmp->next;
	}
	break;
      }
    }
  }
}

void
load_vars(const char *file)
{
  const Unit_t *curr_unit = NULL;

  int ret = 0;   /* to silence gcc 2.7.2.1 */
  char *varname;
  InitPvc_t *pvc;
  LaneDestList_t *ltmp;
  int read_flag = 1;

  assert(file != NULL);
  Debug_unit(&load_unit, "Loading variables from file %s", file);
  yyin = fopen(file, "r");
  if (!yyin) {
    Debug_unit(&load_unit, "Cannot open file %s: %s", file, strerror(errno));
    return;
  }
  g_buf_index = 0;
  do {
    if (read_flag)
      ret = yylex();
    else
      read_flag =1;
    switch(ret) {
    case END:
      Debug_unit(&load_unit, "EOF");
      break;
    case UNIT:
      Debug_unit(&load_unit, "Got unit %s", g_return.stringgi);
      curr_unit = find_unit(g_return.stringgi);
      if (curr_unit == NULL) {
	Debug_unit(&load_unit, "Unknown unit %s", g_return.stringgi);
      }
      Debug_unit(&load_unit, "Got unit %s", g_return.stringgi);
      mem_free(&load_unit,g_return.stringgi);
      break;
    case VARNAME:
      varname = g_return.stringgi;
      Debug_unit(&load_unit, "Got variable name %s", varname);
      ret = yylex();
      switch(ret) {
      case STRING:
	Debug_unit(&load_unit, "Variable is string: %s", g_return.stringgi);
	set_var_str(curr_unit, varname, g_return.stringgi);
	break;
      case BOOLEAN:
	Debug_unit(&load_unit, "Variable is boolean: %s", 
		   g_return.bool==BL_TRUE?"True":"False");
	set_var_bool(curr_unit, varname, g_return.bool);
	break;
      case INTEGER:
	Debug_unit(&load_unit, "Variable is integer: %d", g_return.intti);
	set_var_int(curr_unit, varname, g_return.intti);
	break;
      case ATMADDRESS:
	Debug_unit(&load_unit, "Variable is atmaddress ");
	dump_atmaddr(g_return.atmaddress);
	set_var_addr(curr_unit, varname, g_return.atmaddress);
	break;
      case LANEDEST:
	Debug_unit(&load_unit, "Invalid variable value for %s", varname);
	mem_free(&load_unit, g_return.destaddr);
	break;
      case UNIT:
	Debug_unit(&load_unit, "Invalid variable value for %s", varname);
	mem_free(&load_unit, g_return.stringgi);
	break;
      case VCC:
	Debug_unit(&load_unit, "Variable is vcc");
	pvc = (InitPvc_t *)mem_alloc(curr_unit, sizeof(InitPvc_t));
	pvc->pvc = (LaneVcc_t *)mem_alloc(curr_unit, sizeof(LaneVcc_t));
	pvc->pvc->port = g_return.vcc.port;
	pvc->pvc->vpi = g_return.vcc.vpi;
	pvc->pvc->vci = g_return.vcc.vci;
	pvc->address = NULL;
	pvc->lecid = 0;
	pvc->destinations = NULL;
	ret = yylex();
	if (ret != ATMADDRESS) {
	  Debug_unit(&load_unit, "Invalid atm_address for pvc %d,%d,%d",
		     pvc->pvc->port, pvc->pvc->vpi, pvc->pvc->vci);
	  switch(ret) {
	  case UNIT:
	  case STRING:
	  case VARNAME:
	    mem_free(&load_unit, g_return.stringgi);
	    break;
	  case LANEDEST:
	    mem_free(&load_unit, g_return.destaddr);
	    break;
	  }
	} else {
	  pvc->address = g_return.atmaddress;
	}
	ret = yylex();
	if (ret != INTEGER) {
	  Debug_unit(&load_unit, "Invalid lecid for pvc %d,%d,%d\n",
		     pvc->pvc->port,pvc->pvc->vpi,pvc->pvc->vci);
	  switch(ret) {
	  case UNIT:
	  case STRING:
	  case VARNAME:
	    mem_free(&load_unit, g_return.stringgi);
	    break;
	  case LANEDEST:
	    mem_free(&load_unit, g_return.destaddr);
	    break;
	  case ATMADDRESS:
	    mem_free(&load_unit, g_return.atmaddress);
	    break;
	  }
	} else {
	  pvc->lecid = g_return.intti;
	}
	while((ret=yylex())==LANEDEST) {
	  ltmp=(LaneDestList_t *)mem_alloc(&load_unit, sizeof(LaneDestList_t));
	  ltmp->addr = g_return.destaddr;
	  ltmp->next = pvc->destinations;
	  pvc->destinations = ltmp;
	}
	read_flag=0;
	set_var_vcc(curr_unit, varname, pvc);
	break;	
      default:
	Debug_unit(&load_unit, "Invalid variable value for %s", varname);
	break;
      }
      break;
    case STRING:
      Debug_unit(&load_unit,"Invalid string placement %s",g_return.stringgi);
      mem_free(&load_unit, g_return.stringgi);
      break;
    case ATMADDRESS:
      Debug_unit(&load_unit, "Invalid atm address placement");
      mem_free(&load_unit, g_return.atmaddress);
      break;
    case LANEDEST:
      Debug_unit(&load_unit, "Invalid lane destination placement");
      mem_free(&load_unit, g_return.destaddr);
      break;
    case INTEGER:
      Debug_unit(&load_unit, "Invalid integer placement");
      break;
    default:
      Debug_unit(&load_unit, "Invalid input");
      break;
    }
  } while (ret != END);
  if (fclose(yyin) != 0) {
    Debug_unit(&load_unit, "Cannot close file %s: %s", file, strerror(errno));
  }
}

