#include <syslog.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "rbldnsd.h"
#include "dns.h"
#include "mempool.h"

struct entry {
  ip4addr_t addr;	/* key */
  ip4addr_t r_a;	/* result: addr */
  const char *r_txt;	/* result: txt (strpool) */
};

struct zonedata {
  ip4addr_t r_a;	/* default A RR */
  const char *r_txt;	/* default txt (strpool) */
  unsigned n[4];	/* counts */
  unsigned a[4];	/* allocated - used only when loading */
  struct entry *e[4];	/* entries */
  struct strpool sptx;	/* strpool for TXT RRs */
};

/* indexes */
#define E32 0
#define E24 1
#define E16 2
#define E08 3

void ip4vset_free(struct zonedata *z) {
  if (z) {
    if (z->e[E32]) free(z->e[E32]);
    if (z->e[E24]) free(z->e[E24]);
    if (z->e[E16]) free(z->e[E16]);
    if (z->e[E08]) free(z->e[E08]);
    sp_free(&z->sptx);
    free(z);
  }
}

static int
ip4vset_parseline(struct zonedata *z, char *line, int lineno, int llines) {
  ip4addr_t a, step, r_a;
  unsigned bits, idx, count;
  struct entry *e;
  char *p;
  const char *r_txt;
  int not;
  if (!llines && line[0] == ':') {
    if (!addrtxt(line, &a, &p)) {
      zlog(LOG_WARNING, lineno, "invalid default entry");
      return 1;
    }
    if (a) z->r_a = a;
    if (*p && (z->r_txt = sp_estrdup(&z->sptx, p)) == NULL)
      return 0;
    return 1;
  }

  if (*line == '!') {
    not = 1;
    do ++line;
    while(*line == ' ' || *line == '\t');
  }
  else
    not = 0;
  for(p = line; *p; ++p)
    if (*p == ' ' || *p == '\t') break;
  if (*p) *p++ = '\0';
  if (!(bits = ip4cidr(line, &a))) {
    zlog(LOG_WARNING, lineno, "invalid address");
    return 1;
  }
  if (not)
    r_a = 0;
  else {
    while(*p == ' ' || *p == '\t') ++p;
    if (!*p)
      r_a = z->r_a;
    else if (!addrtxt(p, &r_a, &p)) {
      zlog(LOG_WARNING, lineno, "invalid value");
      return 1;
    }
    else if (!r_a)
      r_a = z->r_a;
  }

  idx = 3 - (--bits>>3); 
  count = 1 << ((31 - bits) & 7);
  step = 1 << (24 - (bits & 24));
  e = z->e[idx];
#if DEBUG
   printf("%s/%02d: idx=%d step=%08x count=%03d\n",
          ip4atos(a), bits+1, idx, step, count);
#endif

  if (z->n[idx] + count > z->a[idx]) {
    do z->a[idx] = z->a[idx] ? z->a[idx] << 1 : 32;
    while(z->n[idx] + count > z->a[idx]);
    e = (struct entry*)erealloc(e, z->a[idx] * sizeof(*e));
    if (!e) return 0;
    z->e[idx] = e;
  }

  if (not) r_txt = NULL;
  else if (!*p) r_txt = z->r_txt;
  else if (!(r_txt = sp_estrdup(&z->sptx, p))) return 0;

  e += z->n[idx];
  z->n[idx] += count;
  
  for(; count--; a += step, ++e) {
    e->addr = a;
    e->r_a = r_a;
    e->r_txt = r_txt;
  }
  return 1;
}

static int ip4vset_cmpent(const struct entry *a, const struct entry *b) {
  if (a->addr < b->addr) return -1;
  if (a->addr > b->addr) return 1;
  if (a->r_a < b->r_a) return -1;
  if (a->r_a > b->r_a) return 1;
  return 0;
}

struct zonedata *ip4vset_load(FILE *f) {
  int r;
  struct zonedata *z = (struct zonedata *)emalloc(sizeof(*z));
  if (!z) return NULL;
  memset(z, 0, sizeof(*z));
  z->r_a = R_A_DEFAULT;
  if (!readzlines(f, z, ip4vset_parseline)) {
    ip4vset_free(z);
    return NULL;
  }
  for(r = 0; r < 4; ++r) {
    if (!z->n[r]) continue;
    if (z->a[r] != z->n[r])
      z->e[r] = (struct entry*)realloc(z->e[r], z->n[r] * sizeof(struct entry));
    qsort(z->e[r], z->n[r], sizeof(struct entry),
          (int(*)(const void*, const void*))ip4vset_cmpent);
  }
#if 0
  zloaded("e32/24/16/8=%u/%u/%u/%u; %u %u",
    z->n[E32], z->n[E24], z->n[E16], z->n[E08], z->sptx.sp_mp.mp_nallocs ? z->sptx.sp_mp.mp_datasz / z->sptx.sp_mp.mp_nallocs : 0, z->sptx.sp_mp.mp_datasz);
#else	      
  zloaded("e32/24/16/8=%u/%u/%u/%u",
          z->n[E32], z->n[E24], z->n[E16], z->n[E08]);
#endif
  return z;
}

static const struct entry *
ip4vset_find(const struct entry *e, int b, ip4addr_t q) {
  int a = 0, m;
  while(a <= b) {
    m = (a + b) >> 1;
    if (e[m].addr == q) {
      while(m && e[m-1].addr == q) --m;
      return e + m;
    }
    if (e[m].addr < q) a = m + 1;
    else b = m - 1;
  }
  return NULL;
}

int ip4vset_query(const struct zonedata *const z, struct dnspacket *p,
            const unsigned char *const query, unsigned qflg)
{
  ip4addr_t q, f;
  const struct entry *e, *t;
  const char *ipsubst;

  if (!(q = dntoip4addr(query)))
    return 0;

#define try(i,mask) \
 (z->n[i] && \
  (t = z->e[i] + z->n[i], \
   e = ip4vset_find(z->e[i], z->n[i] - 1, (f = q & mask))) != NULL)

  if (!try(E32, 0xffffffff) &&
      !try(E24, 0xffffff00) &&
      !try(E16, 0xffff0000) &&
      !try(E08, 0xff000000))
    return 0;

  if (!e->r_a) return 0;

  ipsubst = (qflg & QUERY_TXT) ? ip4atos(q) : NULL;
  do {
    if (qflg & QUERY_A) addrec_a(p, e->r_a);
    if (qflg & QUERY_TXT && e->r_txt) addrec_txt(p, e->r_txt, ipsubst);
  } while(++e < t && e->addr == f);

  return 1;
}
