#include <sys/stat.h>
#include <unistd.h>

#include <stdio.h>  // rename

#include "buffer.h"
#include "byte.h"
#include "env.h"
#include "genalloc.h"
#include "getln.h"
#include "logmsg.h"
#include "open.h"
#include "stralloc.h"

#include "auto_qmail.h"
#include "control.h"
#include "token822.h"

#define WHO "newinclude"

void nomem()
{
  logmsg(WHO, 111, FATAL, "out of memory");
}

void usage()
{
  logmsg(WHO, 100, USAGE, "newinclude list");
}

char *fnlist;
char listbuf[1024];
buffer bi;

stralloc bin = {0};
#define fnbin bin.s
stralloc tmp = {0};
#define fntmp tmp.s
char tmpbuf[1024];
buffer bt;


void readerr()
{
  logmsg(WHO, 111, FATAL, B("unable to read: ", fnlist));
}

void writeerr()
{
  logmsg(WHO, 111, FATAL, B("unable to write to ", fntmp));
}

static void out(char *s, int len)
{
  if (buffer_put(&bt, s, len) == -1) writeerr();
}

void doincl(buf, len) char *buf;
int len;
{
  if (!len) logmsg(WHO, 111, FATAL, "empty :include: filenames not permitted");
  if (byte_chr(buf, len, '\n') != len)
    logmsg(WHO, 111, FATAL, "newlines not permitted in :include: filenames");
  if (byte_chr(buf, len, '\0') != len)
    logmsg(WHO, 111, FATAL, "NUL not permitted in :include: filenames");
  if ((buf[0] != '.') && (buf[0] != '/')) out("./", 2);
  out(buf, len);
  out("", 1);
}

void dorecip(buf, len) char *buf;
int len;
{
  if (!len) logmsg(WHO, 111, FATAL, "empty recipient addresses not permitted");
  if (byte_chr(buf, len, '\n') != len)
    logmsg(WHO, 111, FATAL, "newlines not permitted in recipient addresses");
  if (byte_chr(buf, len, '\0') != len)
    logmsg(WHO, 111, FATAL, "NUL not permitted in recipient addresses");
  if (len > 800) logmsg(WHO, 111, FATAL, "addresses must be under 800 bytes");
  if ((buf[len - 1] == ' ') || (buf[len - 1] == '\t'))
    logmsg(WHO, 111, FATAL, "spaces and tabs not permitted at ends of addresses");
  out("&", 1);
  out(buf, len);
  out("", 1);
}


void die_control()
{
  logmsg(WHO, 111, FATAL, "unable to read controls");
}

stralloc me = {0};
stralloc defaulthost = {0};
stralloc defaultdomain = {0};
stralloc plusdomain = {0};

void readcontrols()
{
  int r;
  int fddir;
  char *x;

  fddir = open_read(".");
  if (fddir == -1) logmsg(WHO, 111, FATAL, "unable to open current directory");

  if (chdir(auto_qmail) == -1) logmsg(WHO, 111, FATAL, B("unable to chdir to: ", auto_qmail));

  r = control_readline(&me, "control/me");
  if (r == -1) die_control();
  if (!r)
    if (!stralloc_copys(&me, "me")) nomem();

  r = control_readline(&defaultdomain, "control/defaultdomain");
  if (r == -1) die_control();
  if (!r)
    if (!stralloc_copy(&defaultdomain, &me)) nomem();
  x = env_get("QMAILDEFAULTDOMAIN");
  if (x)
    if (!stralloc_copys(&defaultdomain, x)) nomem();

  r = control_readline(&defaulthost, "control/defaulthost");
  if (r == -1) die_control();
  if (!r)
    if (!stralloc_copy(&defaulthost, &me)) nomem();
  x = env_get("QMAILDEFAULTHOST");
  if (x)
    if (!stralloc_copys(&defaulthost, x)) nomem();

  r = control_readline(&plusdomain, "control/plusdomain");
  if (r == -1) die_control();
  if (!r)
    if (!stralloc_copy(&plusdomain, &me)) nomem();
  x = env_get("QMAILPLUSDOMAIN");
  if (x)
    if (!stralloc_copys(&plusdomain, x)) nomem();

  if (fchdir(fddir) == -1) logmsg(WHO, 111, FATAL, "unable to set current directory");
}

stralloc cbuf = {0};
token822_alloc toks = {0};
token822_alloc tokaddr = {0};
stralloc address = {0};

void gotincl()
{
  token822_reverse(&tokaddr);
  if (token822_unquote(&address, &tokaddr) != 1) nomem();
  tokaddr.len = 0;
  doincl(address.s, address.len);
}

void gotaddr()
{
  int i;
  int j;
  int flaghasat;

  token822_reverse(&tokaddr);
  if (token822_unquote(&address, &tokaddr) != 1) nomem();

  flaghasat = 0;
  for (i = 0; i < tokaddr.len; ++i)
    if (tokaddr.t[i].type == TOKEN822_AT) flaghasat = 1;

  tokaddr.len = 0;

  if (!address.len) return;

  if (!flaghasat)
    if (address.s[0] == '/') {
      if (!stralloc_0(&address)) nomem();
      logmsg(WHO, 111, FATAL, B("file delivery for ", address.s, " not supported"));
    }
  if (!flaghasat)
    if (address.s[0] == '|') {
      if (!stralloc_0(&address)) nomem();
      logmsg(WHO, 111, FATAL, B("program delivery for ", address.s, " not supported"));
    }

  if (!flaghasat) {
    if (!stralloc_cats(&address, "@")) nomem();
    if (!stralloc_cat(&address, &defaulthost)) nomem();
  }
  if (address.s[address.len - 1] == '+') {
    address.s[address.len - 1] = '.';
    if (!stralloc_cat(&address, &plusdomain)) nomem();
  }
  j = 0;
  for (i = 0; i < address.len; ++i)
    if (address.s[i] == '@') j = i;
  for (i = j; i < address.len; ++i)
    if (address.s[i] == '.') break;
  if (i == address.len) {
    if (!stralloc_cats(&address, ".")) nomem();
    if (!stralloc_cat(&address, &defaultdomain)) nomem();
  }

  dorecip(address.s, address.len);
}


stralloc line = {0};
int match;

void parseerr()
{
  if (!stralloc_0(&line)) nomem();
  logmsg(WHO, 111, FATAL, B("unable to parse this line: ", line.s));
}

void parseline()
{
  int wordok;
  struct token822 *t;
  struct token822 *beginning;

  switch (token822_parse(&toks, &line, &cbuf)) {
    case -1: nomem();
    case 0:  parseerr();
  }

  beginning = toks.t;
  t = toks.t + toks.len;
  wordok = 1;

  if (!token822_readyplus(&tokaddr, 1)) nomem();
  tokaddr.len = 0;

  while (t > beginning) {
    switch ((--t)->type) {
      case TOKEN822_SEMI: break; /*XXX*/
      case TOKEN822_COLON:
        if (t >= beginning + 2)
          if (t[-2].type == TOKEN822_COLON)
            if (t[-1].type == TOKEN822_ATOM)
              if (t[-1].slen == 7)
                if (!byte_diff(t[-1].s, 7, "include")) {
                  gotincl();
                  t -= 2;
                }
        break; /*XXX*/
      case TOKEN822_RIGHT:
        if (tokaddr.len) gotaddr();
        while ((t > beginning) && (t[-1].type != TOKEN822_LEFT))
          if (!token822_append(&tokaddr, --t)) nomem();
        gotaddr();
        if (t <= beginning) parseerr();
        --t;
        while ((t > beginning)
               && ((t[-1].type == TOKEN822_COMMENT) || (t[-1].type == TOKEN822_ATOM)
                   || (t[-1].type == TOKEN822_QUOTE) || (t[-1].type == TOKEN822_AT)
                   || (t[-1].type == TOKEN822_DOT)))
          --t;
        wordok = 0;
        continue;
      case TOKEN822_ATOM:
      case TOKEN822_QUOTE:
      case TOKEN822_LITERAL:
        if (!wordok)
          if (tokaddr.len) gotaddr();
        wordok = 0;
        if (!token822_append(&tokaddr, t)) nomem();
        continue;
      case TOKEN822_COMMENT:
        /* comment is lexically a space; shouldn't affect wordok */
        break;
      case TOKEN822_COMMA:
        if (tokaddr.len) gotaddr();
        wordok = 1;
        break;
      default:
        wordok = 1;
        if (!token822_append(&tokaddr, t)) nomem();
        continue;
    }
  }
  if (tokaddr.len) gotaddr();
}


int main(int argc, char **argv)
{
  int fd;

  umask(033);
  readcontrols();

  fnlist = argv[1];
  if (!fnlist) usage();

  if (!stralloc_copys(&bin, fnlist)) nomem();
  if (!stralloc_cats(&bin, ".bin")) nomem();
  if (!stralloc_0(&bin)) nomem();

  if (!stralloc_copys(&tmp, fnlist)) nomem();
  if (!stralloc_cats(&tmp, ".tmp")) nomem();
  if (!stralloc_0(&tmp)) nomem();

  fd = open_read(fnlist);
  if (fd == -1) readerr();
  buffer_init(&bi, read, fd, listbuf, sizeof(listbuf));

  fd = open_trunc(fntmp);
  if (fd == -1) writeerr();
  buffer_init(&bt, write, fd, tmpbuf, sizeof(tmpbuf));

  for (;;) {
    if (getln(&bi, &line, &match, '\n') == -1) readerr();
    if (!line.len) break;
    if (line.s[0] != '#') parseline();
    if (!match) break;
  }

  if (buffer_flush(&bt) == -1) writeerr();
  if (fsync(fd) == -1) writeerr();
  if (close(fd) == -1) writeerr(); /* NFS stupidity */

  if (rename(fntmp, fnbin) == -1) logmsg(WHO, 111, FATAL, B("unable to move ", fntmp, " to: ", fnbin));

  _exit(0);
}