#include <stdlib.h>
#include <string.h>

#include <bglibs/iobuf.h>
#include <bglibs/msg.h>
#include <bglibs/wrap.h>
#include <bglibs/str.h>

#include "mailfront.h"

struct pattern
{
  int mode;
  str s;
  const char* message;
};

#define T_AFTER_BLANK ('\\')
#define T_RESPONSE ('=')
#define T_COMMENT ('#')
#define T_HEADER (':')
#define T_NORMAL (0)

static response resp_patmatch = { 554, 0 };
static struct pattern* pattern_list;
static str responses;
static unsigned pattern_count;
static int linemode;
static unsigned linepos;
static unsigned linemax = 256;
static char* linebuf;

static int patterns_read(const char* filename)
{
  ibuf in;
  int mode;
  unsigned i;
  unsigned count;
  str line = {0,0,0};
  const char* currmsg = "This message contains prohibited content";
  if (!ibuf_open(&in, filename, 0)) return 0;
  count = 0;
  /* Count the number of non-comment lines. */
  while (ibuf_getstr(&in, &line, LF)) {
    str_rstrip(&line);
    if (line.len > 0 && line.s[0] != T_COMMENT) {
      if (line.s[0] == T_RESPONSE)
	/* Pre-allocate room for the responses */
	wrap_str(str_catb(&responses, line.s+1, line.len));
      else
	++count;
    }
  }
  responses.len = 0;
  /* Allocate the lines. */
  if ((pattern_list = malloc(count * sizeof *pattern_list)) == 0)
    die_oom(111);
  if (!ibuf_rewind(&in))
    die1sys(111, "Could not rewind patterns file");
  memset(pattern_list, 0, count * sizeof *pattern_list);
  for (i = 0; i < count && ibuf_getstr(&in, &line, LF); ) {
    str_rstrip(&line);
    if (line.len > 0) {
      switch (mode = line.s[0]) {
      case T_COMMENT:
	continue;
      case T_RESPONSE:
	currmsg = responses.s + responses.len;
	str_catb(&responses, line.s+1, line.len);
	continue;
      case T_AFTER_BLANK:
      case T_HEADER:
	break;
      default:
	mode = T_NORMAL;
      }
      pattern_list[i].mode = mode;
      wrap_str(str_copyb(&pattern_list[i].s, line.s+1, line.len-1));
      pattern_list[i].message = currmsg;
      ++i;
    }
  }
  pattern_count = i;
  ibuf_close(&in);
  str_free(&line);
  return 1;
}

static const response* init(int fd)
{
  const char* tmp;
  unsigned u;
  if ((tmp = session_getenv("PATTERNS")) != 0)
    if (!patterns_read(tmp))
      warn3sys("Could not read patterns file '", tmp, "'");
  if ((tmp = session_getenv("PATTERNS_LINEMAX")) != 0)
    if ((u = strtoul(tmp, (char**)&tmp, 10)) > 0 && *tmp == 0)
      linemax = u;
  if ((linebuf = malloc(linemax+1)) == 0)
    die_oom(111);
  linemode = T_HEADER;
  linepos = 0;
  return 0;
  (void)fd;
}

static const response* check_line(void)
{
  unsigned i;
  struct pattern* p;
  const str fakeline = { linebuf, linepos, 0 };
  linebuf[linepos] = 0;
  for (p = pattern_list, i = 0; i < pattern_count; ++i, ++p) {
    if ((p->mode == T_NORMAL
	 || p->mode == linemode)
	&& str_glob(&fakeline, &p->s)) {
      resp_patmatch.message = p->message;
      return &resp_patmatch;
    }
  }
  return 0;
}

static const response* check(const char* bytes, unsigned long len)
{
  const char* p;
  const char* const end = bytes + len;
  const response* r;
  if (linebuf == 0) return 0;
  for (p = bytes; p < end; ++p) {
    const char ch = *p;
    if (ch == LF) {
      if (linepos > 0) {
	if ((r = check_line()) != 0)
	  return r;
	if (linemode != T_HEADER)
	  linemode = T_NORMAL;
      }
      else
	linemode = T_AFTER_BLANK;
      linepos = 0;
    }
    else {
      if (linepos < linemax)
	linebuf[linepos++] = ch;
    }
  }
  return 0;
}

struct plugin plugin = {
  .version = PLUGIN_VERSION,
  .data_start = init,
  .data_block = check,
};
