#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "kerbind.h"
#include "gs_descr.h"

#define SYSVAR_bootdev	(*((short *)0x446UL))


typedef struct {
  void *mem;
  size_t size;
  int n_entries;
  int n_slots;
} GrowBuf;

typedef struct {
  int name_off;
  int val_off;
} Var;


static GrowBuf gvars = {0, sizeof(Var), 0, 0},
	       gdata = {0, sizeof(char), 0, 0};
static char *data = 0;
static Var *vars = 0;

char cfg_filename[160] = "A:\\STIK_CFG\\DEFAULT.CFG";
static char stikdir_dat[] = "A:\\STIK_DIR.DAT";


/* growbuf() -- request space for |new_slots| new entries in the
   GrowBuf |G|, reallocating the buffer if necessary */
static int growbuf(GrowBuf *G, int new_slots)
{
  if (!G->mem) {
    G->mem = (void *)kmalloc(G->size * new_slots);
    if (!G->mem) return 0;
    G->n_slots = new_slots; G->n_entries = 0;
    return 1;
  }
  if (G->n_entries + new_slots <= G->n_slots)
    return 1;

  {
    int n = G->n_slots; void *v;

    while (n < G->n_entries + new_slots)
      n *= 2;
    v = (void *)kmalloc(G->size * n);
    if (!v) return 0;
    memcpy(v, G->mem, G->size * G->n_entries);
    kfree(G->mem);
    G->mem = v; G->n_slots = n;
    return 1;
  }
}
#define GROWBUF(G, slots, var) \
	(growbuf(&(G), slots) ? (((var) = (G).mem), 1) : 0)

/* set_var() -- add variable |name| with value |value| (or change value
   if variable is already present).  Returns 1 on success, 0 on failure. */
static int set_var(char *name, char *value)
{
  int i;

  if (!GROWBUF(gvars, 1, vars) ||
      !GROWBUF(gdata, strlen(name) + strlen(value) + 2, data))
    return 0;

  for (i = 0; i < gvars.n_entries; i++) {
    if (!stricmp(name, data + vars[i].name_off)) {
      vars[i].val_off = gdata.n_entries;
      strcpy(data + gdata.n_entries, value);
      gdata.n_entries += strlen(value) + 1;
      return 1;
    }
  }

  i = gvars.n_entries++;
  vars[i].name_off = gdata.n_entries;
  strcpy(data + gdata.n_entries, name);
  gdata.n_entries += strlen(name) + 1;
  vars[i].val_off = gdata.n_entries;
  strcpy(data + gdata.n_entries, value);
  gdata.n_entries += strlen(value) + 1;
  return 1;
}

/* kill_tws() -- strip trailing whitespace from string */
static void kill_tws(char *s)
{
  register char *t = s + strlen(s);

  while (t > s && isspace(t[-1])) t--;
  *t = '\0';
}


static int get_bootdrive(void)
{
  return (int)SYSVAR_bootdev;
}

static int find_config_file(void)
{
  register long l;
  register int n, fd, boot;
  register char *s;

  /* Should be able to do this without Supexec(), since we're in the kernel */
  boot = get_bootdrive();
  cfg_filename[0] += boot;
  stikdir_dat[0] += boot;

  if ((l = f_open(stikdir_dat, 0)) < 0)
    return 1;
  fd = (int)(short)l;
  if ((n = f_read(fd, sizeof(cfg_filename), cfg_filename)) < 0) {
    f_close(fd);
    c_conws("Error reading STIK_DIR.DAT; using default path\r\n");
    return 1;
  }
  if (n >= sizeof(cfg_filename))
    n = sizeof(cfg_filename) - 1;
  if (!(s = strchr(cfg_filename, '\n')))
    s = cfg_filename + n;
  if (s[-1] == '\r')
    s--;
  *s = 0;
  /* Incompatibility:  The STiK docs imply that you can change the
     config file name via STIK_DIR.DAT, but then say to include only the
     new path.  I'm gonna handle it this way:  if the line read from
     STIK_DIR.DAT has a trailing '/' or '\', assume it's a path and
     append "DEFAULT.CFG"; otherwise, assume it's the whole file name
     and return it untouched. */
  if (s[-1] == '\\' || s[-1] == '/')
    strcpy(s, "DEFAULT.CFG");
  return 1;
}

#define NEXT_LINE()	{while (*s) s++; while (!*s) s++;}

int load_config_file()
{
  char *file_data;
  register char *s, *t;
  unsigned long filesize;
  long l;
  int n, fd;
  struct stat S;

  if (!GROWBUF(gvars, 100, vars) || !GROWBUF(gdata, 5000, data)) {
    c_conws("Cannot get memory for STiK vars\r\n");
    return 0;
  }

  if (!find_config_file()) {	/* Okay, so I'm paranoid... ;) */
    c_conws("Unable to find config file\r\n");
    return 0;
  }
  if (f_xattr(0, cfg_filename, &S)) {
    c_conws("Config file ");
    c_conws(cfg_filename);
    c_conws(" does not exist; using built-in defaults...\r\n");
    
    if (!set_var("ALLOCMEM", "100000")) return 0;
    if (!set_var("GLUESTIK_TRACE", "0")) return 0;
    return 1;
  }

  filesize = S.st_size;
  c_conws("Reading config file ");
  c_conws(cfg_filename);
  c_conws("...\r\n");

  file_data = (char *)kmalloc(filesize + 2);
  if (!file_data) {
    c_conws("Cannot get memory to read config file\r\n");
    return 0;
  }
  file_data[filesize] = file_data[filesize+1] = '\0';

  if ((l = f_open(cfg_filename, 0)) < 0) {
    c_conws("Error opening config file ");
    c_conws(cfg_filename);
    c_conws("\r\n");
    return 0;
  }
  fd = (int)(short)l;

  s = file_data;
  while ((n = f_read(fd, filesize, s)) > 0)
    s += n;
  if (n < 0) {
    c_conws("Error reading config file ");
    c_conws(cfg_filename);
    c_conws("\r\n");
    kfree(file_data);
    return 0;
  }
  f_close(fd);
  *s = 0;

  /* Split off all lines */
  for (s = file_data; s < file_data + filesize; s++) {
    if (*s == '\r' || *s == '\n')
      *s = 0;
  }

  s = file_data;
  while (s < file_data + filesize) {

    /* Skip blank lines and comments */
    while (*s && isspace(*s)) s++;
    if (!*s || *s == '#') {
      NEXT_LINE();
      continue;
    }

    /* If no '=', variable is "true" */
    t = strchr(s, '=');
    if (!t) {
      kill_tws(s);
      t = "1";
      goto setit;
    }

    *t++ = 0;
    kill_tws(s);
    while (*t && isspace(*t)) t++;
    /* Empty value means variable is "false" */
    if (!*t) {
      t = "0";
      goto setit;
    }

    kill_tws(t);
  setit:
    if (set_var(s, t)) { NEXT_LINE(); continue; }

    c_conws("Unable to set variable \"");
    c_conws(s);
    c_conws("\"\r\n");
    kfree(file_data);
    return 0;
  }

  kfree(file_data);
  return 1;
}


char* gs_getvstr(char *var)
{
  register int n;

  if (!stricmp(var, "CDVALID")) {
    LOG(DBG_SYSCALL, "Special case:  getvstr(\"CDVALID\") returns \"0\"");
    return "0";
  }

  for (n = 0; n < gvars.n_entries; n++) {
    if (stricmp(var, data + vars[n].name_off) == 0) {
      LOG(DBG_SYSCALL, "getvstr(\"%s\") returns \"%s\"",
	  var, data + vars[n].val_off);
      return data + vars[n].val_off;
    }
  }

  LOG(DBG_SYSCALL, "getvstr(\"%s\") returns \"0\"", var);
  return "0";
}

long gs_setvstr(char *vs, char *value)
{
  int n;

  n = set_var(vs, value);
  LOG((n ? DBG_SYSCALL : DBG_ERROR), "setvstr(\"%s\", \"%s\") returns %d",
      vs, value, n);
  return n;
}


void cleanup_config()
{
  if (gvars.mem)
    kfree(gvars.mem);
  if (gdata.mem)
    kfree(gdata.mem);
}
