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

#include <stdio.h>

#include "buffer.h"
#include "env.h"
#include "exit.h"
#include "genalloc.h"
#include "getln.h"
#include "lock.h"
#include "logmsg.h"
#include "open.h"
#include "str.h"
#include "stralloc.h"

#include "gfrom.h"
#include "maildir.h"
#include "myctime.h"
#include "prioq.h"

char *mbox;
char *mboxtmp;

stralloc filenames = {0};
prioq pq = {0};
prioq pq2 = {0};

stralloc line = {0};

stralloc ufline = {0};

char inbuf[BUFFER_INSIZE];
char outbuf[BUFFER_OUTSIZE];

#define WHO "maildir2mbox"

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

int main()
{
  buffer bi;
  buffer bo;
  struct prioq_elt pe;
  int fdoldmbox;
  int fdnewmbox;
  int fd;
  int match;
  int fdlock;

  umask(077);

  mbox = env_get("MAIL");
  if (!mbox) logmsg(WHO, 111, FATAL, "MAIL not set");
  mboxtmp = env_get("MAILTMP");
  if (!mboxtmp) logmsg(WHO, 111, FATAL, "MAILTMP not set");

  if (maildir_chdir() == -1) logmsg(WHO, 110, FATAL, "Can't changet maildir");
  maildir_clean(&filenames);
  if (maildir_scan(&pq, &filenames, 1, 1) == -1) logmsg(WHO, 112, FATAL, "Can't read maidir");

  if (!prioq_min(&pq, &pe)) _exit(0); /* nothing new */

  fdlock = open_append(mbox);
  if (fdlock == -1) logmsg(WHO, 111, FATAL, B("unable to lock: ", mbox));
  if (lock_ex(fdlock) == -1) logmsg(WHO, 111, FATAL, B("unable to lock: ", mbox));

  fdoldmbox = open_read(mbox);
  if (fdoldmbox == -1) logmsg(WHO, 112, FATAL, B("unable to read: ", mbox));

  fdnewmbox = open_trunc(mboxtmp);
  if (fdnewmbox == -1) logmsg(WHO, 112, FATAL, B("unable to create: ", mboxtmp));

  buffer_init(&bi, read, fdoldmbox, inbuf, sizeof(inbuf));
  buffer_init(&bo, write, fdnewmbox, outbuf, sizeof(outbuf));

  switch (buffer_copy(&bo, &bi)) {
    case -2: logmsg(WHO, 112, FATAL, B("unable to read: ", mbox));
    case -3: logmsg(WHO, 112, FATAL, B("unable to write to: ", mboxtmp));
  }

  while (prioq_min(&pq, &pe)) {
    prioq_delmin(&pq);
    if (!prioq_insert(&pq2, &pe)) die_nomem();

    fd = open_read(filenames.s + pe.id);
    if (fd == -1) logmsg(WHO, 112, FATAL, B("unable to read: $MAILDIR/", filenames.s + pe.id));
    buffer_init(&bi, read, fd, inbuf, sizeof(inbuf));

    if (getln(&bi, &line, &match, '\n') != 0)
      logmsg(WHO, 112, FATAL, B("unable to read: $MAILDIR/", filenames.s + pe.id));

    if (!stralloc_copys(&ufline, "From XXX ")) die_nomem();
    if (match)
      if (stralloc_starts(&line, "Return-Path: <")) {
        if (line.s[14] == '>') {
          if (!stralloc_copys(&ufline, "From MAILER-DAEMON ")) die_nomem();
        } else {
          int i;
          if (!stralloc_ready(&ufline, line.len)) die_nomem();
          if (!stralloc_copys(&ufline, "From ")) die_nomem();

          for (i = 14; i < line.len - 2; ++i)
            if ((line.s[i] == ' ') || (line.s[i] == '\t'))
              ufline.s[ufline.len++] = '-';
            else
              ufline.s[ufline.len++] = line.s[i];
          if (!stralloc_cats(&ufline, " ")) die_nomem();
        }
      }
    if (!stralloc_cats(&ufline, myctime(pe.dt))) die_nomem();
    if (buffer_put(&bo, ufline.s, ufline.len) == -1)
      logmsg(WHO, 112, FATAL, B("unable to write to: ", mboxtmp));

    while (match && line.len) {
      if (gfrom(line.s, line.len))
        if (buffer_puts(&bo, ">") == -1) logmsg(WHO, 112, FATAL, B("unable to write to: ", mboxtmp));
      if (buffer_put(&bo, line.s, line.len) == -1)
        logmsg(WHO, 112, FATAL, B("unable to write to: ", mboxtmp));
      if (!match) {
        if (buffer_puts(&bo, "\n") == -1) logmsg(WHO, 112, FATAL, B("unable to write to: ", mboxtmp));
        break;
      }
      if (getln(&bi, &line, &match, '\n') != 0)
        logmsg(WHO, 112, FATAL, B("unable to read: $MAILDIR/", filenames.s + pe.id));
    }
    if (buffer_puts(&bo, "\n")) logmsg(WHO, 112, FATAL, B("unable to write to: ", mboxtmp));

    close(fd);
  }

  if (buffer_flush(&bo) == -1) logmsg(WHO, 112, FATAL, B("unable to write to: ", mboxtmp));
  if (fsync(fdnewmbox) == -1) logmsg(WHO, 112, FATAL, B("unable to write to: ", mboxtmp));
  if (close(fdnewmbox) == -1) /* NFS dorks */
    logmsg(WHO, 112, FATAL, B("unable to write to: ", mboxtmp));
  if (rename(mboxtmp, mbox) == -1) logmsg(WHO, 112, FATAL, B("unable to move ", mboxtmp, " to: ", mbox));

  while (prioq_min(&pq2, &pe)) {
    prioq_delmin(&pq2);
    if (unlink(filenames.s + pe.id) == -1)
      logmsg(
          WHO,
          0,
          WARN,
          B("$MAILDIR/", filenames.s + pe.id, " will be delivered twice; unable to unlink"));
  }

  _exit(0);
}