#include <sys/types.h>
#include <unistd.h>
#include "strerr.h"
#include "cdb.h"
#include "buffer.h"
#include "seek.h"
#include "fmt.h"

#define USAGE ""
#define FATAL "cdbstats: fatal: "

#define BUFSIZE 1024

const char *progname;
static struct cdb c;
char buf[BUFSIZE];

void usage() { strerr_die4x(100, "usage: ", progname, USAGE, "\n"); }

void getb(char *b, unsigned int len) {
  unsigned int l;

  while (len) {
    switch ((l =buffer_get(buffer_0, b, len))) {
    case 0: strerr_die2x(111, FATAL, "unable to read input: truncated file");
    case -1: strerr_die2sys(111, FATAL, "unable to read input: ");
    }
    len -=l; b +=l;
  }
}
void put1(char *s, unsigned int l) {
  if (buffer_put(buffer_1, s, l) == -1)
    strerr_die2sys(111, FATAL, "unable to write output: ");
}

int main(int argc, char **argv) {
  uint32 pos;
  uint32 eodata;
  uint32 keylen;
  uint32 datalen;
  char num[FMT_ULONG];
  unsigned long records;
  unsigned long distance[11];
  off_t rest;
  int i, j;

  progname =*argv++;
  if (*argv) usage();
  pos =0;
  records =0;
  for (i =0; i < 11; ++i) distance[i] =0;

  cdb_init(&c,0);

  /* read data size */
  getb(buf, 4);
  pos +=4;
  uint32_unpack(buf, &eodata);
  /* data starts after 2048 bytes */
  while (pos < 2048) {
    getb(buf, (2048 -pos) < BUFSIZE ? (2048 -pos) : BUFSIZE);
    pos +=(2048 -pos) < BUFSIZE ? (2048 -pos) : BUFSIZE;
  }
  while (pos < eodata) {
    /* keylen */
    getb(buf, 4);
    pos +=4;
    uint32_unpack(buf, &keylen);
    /* datalen */
    getb(buf, 4);
    pos +=4;
    uint32_unpack(buf, &datalen);

    /* skip keys with len > BUFSIZE */
    if (keylen > BUFSIZE) {
      while (keylen) {
	getb(buf, keylen < BUFSIZE ? keylen : BUFSIZE);
	pos +=keylen < BUFSIZE ? keylen : BUFSIZE;
	keylen -=keylen < BUFSIZE ? keylen : BUFSIZE;
      }
    }
    /* lookup records */
    else {
      /* get keylen */
      getb(buf, keylen);
      pos +=keylen;
      /* save file pos */
      rest =lseek(0, (off_t) 0, SEEK_CUR);
      /* lookup this record */
      cdb_findstart(&c);
      do {
        switch(cdb_findnext(&c, buf, keylen)) {
	case -1: strerr_die2sys(111, FATAL, "unable to read input: ");
	case 0: strerr_die2x(111,FATAL,"unable to read input: truncated file");
        }
      } while (cdb_datapos(&c) != pos);
      if (!c.loop)
	strerr_die2x(111,FATAL,"unable to read input: truncated file");
      /* count records */
      ++records;
      /* count records with specific distance */
      if (c.loop > 10) ++distance[10]; else ++distance[c.loop -1];
      /* reset file pos */
      if (seek_set(0, rest) == -1)
	strerr_die2sys(111, FATAL, "unable to read input: ");
    }
    /* skip data */
    while (datalen) {
      getb(buf, datalen < BUFSIZE ? datalen : BUFSIZE);
      pos +=datalen < BUFSIZE ? datalen : BUFSIZE;
      datalen -=datalen < BUFSIZE ? datalen : BUFSIZE;
    }
  }
  /* format output stats */
  put1("records ", 8);
  for (i =fmt_ulong(0, records); i < 10; ++i)
    put1(" ", 1);
  put1(num, fmt_ulong(num, records));
  put1("\n", 1);
  for (j =0; j < 10; ++j) {
    put1("d", 1);
    put1(num, fmt_ulong(num, j));
    put1("      ", 6);
    for (i =fmt_ulong(0, distance[j]); i < 10; ++i)
      put1(" ", 1);
    put1(num, fmt_ulong(num, distance[j]));
    put1("\n", 1);
  }
  put1(">9      ", 8);
  for (i =fmt_ulong(0, distance[j]); i < 10; ++i)
    put1(" ", 1);
  put1(num, fmt_ulong(num, distance[j]));
  put1("\n", 1);
  if (buffer_flush(buffer_1) == -1)
    strerr_die2sys(111, FATAL, "unable to flush output: ");
  _exit(0);
}
