#include <unistd.h>
#include <stdio.h>
#include "strerr.h"
#include "error.h"
#include "buffer.h"
#include "open.h"
#include "cdb.h"
#include "cdb_make.h"
#include "fmt.h"

#define USAGE " cdb cdb.tmp"
#define FATAL "cdbmake: fatal: "

const char *progname;
static struct cdb_make c;
char *cdb;
char *tmp;
int entry;
char ch;

void usage() { strerr_die4x(100, "usage: ", progname, USAGE, "\n"); }
void die_write() { strerr_die4sys(111, FATAL, "unable to create ", tmp,": "); }
void die_read(const char *e) {
  char s[FMT_ULONG];

  s[fmt_ulong(s, entry)] =0;
  strerr_die5x(111, FATAL, "unable to read input: entry ", s, ": ", e);
}

static inline void getch() {
  switch(buffer_GETC(buffer_0, &ch)) {
  case 0: die_read("unexpected end if input");
  case -1: die_read(error_str(errno));
  }
}

int main(int argc, char **argv) {
  int fdtmp;
  unsigned int keylen;
  unsigned int datalen;
  uint32 hash;
  int i;

  progname =*argv++;
  if (argc != 3) usage();
  cdb =*argv++;
  if (!*cdb) usage();
  tmp =*argv;
  if (!*tmp) usage();
  
  /* start cdb file */
  if ((fdtmp =open_trunc(tmp)) == -1) die_write();
  if (cdb_make_start(&c, fdtmp) == -1) die_write();

  entry =0;
  for (;;) {
    /* read entry */
    ++entry;
    getch();
    if (ch == '\n') break; /* end of input */
    if (ch != '+') die_read("entry must start with +");
    /* read keylen */
    keylen =0;
    for (;;) {
      getch();
      if (ch == ',') break; /* end of keylen */
      if ((ch < '0') || (ch > '9')) die_read("bad keylen format");
      if (keylen > 429496720) { errno =error_nomem; die_write(); }
      keylen =keylen *10 +(ch -'0');
    }
    /* read datalen */
    datalen =0;
    for (;;) {
      getch();
      if (ch == ':') break; /* end of datalen */
      if ((ch < '0') || (ch > '9')) die_read("bad datalen format");
      if (datalen > 429496720) { errno = error_nomem; die_write(); }
      datalen =datalen *10 +(ch -'0');
    }
    /* start cdb entry */
    if (cdb_make_addbegin(&c, keylen, datalen) == -1) die_write();
    hash =CDB_HASHSTART;
    /* write key */
    for (i =0; i < keylen; ++i) {
      getch(); if (buffer_PUTC(&c.b, ch) == -1) die_write();
      hash =cdb_hashadd(hash, ch);
    }
    getch(); if (ch != '-') die_read("key must be followed by ->");
    getch(); if (ch != '>') die_read("key must be followed by ->");
    /* write data */
    for (i =0; i < datalen; ++i) {
      getch(); if (buffer_PUTC(&c.b, ch) == -1) die_write();
    }
    /* finish cdb entry */
    if (cdb_make_addend(&c, keylen, datalen, hash) == -1) die_write();
    /* next entry */
    getch(); if (ch != '\n') die_read("newline expected");
  }

  /* finish cdb file */
  if (cdb_make_finish(&c) == -1) die_write();
  if ((fsync(fdtmp) == -1) || (close(fdtmp) == -1)) die_write();
  if (rename(tmp, cdb) == -1)
    strerr_die6sys(111, FATAL, "unable to rename ", tmp," to ", cdb, ": ");
  _exit(0);
}
