#include "substdio.h"
#include "subfd.h"
#include "strerr.h"
#include "stralloc.h"
#include "open.h"
#include "case.h"
#include "readwrite.h"
#include "cdbmss.h"

#define FATAL "setforward: fatal: "

void usage()
{
  strerr_die1x(100,"setforward: usage: setforward data.cdb data.tmp");
}
void nomem()
{
  strerr_die2x(111,FATAL,"out of memory");
}
void missingsemicolon()
{
  strerr_die2x(100,FATAL,"final instruction must end with semicolon");
}
void extracolon()
{
  strerr_die2x(100,FATAL,"double colons are not permitted");
}
void extracomma()
{
  strerr_die2x(100,FATAL,"commas are not permitted before colons");
}
void nulbyte()
{
  strerr_die2x(100,FATAL,"NUL bytes are not permitted");
}
void longaddress()
{
  strerr_die2x(100,FATAL,"addresses over 800 bytes are not permitted");
}

char *fncdb;
char *fntmp;
int fd;
struct cdbmss cdbmss;
stralloc key = {0};

stralloc target = {0}; /* always initialized; no NUL */
stralloc command = {0}; /* always initialized; no NUL */
stralloc instr = {0}; /* always initialized */

int flagtarget = 0;
/* 0: reading target; command is empty; instr is empty */
/* 1: target is complete; instr still has to be written; reading command */

void writeerr()
{
  strerr_die4sys(111,FATAL,"unable to write to ",fntmp,": ");
}

void doit(prepend,data,datalen)
char *prepend;
char *data;
int datalen;
{
  if (!stralloc_copys(&key,prepend)) nomem();
  if (!stralloc_cat(&key,&target)) nomem();
  case_lowerb(key.s,key.len);
  if (cdbmss_add(&cdbmss,key.s,key.len,data,datalen) == -1)
    writeerr();
}

int getch(ch)
char *ch;
{
  int r;

  r = substdio_get(subfdinsmall,ch,1);
  if (r == -1)
    strerr_die2sys(111,FATAL,"unable to read input: ");
  return r;
}

void main(argc,argv)
int argc;
char **argv;
{
  char ch;
  int r;

  if (!stralloc_copys(&target,"")) nomem();
  if (!stralloc_copys(&command,"")) nomem();
  if (!stralloc_copys(&instr,"")) nomem();

  fncdb = argv[1]; if (!fncdb) usage();
  fntmp = argv[2]; if (!fntmp) usage();

  fd = open_trunc(fntmp);
  if (fd == -1)
    strerr_die4sys(111,FATAL,"unable to create ",fntmp,": ");

  if (cdbmss_start(&cdbmss,fd) == -1) writeerr();

  for (;;) {
    if (!getch(&ch)) goto eof;

    if (ch == '#') {
      while (ch != '\n') if (!getch(&ch)) goto eof;
      continue;
    }

    if (ch == ' ') continue;
    if (ch == '\n') continue;
    if (ch == '\t') continue;

    if (ch == ':') {
      if (flagtarget) extracolon();
      flagtarget = 1;
      continue;
    }

    if ((ch == ',') || (ch == ';')) {
      if (!flagtarget) extracomma();
      if (command.len) {
        if (command.s[0] == '?') {
          doit("?",command.s + 1,command.len - 1);
        }
        else if ((command.s[0] == '|') || (command.s[0] == '!')) {
          if (!stralloc_cat(&instr,&command)) nomem();
          if (!stralloc_0(&instr)) nomem();
        }
        else if ((command.s[0] == '.') || (command.s[0] == '/')) {
          if (!stralloc_cat(&instr,&command)) nomem();
          if (!stralloc_0(&instr)) nomem();
        }
        else {
          if (command.len > 800) longaddress();
          if (command.s[0] != '&')
            if (!stralloc_cats(&instr,"&")) nomem();
          if (!stralloc_cat(&instr,&command)) nomem();
          if (!stralloc_0(&instr)) nomem();
        }
      }

      if (!stralloc_copys(&command,"")) nomem();

      if (ch == ';') {
	if (instr.len)
          doit(":",instr.s,instr.len);

        if (!stralloc_copys(&target,"")) nomem();
        if (!stralloc_copys(&instr,"")) nomem();
        flagtarget = 0;
      }
      continue;
    }

    if (ch == '\\') if (!getch(&ch)) goto eof;
    if (ch == 0) nulbyte();
    if (!stralloc_append(flagtarget ? &command : &target,&ch)) nomem();
  }

  eof:
  if (flagtarget || target.len)
    missingsemicolon();

  if (cdbmss_finish(&cdbmss) == -1) writeerr();
  if (fsync(fd) == -1) writeerr();
  if (close(fd) == -1) writeerr(); /* NFS stupidity */

  if (rename(fntmp,fncdb) == -1)
    strerr_die6sys(111,FATAL,"unable to move ",fntmp," to ",fncdb,": ");
  
  _exit(0);
}
