#ifndef __LEX_REGEX_C__
#define __LEX_REGEX_C__ 1

// cc -o reg -DTEST_REGEX lex_regex.c
// Can also be linked to as an object file, or included directly in another source file.

// standard
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
// linux
#include <regex.h>

#include "lex_regex.h"

const char *Parse_Version = "lex_regex.c: $Revision: 1.2 $";

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

#define MAX_MATCHES 32 // This is the maximum number allowed of matching () groups
                       // in the regex that may be used in replacement strings
                       // Although only \0 through \9 are currently accessible,

// Note: ^ and $ in a regex match beginning and end of the string, not start-of-line and end-of-line
//       However we assume that a string does not contain \n characters.  This will still work if
//       it does but you had better know what you're doing - it would be more sensible to add
//       an alternative version of this procedure that explicitly allowed newlines and used the
//       eflag to handle a string which did not begin at the start of a line.  (If this
//       all means nothing to you, go read "man regexec" until it does.

int lex_regex(char *regexs, char *p, char *replace, char **matched) {

  // when a regex matches something in the source, this extracts the matched text as a string.
  // \0 is the entire string, \1 through \9 are the specific () grouped elements.
  auto char *extract(char *s, int start /* inclusive */, int end /* exclusive */) {
    char *t = malloc(end-start+1);
    int i;
    if (t == NULL) {
      fprintf(stderr, "lex_regex: running out of memory. Programmer may need to restructure the code.\n");
      return NULL;
    }
    for (i = 0; i < end-start; i++) t[i] = s[i+start];
    t[end-start] = '\0';
    return t;
  }

  regmatch_t matches[MAX_MATCHES];
  regex_t regex;
  int reti;
  char msgbuf[100];
  char *dest;

  if (p == NULL) return FALSE;
  if (strchr(p, '\n') != NULL) {
    fprintf(stderr, "lex_regex: this code probably does not do what you expect with respect to ^ and $ ...\n");
  }
  if (*regexs == '\0') return (TRUE); // empty string always matches
  
  /* Compile regular expression */
  if (regcomp(&regex, regexs, REG_EXTENDED) != 0) {
    fprintf(stderr, "lex_regex: programmer error - could not compile regex \"%s\" - we may have run out of malloc memory\n", regexs);
    // No need to regfree(&regex) - http://redhat.com/archives/libvir-list/2013-September/msg00276.html
    return FALSE;
  }

  /* Execute regular expression */
  reti = regexec(&regex, p, MAX_MATCHES, matches, 0);

  if (!reti) {
    char *original_replace = replace;
    if (matched != NULL) {
      dest = *matched;
      for (;;) {
        int c = *replace++;
        if (c == '\0') break;
        if (c == '\\') {
          int n = *replace++;
          char *insert;
          if (n < '0' || n > '9') {
            fprintf(stderr, "lex_regex: Bad replacement syntax '\\%c' in \"%s\" (should be \\0 to \\9)\n", n, original_replace);
            regfree(&regex);
            return (FALSE);
          }
          n -= '0';
          if (matches[n].rm_so == -1) {
            fprintf(stderr, "lex_regex: No such group '\\%c' for replacement in \"%s\"\n", n+'0', regexs);
            regfree(&regex);
            return (FALSE);
          }
          strcpy(dest, insert=extract(p, matches[n].rm_so, matches[n].rm_eo));
          dest += strlen(insert); // point to NUL
        } else {
          *dest++ = c;
        }
      }
      *dest = '\0';
    }
    regfree(&regex);
    return (TRUE);
  } else if (reti == REG_NOMATCH) {
    regfree(&regex);    /* Free memory allocated to the pattern buffer by regcomp() */
                        // NB http://redhat.com/archives/libvir-list/2013-September/msg00276.html
                        // only applies to failed regcomp, not failed regexec
    return (FALSE);
  }

  regerror(reti, &regex, msgbuf, sizeof(msgbuf));
  fprintf(stderr, "lex_regex: Regex match internal error: %s\n", msgbuf);
  fflush(stderr); fflush(stdout);
  return (FALSE);
}

#ifdef TEST_REGEX
int main(int argc, char **argv) {
  char *matched = calloc(1, 128);
  if (lex_regex("[A-Z][A-Z]*", "... FRED ok?", "\\0", &matched)) fprintf(stderr, "Passed: %s\n", matched); else fprintf(stderr, "Failed\n");
  if (lex_regex("[A-Z][A-Z]*", "... fred ok?", "\\0", &matched)) fprintf(stderr, "Failed\n"); else fprintf(stderr, "Passed\n");
  if (lex_regex("[A-Z][A-Z]*", "... FRED ok?", "\\A", &matched)) fprintf(stderr, "Failed\n"); else fprintf(stderr, "Passed\n");
  if (lex_regex("^([^A-Z]*)([A-Z][A-Z]*)(.*)", "... FRED ok?", "\\1[[\\2]]\\3", &matched)) fprintf(stderr, "Passed: %s\n", matched); else fprintf(stderr, "Failed\n");
  if (lex_regex("[A-Z][A-Z]*", "... FRED ok?", "\\7", &matched)) fprintf(stderr, "Failed\n"); else fprintf(stderr, "Passed\n");
  if (lex_regex("[A-Z][A-Z]*", "... FRED ok?", "\\0", NULL)) fprintf(stderr, "Passed\n"); else fprintf(stderr, "Failed\n");
  fflush(stderr);

  exit(EXIT_SUCCESS);
  return EXIT_FAILURE;
}
#endif

#endif // __LEX_REGEX_C__
