#include <errno.h>
#include <stdlib.h>
#include <stralloc.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <db.h>
#include <afblib/inbuf.h>
#include <afblib/inbuf_scan.h>
#include <afblib/outbuf.h>
#include <afblib/outbuf_printf.h>

char* pnd_fields[] = {
   "Titel:",
   "Name:",
   "Alternativnamen:",
   "Kurzbeschreibung:",
   "Geburtsdatum:",
   "Geburtsort:",
   "Sterbedatum:",
   "Sterbeort:",
   0
};

int main(int argc, char** argv) {
   outbuf err = {2};

   char* cmdname = *argv++; --argc;
   if (argc != 2) {
      outbuf_printf(&err, "Usage: %s dbfile port\n", cmdname);
      outbuf_flush(&err); exit(1);
   }
   char* dbname = *argv++; --argc;
   int portnum = atoi(*argv++); --argc;
   if (portnum <= 0) {
      outbuf_printf(&err, "%s: bad port number\n", cmdname);
      outbuf_flush(&err); exit(1);
   }

   DB* dbp;
   if (db_create(&dbp, 0, 0) != 0 ||
	 dbp->open(dbp,  /* DB structure pointer */
	     0,          /* transaction pointer */
	     dbname,     /* on-disk file that holds the database */
	     0,          /* optional logical database name */
	     DB_BTREE,   /* database access method */
	     0,          /* open flags */
	     0) != 0) {  /* file mode */
      outbuf_printf(&err, "%s: cannot open %s\n", cmdname, dbname);
      outbuf_flush(&err);
      exit(1);
   }

   struct sockaddr_in address = {0};
   address.sin_family = AF_INET;
   address.sin_addr.s_addr = htonl(INADDR_ANY);
   address.sin_port = htons(portnum);
   int sfd = socket(PF_INET, SOCK_STREAM, 0);
   int optval = 1;
   if (sfd < 0 ||
        setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
                   &optval, sizeof optval) < 0 ||
        bind(sfd, (struct sockaddr *) &address,
             sizeof address) < 0 ||
        listen(sfd, SOMAXCONN) < 0) {
      outbuf_printf(&err, "%s: %s\n", cmdname, strerror(errno));
      exit(1);
   }

   stralloc data_buffer = {0};
   u_int32_t pagesize;
   if (dbp->get_pagesize(dbp, &pagesize) != 0) {
      pagesize = 1024;
   }

   stralloc_ready(&data_buffer, pagesize);
   stralloc key_string = {0};

   int fd;
   while ((fd = accept(sfd, 0, 0)) >= 0) {
      inbuf in = {fd}; outbuf out = {fd};
      if (inbuf_scan(&in, "[ \t]*(.*?)[ \t]*\r?\n", &key_string) == 1) {
	 DBT key = {.data = key_string.s, .size = key_string.len,
	    .flags = DB_DBT_USERMEM};
	 DBT data = {.data = data_buffer.s,
	    .ulen = data_buffer.a, .flags = DB_DBT_USERMEM};
	 int ret;
	 for(;;) {
	    ret = dbp->get(dbp, 0, &key, &data, 0);
	    if (ret == 0) break;
	    if (ret == DB_BUFFER_SMALL) {
	       /* enlarge input buffer */
	       if (!stralloc_ready(&data_buffer, data.size + 1)) {
		  outbuf_printf(&err, "%s: out of memory.\n", cmdname);
		  outbuf_flush(&err); exit(1);
	       }
	       data.ulen = data_buffer.a;
	    } else {
	       outbuf_printf(&err, "%s: db retrieval failed for key '%.*s'.\n",
		  cmdname, key_string.len, key_string.s);
	       outbuf_flush(&err);
	       break;
	    }
	 } 
	 if (ret == 0) {
	    data_buffer.len = data.size;
	    char* cp = data_buffer.s;
	    for (char** fields = pnd_fields; *fields; ++fields) {
	       char* next = cp;
	       while (*next != '\t' && next < data_buffer.s + data_buffer.len) {
		  ++next;
	       }
	       if (next > cp) {
		  outbuf_printf(&out, "%-20s %.*s\r\n", *fields, next-cp, cp);
	       }
	       cp = next+1;
	    }
	 } else {
	    outbuf_printf(&out, "No record found for this key.\r\n");
	 }
	 outbuf_flush(&out);
      }
      close(fd);
   }
}
