#include <alloca.h>
#include <ctype.h>
#include <fcntl.h>
#include <popt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include "pump.h"

static void parseError(int line, char * message) {
   fprintf(stderr, "error parsing config file at line %d: %s\n", line,
           message);
}

static int readStanza(char ** cfg, struct pumpOverrideInfo * overrideList,
                     struct pumpOverrideInfo * override, int * lineNum) {
   struct pumpOverrideInfo * nextO = override + 1;
   char * start, * end, * next, * rest, * chptr;
   int line;
   int argc;
   char ** argv;
   int num;

   if (!lineNum) {
       line = 1;
       lineNum = &line;
   }

   next = *cfg;
   while (next && *next) {
       start = next;

       while (isspace(*start) && *start != '\n') start++;
       if (*start == '\n') {
           next = start + 1;
           (*lineNum)++;
           continue;
       } else if (*start == '#') {
           next = strchr(start, '\n') + 1;
       }

       end = strchr(start, '\n');
       *end = '\0';
       next = end + 1;

       chptr = start;
       while (!isspace(*chptr) && *chptr) chptr++;
       rest = chptr;
       while (*rest && isspace(*rest)) rest++;
       *chptr = '\0';

       if (!strcmp(start, "device")) {
           if (overrideList != override) {
               parseError(*lineNum, "device directive may not occur inside "
                          "of device specification");
               return 1;
           }

           if (!*rest || *rest == '{') {
               parseError(*lineNum, "device directive must be followed by a "
                          "device name");
               return 1;
           }

           end = rest + 1;
           while (!isspace(*end) && *end != '{' && *end) end++;
           chptr = end;
           while (isspace(*end) && *end) end++;

           if (*end != '{') {
               parseError(*lineNum, "device directive must end with a {");
               return 1;
           }

           end++;
           *chptr = '\0';

           while (*end && isspace(*end)) end++;
           if (*end) {
               parseError(*lineNum, "unexpected information after {");
               return 1;
           }

           *nextO = *override;
           strcpy(nextO->intf.device, rest);

           (*lineNum)++;
           if (readStanza(&next, overrideList, nextO, lineNum)) return 1;
           (*lineNum)--;       /* it'll get incremented again at the end
                                  of this while loop */
           nextO++;
       } else if (!strcmp(start, "}")) {
           if (overrideList == override) {
               parseError(*lineNum,"} may only occur inside of a "
                          "device directive");
               return 1;
           } else if (*rest) {
               parseError(*lineNum, "unexpected information after }");
           }

           *cfg = next;
           (*lineNum)++;

           return 0;
       } else if (!strcmp(start, "timeout")) {
           poptParseArgvString(rest, &argc, &argv);

           if (argc != 1) {
               parseError(*lineNum, "timeout directive expects a "
                          "single argument");
               return 1;
           }

           num = strtol(argv[0], &chptr, 0);
           if (*chptr) {
               parseError(*lineNum, "timeout requires a numeric argument");
               return 1;
           }

           override->timeout = num;
       } else if (!strcmp(start, "retries")) {
           poptParseArgvString(rest, &argc, &argv);

           if (argc != 1) {
               parseError(*lineNum, "retries directive expects a "
                          "single argument");
               return 1;
           }

           num = strtol(argv[0], &chptr, 0);
           if (*chptr) {
               parseError(*lineNum, "retries requires a numeric argument");
               return 1;
           }

           override->numRetries = num;
       } else if (!strcmp(start, "domainsearch")) {
           if (overrideList != override) {
               parseError(*lineNum, "domainsearch directive may not occur "
                          "inside of device specification");
               return 1;
           }

           poptParseArgvString(rest, &argc, &argv);

           if (argc != 1) {
               parseError(*lineNum, "domainsearch directive expects a "
                          "single argument");
               return 1;
           }

           /*
               We don't free this as other configurations may have inherited
               it. This could be the wrong decision, but leak would be tiny
               so why worry?
           */
           override->searchPath = strdup(argv[0]);
           free(argv);
       } else if (!strcmp(start, "nodns")) {
           if (*rest) {
               parseError(*lineNum, "unexpected argument to nodns directive");
               return 1;
           }
           override->flags |= OVERRIDE_FLAG_NODNS;
       } else {
           char * error;

           error = malloc(strlen(start) + 50);
           sprintf(error, "unrecognized directive %s", start);
           parseError(*lineNum, error);
           free(error);

           return 1;
       }

       (*lineNum)++;
   }

   *cfg = next;

   return 0;
}

int readPumpConfig(char * configFile, struct pumpOverrideInfo ** overrides) {
   int fd;
   char * buf, * chptr;
   struct stat sb;
   int count;
   void * orig = *overrides;

   if ((fd = open(configFile, O_RDONLY)) < 0) {
       *overrides = calloc(sizeof(**overrides), 2);
       pumpInitOverride(*overrides);
       close(fd);
       return 0;
   }

   fstat(fd, &sb);
   buf = alloca(sb.st_size + 2);
   if (read(fd, buf, sb.st_size) != sb.st_size) {
       close(fd);
       return 1;
   }
   close(fd);
   buf[sb.st_size] = '\n';
   buf[sb.st_size + 1] = '\0';

   /* this will give us an upper limit anyway */
   count = 1;
   chptr = buf;
   while ((chptr = strchr(chptr, '{'))) {
       chptr++;
       count++;
   }

   *overrides = calloc(sizeof(**overrides), count + 1);
   pumpInitOverride(*overrides);

   count = readStanza(&buf, *overrides, *overrides, NULL);
   if (count) {
       free(*overrides);
       *overrides = orig;
   }

   return count;
};