#include <unistd.h>

#include "alloc.h"
#include "buffer.h"
#include "case.h"
#include "exit.h"
#include "fmt.h"
#include "genalloc.h"
#include "getln.h"
#include "logmsg.h"
#include "readwrite.h"
#include "scan.h"
#include "str.h"
#include "stralloc.h"

#define WHO "matchup"

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

void die_read()
{
  logmsg(WHO, 110, ERROR, "unable to read input: ");
}

void die_write()
{
  logmsg(WHO, 110, ERROR, "unable to write output: ");
}

void die_write5()
{
  logmsg(WHO, 111, FATAL, "unable to write fd 5: ");
}

void out(char *buf, int len)
{
  if (buffer_put(buffer_1, buf, len) == -1) die_write();
}

void outs(char *buf)
{
  if (buffer_puts(buffer_1, buf) == -1) die_write();
}

char buf5[512];
buffer bo5 = BUFFER_INIT(write, 5, buf5, sizeof(buf5));

void out5(char *buf, int len)
{
  if (buffer_put(&bo5, buf, len) == -1) die_write5();
}

void outs5(char *buf)
{
  if (buffer_puts(&bo5, buf) == -1) die_write5();
}

GEN_ALLOC_typedef(ulongalloc, unsigned long, u, len, a);
GEN_ALLOC_ready(ulongalloc, unsigned long, u, len, a, i, n, x, 30, ulongalloc_ready);
GEN_ALLOC_readyplus(ulongalloc, unsigned long, u, len, a, i, n, x, 30, ulongalloc_readyplus);

char strnum[FMT_ULONG];

stralloc pool = {0};
unsigned int poolbytes = 0;

int nummsg = 0;
ulongalloc msg = {0};
ulongalloc bytes = {0};
ulongalloc qp = {0};
ulongalloc uid = {0};
ulongalloc numk = {0};
ulongalloc numd = {0};
ulongalloc numz = {0};
ulongalloc sender = {0};
ulongalloc birth = {0};

int msg_find(unsigned long m)
{
  int i;
  for (i = 0; i < nummsg; ++i)
    if (msg.u[i] == m) return i;
  return -1;
}

int msg_add(unsigned long m)
{
  int i;
  for (i = 0; i < nummsg; ++i)
    if (msg.u[i] == m) return i;
  i = nummsg++;
  if (!ulongalloc_ready(&msg, nummsg)) nomem();
  if (!ulongalloc_ready(&bytes, nummsg)) nomem();
  if (!ulongalloc_ready(&qp, nummsg)) nomem();
  if (!ulongalloc_ready(&uid, nummsg)) nomem();
  if (!ulongalloc_ready(&numk, nummsg)) nomem();
  if (!ulongalloc_ready(&numd, nummsg)) nomem();
  if (!ulongalloc_ready(&numz, nummsg)) nomem();
  if (!ulongalloc_ready(&sender, nummsg)) nomem();
  if (!ulongalloc_ready(&birth, nummsg)) nomem();
  msg.u[i] = m;
  return i;
}

void msg_kill(int i)
{
  poolbytes -= str_len(pool.s + sender.u[i]) + 1;
  poolbytes -= str_len(pool.s + birth.u[i]) + 1;

  --nummsg;
  msg.u[i] = msg.u[nummsg];
  bytes.u[i] = bytes.u[nummsg];
  qp.u[i] = qp.u[nummsg];
  uid.u[i] = uid.u[nummsg];
  numk.u[i] = numk.u[nummsg];
  numd.u[i] = numd.u[nummsg];
  numz.u[i] = numz.u[nummsg];
  sender.u[i] = sender.u[nummsg];
  birth.u[i] = birth.u[nummsg];
}

int numdel = 0;
ulongalloc del = {0};
ulongalloc dmsg = {0};
ulongalloc dchan = {0};
ulongalloc drecip = {0};
ulongalloc dstart = {0};

int del_find(unsigned long d)
{
  int i;
  for (i = 0; i < numdel; ++i)
    if (del.u[i] == d) return i;
  return -1;
}

int del_add(unsigned long d)
{
  int i;
  for (i = 0; i < numdel; ++i)
    if (del.u[i] == d) return i;
  i = numdel++;
  if (!ulongalloc_ready(&del, numdel)) nomem();
  if (!ulongalloc_ready(&dmsg, numdel)) nomem();
  if (!ulongalloc_ready(&dchan, numdel)) nomem();
  if (!ulongalloc_ready(&drecip, numdel)) nomem();
  if (!ulongalloc_ready(&dstart, numdel)) nomem();
  del.u[i] = d;
  return i;
}

void del_kill(int i)
{
  poolbytes -= str_len(pool.s + dchan.u[i]) + 1;
  poolbytes -= str_len(pool.s + drecip.u[i]) + 1;
  poolbytes -= str_len(pool.s + dstart.u[i]) + 1;
  --numdel;
  del.u[i] = del.u[numdel];
  dmsg.u[i] = dmsg.u[numdel];
  dchan.u[i] = dchan.u[numdel];
  drecip.u[i] = drecip.u[numdel];
  dstart.u[i] = dstart.u[numdel];
}

stralloc pool2 = {0};

void garbage()
{
  int i;
  char *x;

  if (pool.len - poolbytes < poolbytes + 4096) return;

  if (!stralloc_copys(&pool2, "")) nomem();

  for (i = 0; i < nummsg; ++i) {
    x = pool.s + birth.u[i];
    birth.u[i] = pool2.len;
    if (!stralloc_cats(&pool2, x)) nomem();
    if (!stralloc_0(&pool2)) nomem();
    x = pool.s + sender.u[i];
    sender.u[i] = pool2.len;
    if (!stralloc_cats(&pool2, x)) nomem();
    if (!stralloc_0(&pool2)) nomem();
  }

  for (i = 0; i < numdel; ++i) {
    x = pool.s + dstart.u[i];
    dstart.u[i] = pool2.len;
    if (!stralloc_cats(&pool2, x)) nomem();
    if (!stralloc_0(&pool2)) nomem();
    x = pool.s + dchan.u[i];
    dchan.u[i] = pool2.len;
    if (!stralloc_cats(&pool2, x)) nomem();
    if (!stralloc_0(&pool2)) nomem();
    x = pool.s + drecip.u[i];
    drecip.u[i] = pool2.len;
    if (!stralloc_cats(&pool2, x)) nomem();
    if (!stralloc_0(&pool2)) nomem();
  }

  if (!stralloc_copy(&pool, &pool2)) nomem();

  poolbytes = pool.len; /* redundant, but doesn't hurt */
}

stralloc line = {0};
int match;

#define FIELDS 20
int field[FIELDS];

void clear()
{
  while (numdel > 0) del_kill(0);
  garbage();
}

void starting()
{
  unsigned long d;
  unsigned long m;
  int dpos;

  scan_ulong(line.s + field[3], &d);
  scan_ulong(line.s + field[5], &m);

  dpos = del_add(d);

  dmsg.u[dpos] = m;

  dstart.u[dpos] = pool.len;
  if (!stralloc_cats(&pool, line.s + field[0])) nomem();
  if (!stralloc_0(&pool)) nomem();

  dchan.u[dpos] = pool.len;
  if (!stralloc_cats(&pool, line.s + field[7])) nomem();
  if (!stralloc_0(&pool)) nomem();

  drecip.u[dpos] = pool.len;
  if (!stralloc_cats(&pool, line.s + field[8])) nomem();
  if (!stralloc_0(&pool)) nomem();
  case_lowers(pool.s + drecip.u[dpos]);

  poolbytes += pool.len - dstart.u[dpos];
}

void delivery()
{
  unsigned long d;
  unsigned long m;
  int dpos;
  int mpos;
  char *result = "?";
  char *reason = "";

  scan_ulong(line.s + field[2], &d);

  dpos = del_find(d);
  if (dpos == -1) return;

  m = dmsg.u[dpos];
  mpos = msg_find(m);

  if (str_start(line.s + field[3], "succ")) {
    if (mpos != -1) ++numk.u[mpos];
    result = "d k ";
    reason = line.s + field[4];
  } else if (str_start(line.s + field[3], "fail")) {
    if (mpos != -1) ++numd.u[mpos];
    result = "d d ";
    reason = line.s + field[4];
  } else if (str_start(line.s + field[3], "defer")) {
    if (mpos != -1) ++numz.u[mpos];
    result = "d z ";
    reason = line.s + field[4];
  } else if (str_start(line.s + field[3], "report")) {
    if (mpos != -1) ++numz.u[mpos];
    result = "d z ";
    reason = "report_mangled";
  }

  outs(result);

  if (mpos != -1) {
    outs(pool.s + birth.u[mpos]);
    outs(" ");
    outs(pool.s + dstart.u[dpos]);
    outs(" ");
    outs(line.s + field[0]);
    outs(" ");
    out(strnum, fmt_ulong(strnum, bytes.u[mpos]));
    outs(" ");
    outs(pool.s + sender.u[mpos]);
    outs(" ");
    outs(pool.s + dchan.u[dpos]);
    outs(".");
    outs(pool.s + drecip.u[dpos]);
    outs(" ");
    out(strnum, fmt_ulong(strnum, qp.u[mpos]));
    outs(" ");
    out(strnum, fmt_ulong(strnum, uid.u[mpos]));
    outs(" ");
    outs(reason);
  } else {
    outs(pool.s + dstart.u[dpos]);
    outs(" ");
    outs(pool.s + dstart.u[dpos]);
    outs(" ");
    outs(line.s + field[0]);
    outs(" 0 ? ");
    outs(pool.s + dchan.u[dpos]);
    outs(".");
    outs(pool.s + drecip.u[dpos]);
    outs(" ? ? ");
    outs(reason);
  }

  outs("\n");

  del_kill(dpos);
  garbage();
}

void newmsg()
{
  unsigned long m;
  int mpos;

  scan_ulong(line.s + field[3], &m);
  mpos = msg_find(m);
  if (mpos == -1) return;
  msg_kill(mpos);
  garbage();
}

void endmsg()
{
  unsigned long m;
  int mpos;

  scan_ulong(line.s + field[3], &m);
  mpos = msg_find(m);
  if (mpos == -1) return;

  outs("m ");
  outs(pool.s + birth.u[mpos]);
  outs(" ");
  outs(line.s + field[0]);
  outs(" ");
  out(strnum, fmt_ulong(strnum, bytes.u[mpos]));
  outs(" ");
  out(strnum, fmt_ulong(strnum, numk.u[mpos]));
  outs(" ");
  out(strnum, fmt_ulong(strnum, numd.u[mpos]));
  outs(" ");
  out(strnum, fmt_ulong(strnum, numz.u[mpos]));
  outs(" ");
  outs(pool.s + sender.u[mpos]);
  outs(" ");
  out(strnum, fmt_ulong(strnum, qp.u[mpos]));
  outs(" ");
  out(strnum, fmt_ulong(strnum, uid.u[mpos]));
  outs("\n");

  msg_kill(mpos);
  garbage();
}

void info()
{
  unsigned long m;
  int mpos;

  scan_ulong(line.s + field[3], &m);
  mpos = msg_add(m);

  scan_ulong(line.s + field[5], &bytes.u[mpos]);
  scan_ulong(line.s + field[9], &qp.u[mpos]);
  scan_ulong(line.s + field[11], &uid.u[mpos]);

  numk.u[mpos] = 0;
  numd.u[mpos] = 0;
  numz.u[mpos] = 0;

  birth.u[mpos] = pool.len;
  if (!stralloc_cats(&pool, line.s + field[0])) nomem();
  if (!stralloc_0(&pool)) nomem();

  sender.u[mpos] = pool.len;
  if (!stralloc_cats(&pool, line.s + field[7])) nomem();
  if (!stralloc_0(&pool)) nomem();
  case_lowers(pool.s + sender.u[mpos]);

  poolbytes += pool.len - birth.u[mpos];
}

void extra()
{
  unsigned long m;
  int mpos;

  scan_ulong(line.s + field[2], &m);
  mpos = msg_find(m);
  if (mpos == -1) return;

  scan_ulong(line.s + field[3], &numk.u[mpos]);
  scan_ulong(line.s + field[4], &numz.u[mpos]);
  scan_ulong(line.s + field[5], &numd.u[mpos]);
}

void pending()
{
  int i;

  for (i = 0; i < nummsg; ++i) {
    outs5(pool.s + birth.u[i]);
    outs5(" info msg ");
    out5(strnum, fmt_ulong(strnum, msg.u[i]));
    outs5(": bytes ");
    out5(strnum, fmt_ulong(strnum, bytes.u[i]));
    outs5(" from ");
    outs5(pool.s + sender.u[i]);
    outs5(" qp ");
    out5(strnum, fmt_ulong(strnum, qp.u[i]));
    outs5(" uid ");
    out5(strnum, fmt_ulong(strnum, uid.u[i]));
    outs5("\n");
    outs5(pool.s + birth.u[i]);
    outs5(" extra ");
    out5(strnum, fmt_ulong(strnum, msg.u[i]));
    outs5(" ");
    out5(strnum, fmt_ulong(strnum, numk.u[i]));
    outs5(" ");
    out5(strnum, fmt_ulong(strnum, numz.u[i]));
    outs5(" ");
    out5(strnum, fmt_ulong(strnum, numd.u[i]));
    outs5("\n");
  }

  for (i = 0; i < numdel; ++i) {
    outs5(pool.s + dstart.u[i]);
    outs5(" starting delivery ");
    out5(strnum, fmt_ulong(strnum, del.u[i]));
    outs5(": msg ");
    out5(strnum, fmt_ulong(strnum, dmsg.u[i]));
    outs5(" to ");
    outs5(pool.s + dchan.u[i]);
    outs5(" ");
    outs5(pool.s + drecip.u[i]);
    outs5("\n");
  }

  out5(line.s, line.len);
  if (buffer_flush(&bo5) == -1) die_write5();
}

stralloc outline = {0};

int main()
{
  int i;
  int j;
  char ch;

  if (!stralloc_copys(&pool, "")) nomem();

  if (!ulongalloc_ready(&msg, 1)) nomem();
  if (!ulongalloc_ready(&bytes, 1)) nomem();
  if (!ulongalloc_ready(&qp, 1)) nomem();
  if (!ulongalloc_ready(&uid, 1)) nomem();
  if (!ulongalloc_ready(&numk, 1)) nomem();
  if (!ulongalloc_ready(&numd, 1)) nomem();
  if (!ulongalloc_ready(&numz, 1)) nomem();
  if (!ulongalloc_ready(&del, 1)) nomem();
  if (!ulongalloc_ready(&dmsg, 1)) nomem();

  for (;;) {
    if (getln(buffer_0, &line, &match, '\n') == -1) die_read();
    if (!match) break;

    if (!stralloc_copy(&outline, &line)) nomem();

    for (i = 0; i < line.len; ++i) {
      ch = line.s[i];
      if ((ch == '\n') || (ch == ' ') || (ch == '\t')) line.s[i] = 0;
    }
    j = 0;
    for (i = 0; i < FIELDS; ++i) {
      while (j < line.len)
        if (line.s[j])
          break;
        else
          ++j;
      field[i] = j;
      while (j < line.len)
        if (!line.s[j])
          break;
        else
          ++j;
    }
    if (!stralloc_0(&line)) nomem();

    if (str_equal(line.s + field[1], "status:"))
      ;
    else if (str_equal(line.s + field[1], "starting"))
      starting();
    else if (str_equal(line.s + field[1], "delivery"))
      delivery();
    else if (str_equal(line.s + field[1], "new"))
      newmsg();
    else if (str_equal(line.s + field[1], "end"))
      endmsg();
    else if (str_equal(line.s + field[1], "info"))
      info();
    else if (str_equal(line.s + field[1], "extra"))
      extra();
    else if (str_equal(line.s + field[1], "running"))
      clear();
    else if (str_equal(line.s + field[1], "exiting"))
      clear();
    else if (str_equal(line.s + field[1], "number"))
      ;
    else if (str_equal(line.s + field[1], "local"))
      ;
    else if (str_equal(line.s + field[1], "remote"))
      ;
    else if (str_equal(line.s + field[1], "warning:"))
      out(outline.s, outline.len);
    else if (str_equal(line.s + field[1], "alert:"))
      out(outline.s, outline.len);
    else {
      outs("? ");
      out(outline.s, outline.len);
    }
  }

  if (buffer_flush(buffer_1) == -1) die_write();

  pending();

  _exit(0);
}