#include "alloc.h"
#include "stralloc.h"
#include "open.h"
#include "cdbread.h"
#include "byte.h"
#include "fmt.h"
#include "getln.h"
#include "ip.h"
#include "str.h"
#include "ip_bit.h"
#include "rules.h"

stralloc rules_name = {0};
stralloc ipstring = {0};

static struct cdb c;

static int dorule(void (*callback)(char *,unsigned int)) {
  char *data;
  unsigned int datalen;

  switch (cdb_find(&c,rules_name.s,rules_name.len)) {
    case -1: return -1;
    case  0: return 0;
  }

  datalen = cdb_datalen(&c);
  data = alloc(datalen);
  if (!data) return -1;
  if (cdb_read(&c,data,datalen,cdb_datapos(&c)) == -1) {
    alloc_free(data);
    return -1;
  }

  callback(data, datalen);
  alloc_free(data);
  return 1;
}

static int doit(void (*callback)(char *, unsigned int), char *ip, char *host, char *info) {
  int p;
  int r;
  int ipv6 = str_len(ip) - byte_chr(ip,str_len(ip),':');

  if (info) {							/* 1. info@ip */
    if (!stralloc_copys(&rules_name,info)) return -1;
    if (!stralloc_cats(&rules_name,"@")) return -1;
    if (ipv6) {
      if (!ip6_fmt_str(&ipstring,ip))
        if (!stralloc_catb(&rules_name,ipstring.s,ipstring.len)) return -1;
    }
    else
      if (!stralloc_cats(&rules_name,ip)) return -1;
    r = dorule(callback);
    if (r) return r;

    if (host) {							/* 2. info@=host */
      if (!stralloc_copys(&rules_name,info)) return -1;
      if (!stralloc_cats(&rules_name,"@=")) return -1;
      if (!stralloc_cats(&rules_name,host)) return -1;
      r = dorule(callback);
      if (r) return r;
    }
  }

  if (ipv6) {							/* 3. IPv6/IPv4 */
    if (!ip6_fmt_str(&ipstring,ip)) {		
        if (!stralloc_copyb(&rules_name,ipstring.s,ipstring.len)) return -1;
        r = dorule(callback);
        if (r) return r;
    }
  } else {
    if (!stralloc_copys(&rules_name,ip)) return -1;
    r = dorule(callback);
    if (r) return r;
  }

  if (host) {							/* 4. =host */
    if (!stralloc_copys(&rules_name,"=")) return -1;
    if (!stralloc_cats(&rules_name,host)) return -1;
    r = dorule(callback);
    if (r) return r;
  }

  if (!ipv6) {							/* 5. IPv4 class-based */
    if (!stralloc_copys(&rules_name,ip)) return -1;
    while (rules_name.len > 0) {
      if (ip[rules_name.len - 1] == '.') {
        r = dorule(callback);
        if (r) return r;
      }
      --rules_name.len;
    } 
  }

  if (ipv6) {							/* 6. IPv6/IPv4 CIDR */
    if (!ip6_bitstring(&ipstring,ip,128)) {
      for (p = 129; p > 1; p--) {
        if (!stralloc_copys(&rules_name,"^")) return -1;
        if (!stralloc_catb(&rules_name,ipstring.s,p)) return -1;
        r = dorule(callback);
        if (r) return r;
      }
    }
  } else {
    if (!ip4_bitstring(&ipstring,ip,32)) {	
      for (p = 33; p > 1; p--) {
        if (!stralloc_copys(&rules_name,"_")) return -1;
        if (!stralloc_catb(&rules_name,ipstring.s,p)) return -1;
        r = dorule(callback);
        if (r) return r;
      }
    }
  } 

  if (host) {							/* 7. =host. */
    while (*host) {
      if (*host == '.') {
        if (!stralloc_copys(&rules_name,"=")) return -1;
        if (!stralloc_cats(&rules_name,host)) return -1;
        r = dorule(callback);
        if (r) return r;
      }
      ++host;
    }
    if (!stralloc_copys(&rules_name,"=")) return -1;		/* 8. = rule */
    r = dorule(callback);
    if (r) return r;
  } 

  rules_name.len = 0;
  return dorule(callback);
}

int rules(void (*callback)(char *,unsigned int), int fd, char *ip, char *host, char *info) {
  int r;

  cdb_init(&c,fd);
  r = doit(callback,ip,host,info);
  cdb_free(&c);
  return r;
}
