/* BUG?: If a rule is empty ("") the @1.text value is not plugged properly? */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>

#include "taccutil.h"
#include "mmalloc.h"
#include "regexp.h"

#ifdef MEMDEBUG
#include "mnemosyn.h"
#endif

#ifdef strlen
#undef strlen
#endif
int taccutil_slen(const char *a, const int b, const char *c, const char *s)
{
  return(strlen(s));
}
int taccutil_strlen(const char *f, const int l, const char *n, const char *s)
{
  fprintf(stderr, "%s, %d: %s = %p\n", f, l, n, s);
  fprintf(stderr, "  %ld:\"%s\"\n", (long)strlen(s), s);
  if (s == NULL) {
    fprintf(stderr, "ERROR: %s, %d: %s = NULL\n", f, l, n);
    exit(1);
  } else {
    return(strlen(s));
  }
}


#ifndef NULL
#define NULL 0
#endif

#ifndef FALSE
#define FALSE (0!=0)
#define TRUE (0==0)
#endif

int cover_testing = FALSE;
int lev = 0;
int _optimise = FALSE;
int _debug = FALSE;
int _execute_debug = FALSE;

static char exe[256];
char *ExeMassage(char *s)
{
  char *p, *q;
  strcpy(exe, s);
  if ((p = strrchr(exe, '/')) != NULL) {
    p += 1;
  } else if ((p = strrchr(exe, '\\')) != NULL) {
    p += 1;
  } else {
    p = exe;
  }
  if ((q = strchr(p, '.')) != NULL) {
    *q = '\0';
  }
  q = p;
  while (*q != '\0') {
    if (isalpha(*q) && isupper(*q)) *q = tolower(*q);
    q += 1;
  }
  return(p);
}


int debug(const char *fmt, ...)
{
  static char outbuf[256];
  char *s = outbuf;
  va_list arg_ptr;
  int i;
  int c;
  int l = 0;

  if (_debug) {
  for (i = 0; i < lev*3; i++) {
    putc(' ', stderr);
  }
  va_start(arg_ptr,fmt);
  l = vsprintf(outbuf,fmt,arg_ptr);
  va_end(arg_ptr);
  for (;;) {
    c = (*s++)&255;
    if (c == '\0') break;
    if ((c != '\n') && (c < 32)) {
      putc('^', stderr); l += 1;
      putc(c+'A'-1, stderr);
    } else {
      putc(c, stderr);
    }
  }
  putc('\n', stderr);
  }
  return(l);
}

int debug_execute(const char *fmt, ...)
{
  static char outbuf[256];
  char *s = outbuf;
  va_list arg_ptr;
  int i;
  int c;
  int l = 0;

  if (_debug || _execute_debug) {
  for (i = 0; i < lev*3; i++) {
    putc(' ', stderr);
  }
  va_start(arg_ptr,fmt);
  l = vsprintf(outbuf,fmt,arg_ptr);
  va_end(arg_ptr);
  for (;;) {
    c = (*s++)&255;
    if (c == '\0') break;
    if ((c != '\n') && (c < 32)) {
      putc('^', stderr); l += 1;
      putc(c+'A'-1, stderr);
    } else {
      putc(c, stderr);
    }
  }
  putc('\n', stderr);
  }
  return(l);
}

int debug_enter(const char *fmt, ...)
{
  if (_debug) {
    int i;
    static char outbuf[256];
    va_list arg_ptr;
    int l = 0;
    
    for (i = 0; i < lev*3; i++) {
      putc(' ', stderr);
    }
    va_start(arg_ptr,fmt);
    l = vsprintf(outbuf,fmt,arg_ptr);
    va_end(arg_ptr);
    fputs(outbuf,stderr);
    putc('\n', stderr);
  }
  lev += 1;
  return(TRUE);
}

int debug_execute_enter(const char *fmt, ...)
{
  if (_debug || _execute_debug) {
    int i;
    static char outbuf[256];
    va_list arg_ptr;
    int l = 0;
    
    for (i = 0; i < lev*3; i++) {
      putc(' ', stderr);
    }
    va_start(arg_ptr,fmt);
    l = vsprintf(outbuf,fmt,arg_ptr);
    va_end(arg_ptr);
    fputs(outbuf,stderr);
    putc('\n', stderr);
  }
  lev += 1;
  return(TRUE);
}

int debug_exit(const char *fmt, ...)
{
  lev -= 1;
  if (_debug) {
    static char outbuf[256];
    va_list arg_ptr;
    int i;
    int l = 0;

    for (i = 0; i < lev*3; i++) {
      putc(' ', stderr);
    }
    va_start(arg_ptr,fmt);
    l = vsprintf(outbuf,fmt,arg_ptr);
    va_end(arg_ptr);
    fputs(outbuf,stderr);
    putc('\n', stderr);
  }
  return(TRUE);
}

int debug_execute_exit(const char *fmt, ...)
{
  lev -= 1;
  if (_debug || _execute_debug) {
    static char outbuf[256];
    va_list arg_ptr;
    int i;
    int l = 0;

    for (i = 0; i < lev*3; i++) {
      putc(' ', stderr);
    }
    va_start(arg_ptr,fmt);
    l = vsprintf(outbuf,fmt,arg_ptr);
    va_end(arg_ptr);
    fputs(outbuf,stderr);
    putc('\n', stderr);
  }
  return(TRUE);
}

int Dummy(YYTYPE args[])
{
  if (!_optimise) debug("Executing dummy procedure");
  return(TRUE);
}

YYTYPE *makeparsetree(YYTYPE temp, int (*action) (YYTYPE __arg[]), int entries, char *name, int tag)
{
  YYTYPE *yy;
  int i;
  yy = stackmalloc(sizeof(yy->record) + sizeof(YYTYPE *)*entries);
  if (yy == NULL) {
    fprintf(stderr, "elefaunt: source too large to parse\n");
    exit(EXIT_FAILURE);
  }
  yy->record.type = tag;
  yy->execute.name = name;
  yy->execute.text = NULL;
  yy->execute.lineno = 0L;
  yy->execute.offset = 0L;
  yy->execute.action = action;
  yy->execute.nargs = entries;
  yy->execute.arg[0] = yy;
  for (i = 1; i <= entries; i++) {
    yy->execute.arg[i] = temp.execute.arg[i];
  }
  return(yy);
}


int _regexp_parse(patpair savepat[], int patno, YYTYPE **inf)
{
  char buf[REGEXP_BUFF_SIZE], *nextp = buf;
  char found[REGEXP_BUFF_SIZE];
  static int cur_line = 1;
  long start;
  int lastc, i=0;
  YYTYPE temp;
  regexp *r;
  char *pat = savepat[patno].pat;

  /* Could optimise regcomp by scanning patterns to find
     first one that matches pat - so if two the same, only
     one will be used hence savepat will be shared. */

  if (pat[0] == '^' && pat[1] == '\0') {
    *inf = NULL;
    return(0==0);
  }

  if (!_optimise) debug("Lexing %s at file position %ld", pat, ftell(yyin));
  *inf = makeparsetree(temp, Dummy, 0, "regexp", TYPE_REGEX);

  note_backtrack(&start);

  /* Fill the buff */
  if (inf == NULL) {
    fprintf(stderr, "tacc.h: Fatal error - inf is NULL\n");
    exit(EXIT_FAILURE);
  }
  (*inf)->execute.lineno = cur_line;
  (*inf)->execute.offset = ftell(yyin);
  for (;;) {
    lastc = fgetc(yyin);
    if (lastc == '\n') break;
    if (lastc == EOF) break;
    if (i == REGEXP_BUFF_SIZE-2) break;
    *nextp++ = lastc; i += 1;
  }
  *nextp = '\0';

  if (savepat[patno].r == NULL) {
     r = regcomp(pat);
     savepat[patno].r = r; /* These are freed on exit from root parse procedure */
  } else {
     r = savepat[patno].r;
  }

  i = regexec(r, buf);

  if (i == 1) {
    int l;

    regsub(r, "\\0", found);
    (*inf)->execute.text = stackmalloc(strlen(found)+1);
    strcpy((*inf)->execute.text, found);
    if (!_optimise) debug("REGEXP MATCHED \"%s\" -- storing in arg block at %p (string at %p)",
      found, (*inf), (*inf)->execute.text);
    l = strlen(found);
#if defined(__MSDOS__) && (!(defined(TACC_BINARY_FILES))) /* CRLF bodge */
    if ((l == 0) && (savepat[patno].pat[strlen(savepat[patno].pat)-1] == '$')) {
      cur_line += 1;
      l = 2;
    }
#else
    if ((l == 0) && (savepat[patno].pat[strlen(savepat[patno].pat)-1] == '$')) {
      cur_line += 1;
      l = 1;
    }
#endif
    /* *** THE BUG *** -- this was OK for matching null strings but
       not \n's at end of line.  This is because of the ambiguity of matching
       a null string with '\0' at the end of a line, and a pattern ending
       in $ with the same \0 at the end of the line.  I think we need a
       multi-line regexp (and one which accepts \x02 etc since at the moment
       such characters have to be put in verbatim!)

       It also goes wrong on EOF.  I've bodged it by checking whether the
       last char in the pattern was a '$', but this isn't really good enough --
       It would fail on \$ for instance.  Working out whether a real $ was wanted
       in a case like \\\\\\\\\\\\\$ is more bother than it is worth...
     */


    fseek(yyin, start+l, SEEK_SET);
  } else {
    stackfree(*inf); *inf = NULL;
    do_backtrack(start);
  }

  return(i == 1);
}

int execute_parsetree(YYTYPE *root)
{ 
  int i;
  i = 1;
  for (;;) {
    if (i > root->execute.nargs) break;
    if (root->execute.arg[i] != NULL) {
      if (!_optimise) debug_execute_enter("Evaluating %s parameter %d of %d (held at %p)", root->execute.name, i, root->execute.nargs,
         root->execute.arg[i]);
      execute_parsetree(root->execute.arg[i]);
      if (!_optimise) debug_execute_exit("%s returned %d", root->execute.arg[i]->execute.name, root->execute.arg[i]->execute.user_value);
    } else {
      if (!_optimise) debug_execute("%s's parameter %d of %d is a regexp", root->execute.name, i, root->execute.nargs);
    }
    i += 1;
  }
  if (!_optimise) debug_execute("Evaluating function %s at %p", root->execute.name, root);
  root->execute.action(root);
  root->execute.user_value = root->execute.arg[0]->execute.user_value;
  return(TRUE);
}

long tacc_bestpos = -1L;
int note_backtrack(long *pos)
{
  long p;
  if (yyin == NULL) {
    *pos = p = 0L;
  } else {
    *pos = p = ftell(yyin);
  }
  if (p > tacc_bestpos) tacc_bestpos = p; /* What a hack! */
  if (!_optimise) debug("Noting inpos %ld", p);
  return(TRUE);
}

void do_backtrack(long pos)
{
  if (!_optimise) debug("Restoring inpos %ld", pos);
  if (yyin != NULL) fseek(yyin, pos, SEEK_SET);
}

void dump_parsetree(YYTYPE *root)
{
  int i;

  if (!_optimise) {
  debug_enter("%s -- %d args", root->execute.name, root->execute.nargs);
  for (i = 1; i <= root->execute.nargs; i++) {
    debug("%s -- arg %d at %p", root->execute.name, i, root->execute.arg[i]);
    if (root->execute.arg[i] != NULL) dump_parsetree(root->execute.arg[i]);
  }
  debug_exit("End of %s", root->execute.name);
  }
}

void free_parsetree(YYTYPE *root)
{
  int i;

  if (root != NULL) {
    if (!_optimise) debug_enter("%s -- %d args", root->execute.name, root->execute.nargs);
    for (i = 1; i <= root->execute.nargs; i++) {
      if (!_optimise) debug("%s -- arg %d at %p", root->execute.name, i, root->execute.arg[i]);
      if (root->execute.arg[i] != NULL) {
        if (root->execute.arg[i]->record.type == TYPE_REGEX) {
          if (root->execute.arg[i]->execute.text != NULL) {
            /*fprintf(stderr, "Freeing text %s %p\n",
                      root->execute.arg[i]->execute.text,
                      root->execute.arg[i]->execute.text);*/
            stackfree(root->execute.arg[i]->execute.text); root->execute.arg[i]->execute.text = NULL;
          }
        }
        free_parsetree(root->execute.arg[i]);
        /*fprintf(stderr, "Freeing record of type %d at %p\n",
                  root->execute.arg[i]->record.type, root->execute.arg[i]);*/
        stackfree(root->execute.arg[i]); root->execute.arg[i] = NULL;
      }
    }
    if (!_optimise) debug_exit("End of %s", root->execute.name);
  }
}

int not(int (*_parse)(YYTYPE **inf), YYTYPE **arg)
{
  int rc;
  long pos;
  note_backtrack(&pos);
  rc = _parse(arg);
  do_backtrack(pos);
  return(!rc);
}

int is(int (*_parse)(YYTYPE **inf), YYTYPE **arg)
{
  int rc;
  long pos;
  note_backtrack(&pos);
  rc = _parse(arg);
  do_backtrack(pos);
  return(rc);
}
