#include <stdio.h>
#include <unistd.h>
#include "strerr.h"
#include "error.h"
#include "cdb.h"
#include "cdb_make.h"
#include "open.h"
#include "buffer.h"
#include "stralloc.h"
#include "str.h"
#include "byte.h"
#include "dns.h"
#include "ip4.h"
#include "case.h"
#include "seek.h"
#include "tai.h"
#include "env.h"
#include "scan.h"

#define USAGE " add|remove fqdn ..."
#define FATAL "tinydyndns-data: fatal: "
#define WARNING "tinydyndns-data: warning: "
#define INFO "tinydyndns-data: info: "

char *progname;

void usage () { strerr_die4x(1, "usage: ", progname, USAGE, "\n"); }
void die_nomem() { strerr_die2x(111, FATAL, "out of memory."); }
void fatal(const char *m1, const char *m2) {
  strerr_die4sys(111, FATAL, m1, m2, ": ");
}
void warn(const char *m1, const char *m2) {
  strerr_warn3(WARNING, m1, m2, 0);
}
void info(const char *m1, const char *m2) {
  buffer_puts(buffer_1, INFO);
  buffer_puts(buffer_1, m1);
  buffer_puts(buffer_1, m2);
  buffer_putsflush(buffer_1, "\n");
}

unsigned int add =0;
unsigned int rem =0;
unsigned long ttl;
unsigned long ttd;
const char *ip;
const char *loc;

int rc =0;

int fdcdb;
int fdtmp;
stralloc sa ={0};
static char *host;
struct cdb_make c;
struct cdb cdb;
char **fqdn;
uint32 pos =0;

void get(buffer *b, char *buf, unsigned int l) {
  int r;
  while (l > 0) {
    r =buffer_get(b, buf, l);
    if (r == -1) fatal("unable to read data.cdb", 0);
    if (r == 0)
      strerr_die2x(111, FATAL, "unable to read data.cdb: truncated file");
    buf +=r;
    l -=r;
  }
}

int main(int argc, char ** argv) {
  buffer bcdb;
  char bcdbspace[4096];
  char buf[8];
  char ipbuf[4];
  uint32 eod;
  uint32 klen;
  uint32 dlen;
  stralloc key ={0};
  char *s;
  struct tai tt, plus;

  progname =*argv++;

  if (! *argv) usage();
  switch (**argv) {
  case 'a': /* add */
    add =1;
    break;
  case 'r': /* remove */
    rem =1;
    break;
  default:
    usage();
  }
  if (! *++argv) usage();

  if ((s =env_get("TTL"))) scan_ulong(s, &ttl);
  if ((s =env_get("TTD"))) scan_ulong(s, &ttd);
  if (! (ip =env_get("IP"))) ip ="127.14.14.14";
  if ((loc =env_get("LOC"))) if (! loc[0] || ! loc[1]) loc =0;

  /* open data.cdb */
  if ((fdcdb =open_read("data.cdb")) == -1)
    fatal("unable to open data.cdb", 0);
  buffer_init(&bcdb, buffer_unixread, fdcdb, bcdbspace, sizeof bcdbspace);

  /* lock data.cdb */

  /* open data.tmp */
  if ((fdtmp =open_trunc("data.tmp")) == -1)
    fatal("unable to open data.tmp", 0);
  if (cdb_make_start(&c, fdtmp) == -1)
    fatal("unable to create ok.cdb.tmp", 0);

  /* size of database */
  get(&bcdb, buf, 4);
  pos +=4;
  uint32_unpack(buf, &eod);

  while (pos < 2048) {
    get(&bcdb, buf, 4);
    pos +=4;
  }
  while (pos < eod) {
    get(&bcdb, buf, 4);
    pos +=4;
    uint32_unpack(buf, &klen);
    get(&bcdb, buf, 4);
    pos +=4;
    uint32_unpack(buf, &dlen);

    if (! stralloc_ready(&sa, klen +dlen)) die_nomem();
    get(&bcdb, sa.s, klen +dlen);
    pos +=klen +dlen;
    if (rem) {
      int skip =0;

      for (fqdn =argv; *fqdn; ++fqdn) {
	if (! dns_domain_fromdot(&host, *fqdn, str_len(*fqdn))) die_nomem();
	case_lowerb(host, dns_domain_length(host));
	if (byte_equal(host, dns_domain_length(host), sa.s)) {
	  info("remove: ", *fqdn);
	  skip =1;
	  rc--;
	  break;
	}
      }
      if (skip) continue;
    }

    if (cdb_make_add(&c, sa.s, klen, sa.s +klen, dlen) == -1)
      fatal("unable to write data.tmp", 0);
  }

  if (add) {
    seek_begin(fdcdb);
    cdb_init(&cdb, fdcdb);
    if (! ip4_scan(ip, ipbuf))
      strerr_die3x(100, FATAL, "unable to scan IP address: ", ip);
    for (fqdn =argv; *fqdn; ++fqdn) {
      if (! dns_domain_fromdot(&host, *fqdn, str_len(*fqdn))) die_nomem();
      case_lowerb(host, dns_domain_length(host));
      if (cdb_find(&cdb, host, dns_domain_length(host)) == 1) {
	warn(*fqdn, ": already in cdb.");
	rc++;
	continue;
      }
      if (! stralloc_copyb(&sa, DNS_T_A, 2)) die_nomem();
      if (loc) {
	if (! stralloc_append(&sa, ">")) die_nomem();
	if (! stralloc_catb(&sa, loc, 2)) die_nomem();
      }
      else
	if (! stralloc_append(&sa, "=")) die_nomem();
      if (! ttd && ! ttl) ttl =5;
      uint32_pack_big(buf, ttl);
      if (! stralloc_catb(&sa, buf, 4)) die_nomem(); /* ttl */
      if (ttd) {
	tai_now(&tt);
	tai_uint(&plus, ttd);
	tai_add(&tt, &tt, &plus);
	tai_pack(buf, &tt);
	if (! stralloc_catb(&sa, buf, 8)) die_nomem();
      }
      else
	if (! stralloc_catb(&sa, "\0\0\0\0\0\0\0\0", 8)) die_nomem(); /* ttd */
      if (! stralloc_catb(&sa, ipbuf, 4)) die_nomem();
      if (! stralloc_copyb(&key, host, dns_domain_length(host))) die_nomem();
      if (cdb_make_add(&c, key.s, key.len, sa.s, sa.len) == -1)
	fatal("unable to write data.tmp", 0);
      info("add: ", *fqdn);
    }
    cdb_free(&cdb);
  }
  close(fdcdb);

  if (cdb_make_finish(&c) == -1) fatal("unable to write data.tmp", 0);
  if (fsync(fdtmp) == -1) fatal("unable to write data.cdb.tmp", 0);
  close(fdtmp);
  if (rename("data.tmp","data.cdb") == -1)
    fatal("unable to move data.tmp to data.cdb", 0);

  if (rem)
    for (fqdn =argv; *fqdn; ++fqdn)
      rc++;
  /*  info("done", ""); */
  if (rc > 100) rc =100;
  _exit(rc);
}
