/*
 *	CUPS Filter Generating Xerox Accounting Attributes
 *
 *	(c) 2016 Martin Mares <mj@ucw.cz>
 */

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

#define PRINTF(i,j) __attribute__((format(printf,i,j)))
#define NONRET __attribute__((noreturn))

#if 0
#define DEBUG(...) debug(__VA_ARGS__)
#else
#define DEBUG(...) do { } while(0)
#endif

static char *job_id;
static char *job_user;
static char *job_title;

/*** Utility functions ***/

static void PRINTF(1,2) NONRET bug(const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);

  fprintf(stderr, "ERROR: ACCT: ");
  vfprintf(stderr, fmt, args);
  fputc('\n', stderr);

  va_end(args);
  exit(1);
}

#define ASSERT(_cond) do { if (!(_cond)) bug("Assertion failed: %s", #_cond); } while (0)

#if 0
static void PRINTF(1,2) error(const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);

  fprintf(stderr, "ERROR: ACCT: ");
  vfprintf(stderr, fmt, args);
  fputc('\n', stderr);

  va_end(args);
}
#endif

#if 0
static void PRINTF(1,2) debug(const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);

  fprintf(stderr, "DEBUG: ACCT: ");
  vfprintf(stderr, fmt, args);
  fputc('\n', stderr);

  va_end(args);
}
#endif

static char *xstrdup(const char *x)
{
  char *p = strdup(x);
  if (!p)
    bug("Out of memory");
  return p;
}

/**
 *  This is our parser of PJL. A hacky one, indeed, but it is expected
 *  to parse only PJL directives generated by our PPD or by CUPS itself,
 *  so we need not pay attention to all obscure details of the language.
 **/

#define LINESIZE 256

static char *skip_spaces(char *p)
{
  while (*p == ' ' || *p == '\t')
    p++;
  return p;
}

static int my_toupper(int c)
{
  if (c >= 'a' && c <= 'z')
    return c - 32;
  else
    return c;
}

static char *token_buf;

static char *get_token(char **pp)
{
  char *token_start = token_buf;
  char *tok = token_start;
  char *p = skip_spaces(*pp);
  if (!*p)
    return NULL;
  int c = my_toupper(*p);
  if (c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_')
    {
      while (c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_')
	{
	  *tok++ = c;
	  c = my_toupper(*++p);
	}
    }
  else
    *tok++ = *p++;
  *tok++ = 0;
  *pp = p;
  token_buf = tok;
  return token_start;
}

static void parse_pjl(void)
{
  static const char uel[] = "\e%-12345X";
  for (int i=0; uel[i]; i++)
    if (getchar() != uel[i])
      bug("PJL error: No UEL found (pos %u)", i);
  printf("\e%%-12345X");

  char line[LINESIZE], tokens[2*LINESIZE];
  for (;;)
    {
      int i = 0;
      for (;;)
	{
	  int c = getchar();
	  if (c < 0)
	    bug("PJL error: Premature EOF");
	  if (i < 4 && c != "@PJL"[i])
	    bug("PJL error: Unrecognized line");
	  if (c == '\r')
	    continue;
	  if (c == '\n')
	      break;
	  if (i >= LINESIZE-1)
	    bug("PJL error: Line too long");
	  line[i++] = c;
	}
      while (i > 0 && (line[i-1] == ' ' || line[i-1] == '\t'))
	i--;
      line[i] = 0;

      DEBUG("PJL: %s", line);
      char *p = line+1;
      token_buf = tokens;
      char *t, *t2;
      if (!(t = get_token(&p)) || strcasecmp(t, "PJL"))
	bug("PJL error: Malformed line");
      if (!(t = get_token(&p)))
	{
	  puts(line);
	  continue;
	}

      if (!strcasecmp(t, "ENTER"))
	{
	  if ((t2 = get_token(&p)) && !strcasecmp(t2, "LANGUAGE"))
	    {
	      puts(line);
	      return;
	    }
	}

      puts(line);
    }
}

static void copy_body(void)
{
  char buf[1024];
  size_t n;

  while (n = fread(buf, 1, sizeof(buf), stdin))
    {
      if (fwrite(buf, 1, n, stdout) != n)
	bug("Write error");
    }
}

/*** Main ***/

int main(int argc, char **argv)
{
  if (argc < 5)
    {
      fprintf(stderr, "Usage: xerox-acct <job> <user> <title> <num-copies> [<options>]\n");
      return 1;
    }
  job_id = xstrdup(argv[1]);
  job_user = xstrdup(argv[2]);
  job_title = xstrdup(argv[3]);

  parse_pjl();
  copy_body();
  return 0;
}