#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "regex.h"
#include "siod.h"

/* OSF/1 doc says that POSIX and XPG4 include regcomp in libc.
   So we might as well set ourselves up to take advantage of it.
   This functionality is also available in hpux, and is also provided
   by the FSF's librx package, so if you can use that if your
   operating system vendor doesn't supply it.
 */

static void
init_regex_version (void)
{
  setvar (cintern ("*regex-version*"),
	  cintern ("$Id$"),
	  NIL);
}

long tc_regex = 0;

struct tc_regex
  {
    int compflag;
    size_t nmatch;
    regex_t *r;
    regmatch_t *m;
  };

struct tc_regex *
get_tc_regex (LISP ptr)
{
  if NTYPEP
    (ptr, tc_regex) my_err ("not a regular expression", ptr);
  return ((struct tc_regex *) ptr->storage_as.string.data);
}

LISP
regcomp_l (LISP pattern, LISP flags)
{
  long iflag, iflags;
  char *str, errbuff[1024];
  int error;
  LISP result;
  struct tc_regex *h;
  iflags = NNULLP (flags) ? get_c_long (flags) : 0;
  str = get_c_string (pattern);
  iflag = no_interrupt (1);
  result = cons (NIL, NIL);
  h = (struct tc_regex *) must_malloc (sizeof (struct tc_regex));
  h->compflag = 0;
  h->nmatch = 0;
  h->r = NULL;
  h->m = NULL;
  result->type = tc_regex;
  result->storage_as.string.data = (char *) h;
  h->r = (regex_t *) must_malloc (sizeof (regex_t));
  if ((error = regcomp (h->r, str, iflags)))
    {
      regerror (error, h->r, errbuff, sizeof (errbuff));
      return (my_err (errbuff, pattern));
    }
  h->compflag = 1;
  if (iflags & REG_NOSUB)
    {
      no_interrupt (iflag);
      return (result);
    }
  h->nmatch = h->r->re_nsub + 1;
  h->m = (regmatch_t *) must_malloc (sizeof (regmatch_t) * h->nmatch);
  no_interrupt (iflag);
  return (result);
}

LISP
regerror_l (LISP code, LISP ptr)
{
  char errbuff[1024];
  regerror (get_c_long (code), get_tc_regex (ptr)->r, errbuff, sizeof (errbuff));
  return (strcons (strlen (errbuff), errbuff));
}

LISP
regexec_l (LISP ptr, LISP str, LISP eflags)
{
  size_t j;
  int error;
  LISP result;
  struct tc_regex *h;
  h = get_tc_regex (ptr);
  if ((error = regexec (h->r,
		       get_c_string (str),
		       h->nmatch,
		       h->m,
		       NNULLP (eflags) ? get_c_long (eflags) : 0)))
    return (flocons (error));
  for (j = 0, result = NIL; j < h->nmatch; ++j)
    result = cons (cons (flocons (h->m[j].rm_so),
			 flocons (h->m[j].rm_eo)),
		   result);
  return (nreverse (result));
}

void
regex_gc_free (LISP ptr)
{
  struct tc_regex *h;
  if ((h = (struct tc_regex *) ptr->storage_as.string.data))
    {
      if ((h->compflag) && h->r)
	regfree (h->r);
      if (h->r)
	{
	  free (h->r);
	  h->r = NULL;
	}
      if (h->m)
	{
	  free (h->m);
	  h->m = NULL;
	}
      free (h);
      ptr->storage_as.string.data = NULL;
    }
}

void
regex_prin1 (LISP ptr, struct gen_printio *f)
{
  char buffer[256];
  regex_t *p;
  p = get_tc_regex (ptr)->r;
  sprintf (buffer, "#<REGEX %p nsub=%d",
	   p, p->re_nsub);
  gput_st (f, buffer);
  gput_st (f, ">");
}

void
init_regex (void)
{
  long j;
  tc_regex = allocate_user_tc ();
  set_gc_hooks (tc_regex,
		NULL,
		NULL,
		NULL,
		regex_gc_free,
		&j);
  set_print_hooks (tc_regex, regex_prin1);
  init_subr_2 ("regcomp", regcomp_l);
  init_subr_2 ("regerror", regerror_l);
  init_subr_3 ("regexec", regexec_l);
  setvar (cintern ("REG_EXTENDED"), flocons (REG_EXTENDED), NIL);
  setvar (cintern ("REG_ICASE"), flocons (REG_ICASE), NIL);
  setvar (cintern ("REG_NOSUB"), flocons (REG_NOSUB), NIL);
  setvar (cintern ("REG_NEWLINE"), flocons (REG_NEWLINE), NIL);

  setvar (cintern ("REG_NOTBOL"), flocons (REG_NOTBOL), NIL);
  setvar (cintern ("REG_NOTEOL"), flocons (REG_NOTEOL), NIL);

  setvar (cintern ("REG_NOMATCH"), flocons (REG_NOMATCH), NIL);
  setvar (cintern ("REG_BADPAT"), flocons (REG_BADPAT), NIL);
  setvar (cintern ("REG_ECOLLATE"), flocons (REG_ECOLLATE), NIL);
  setvar (cintern ("REG_ECTYPE"), flocons (REG_ECTYPE), NIL);
  setvar (cintern ("REG_EESCAPE"), flocons (REG_EESCAPE), NIL);
  setvar (cintern ("REG_ESUBREG"), flocons (REG_ESUBREG), NIL);
  setvar (cintern ("REG_EBRACK"), flocons (REG_EBRACK), NIL);
  setvar (cintern ("REG_EPAREN"), flocons (REG_EPAREN), NIL);
  setvar (cintern ("REG_EBRACE"), flocons (REG_EBRACE), NIL);
  setvar (cintern ("REG_BADBR"), flocons (REG_BADBR), NIL);
  setvar (cintern ("REG_ERANGE"), flocons (REG_ERANGE), NIL);
  setvar (cintern ("REG_ESPACE"), flocons (REG_ESPACE), NIL);
  setvar (cintern ("REG_BADRPT"), flocons (REG_BADRPT), NIL);
#ifdef REG_ECHAR
  setvar (cintern ("REG_ECHAR"), flocons (REG_ECHAR), NIL);
#endif
  init_regex_version ();
}
