/* lib_shellpattern.
 * (c)2002, 2003 by Samuel Abels (spam debain org)
 * This project's homepage is: http://www.debain.org/cantus
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fnmatch.h>
#include <regex.h>
#include "lib_regexp.h"

#ifdef _CPLUSPLUS_
extern "C" {
#endif

/******************************************************************************
 * STATICS
 ******************************************************************************/
/*
 * Uppercase a string.
 */
static void strtoupper(char *str)
{
  if (!str)
    return;
  while (*str != '\0') {
    if (isalpha(*str))
      *str = toupper(*str);
    str++;
  }
}


/*
 * Replace "from" by "to".
 */
static void str_replace(char *str, const char *from, const char *to, int maxlen)
{
  char  *pstr   = str;
  int   fromlen = strlen(from);
  int   tolen   = strlen(to);
  
  while (*pstr != '\0' && pstr - str < maxlen) {
    if (strncmp(pstr, from, fromlen) != 0) {
      *pstr++;
      continue;
    }
    memmove(pstr + tolen, pstr + fromlen, maxlen - ((pstr + tolen) - str) - 1);
    memcpy(pstr, to, tolen);
    pstr += tolen;
  }
}
/******************************************************************************
 * END STATICS
 ******************************************************************************/


/*
 * This function will compare a shell pattern with a string.
 * TRUE if match. Otherwise FALSE.
 */
short int shellpattern_match(const char *string,
                             const char *pattern,
                             short int casess)
{
  char      *stringcp   = NULL;
  char      *patterncp  = NULL;
  short int match       = FALSE;
  
  if (!pattern || !string)
    return(FALSE);
  
  /* Create a duplicate of the string. */
  stringcp  = strdup(string);
  patterncp = strdup(pattern);
  
  /* If we are case insensitive, uppercase those strings now. */
  if (!casess) {
    strtoupper(stringcp);
    strtoupper(patterncp);
  }
  
  match = (fnmatch(patterncp, stringcp, 0) == 0);
  
  free(patterncp);
  free(stringcp);
  
  return(match);
}


/*
 * This function will compare a list of shell patterns with a string.
 * TRUE if any of them matches, otherwise FALSE.
 */
short int shellpattern_match_any(const char *string,
                                 const char *pattern[],
                                 short int casess)
{
  int i = -1;
  
  while (pattern[++i]) {
    if (shellpattern_match(string, pattern[i], casess))
      return(TRUE);
  }
  
  return(FALSE);
}


/*
 * This function will compare a shell pattern with a string and
 * return the embraced glob "(*)" match or NULL.
 */
short int shellpattern_match_grab(const char *string,
                                  const char *pattern,
                                  short int casess,
                                  char **match)
{
  regex_t     preg;               /* Pre-compiled pattern buffer. */
  int         numoffsets    = 50;
  regmatch_t  pmatch[numoffsets]; /* Substring addresses, filled by regexec. */
  char        regexp[1024];
  char        *result       = NULL;
  int         flags         = 0;
  
  /* Translate the pattern into a regular expression. */
  strncpy(regexp, pattern, 1023);
  str_replace(regexp, ".", "\\.", 1023);
  str_replace(regexp, "*", ".*", 1023);
  str_replace(regexp, "(", "\\(", 1023);
  str_replace(regexp, ")", "\\)", 1023);
  str_replace(regexp, "?", ".", 1023);
  str_replace(regexp, "[!", "[^", 1023);
  
  /* Compile the regular expression. */
  flags = REG_NEWLINE;
  if (!casess)
    flags |= REG_ICASE;
  if (regcomp(&preg, regexp, flags) != 0) {
    fprintf(stderr, "Error: RegExp compilation failed: \"%s\"\n", regexp);
    return(-1);
  }
  
  /* Execute the regular expression. */
  if (regexec(&preg, string, numoffsets, pmatch, REG_NOTBOL | REG_NOTEOL) != 0)
    return(1);
  
  result = (char*)calloc(sizeof(char), (pmatch[1].rm_eo - pmatch[1].rm_so) + 2);
  memcpy(result, string + pmatch[1].rm_so, (pmatch[1].rm_eo - pmatch[1].rm_so));
  *match = result;
  
  return(0);
}


/* This function will compare a regexp with a string and
 * return the content of all embraced matches, or NULL. Do not forget to free
 * the content of "matches[]"!
 */
short int regexp_match_grab(const char *string,
                            const char *regexp,
                            short int casess,
                            char **matches)
{
  regex_t     preg;               /* Pre-compiled pattern buffer. */
  int         numoffsets    = 50;
  regmatch_t  pmatch[numoffsets]; /* Substring addresses, filled by regexec. */
  char        *result       = NULL;
  int         flags         = 0;
  int         i             = 0;
  
#ifdef _DEBUG_
  printf("regexp_match_grab(): String: \"%s\"\n", string);
  printf("regexp_match_grab(): RegExp: \"%s\"\n", regexp);
#endif
  
  /* Compile the regular expression. */
  flags = REG_NEWLINE | REG_EXTENDED;
  if (!casess)
    flags |= REG_ICASE;
  if (regcomp(&preg, regexp, flags) != 0) {
    fprintf(stderr, "Error: RegExp compilation failed: \"%s\"\n", regexp);
    return(-1);
  }
  
#ifdef _DEBUG_
  printf("regexp_match_grab(): Compilation success.\n");
#endif
  
  /* Execute the regular expression. */
  if (regexec(&preg, string, numoffsets, pmatch, REG_NOTBOL | REG_NOTEOL) != 0)
    return(1);
  
#ifdef _DEBUG_
  printf("regexp_match_grab(): Matches.\n");
#endif
  
  i = 0;
  while (pmatch[++i].rm_so != -1) {
    int len = (pmatch[i].rm_eo - pmatch[i].rm_so);
    result  = (char*)calloc(sizeof(char), len);
    memcpy(result, string + pmatch[i].rm_so, len);
#ifdef _DEBUG_
    printf("regexp_match_grab(): Match %i: %s\n", i, result);
#endif
    matches[i - 1] = result;
  }
  
  matches[i - 1] = NULL;
  return(0);
}


/* Frees the matches previously returned by regexp_match_grab.
 */
void free_regexp_matches(char **matches)
{
  int i = -1;
#ifdef _DEBUG_
  printf("free_regexp_matches(): Called.\n");
#endif
  while (matches[++i])
    free(matches[i]);
#ifdef _DEBUG_
  printf("free_regexp_matches(): Free'd.\n");
#endif
}


/*
 * Determine whether the pattern can be successfully compiled.
 * Returns TRUE if the pattern is valid, otherwise FALSE.
 */
short int shellpattern_is_valid(const char *pattern)
{
  regex_t     preg;               /* Pre-compiled pattern buffer. */
  char        regexp[1024];
  
  /* Translate the pattern into a regular expression. */
  strncpy(regexp, pattern, 1023);
  str_replace(regexp, "*", ".*", 1023);
  str_replace(regexp, "(", "\\(", 1023);
  str_replace(regexp, ")", "\\)", 1023);
  str_replace(regexp, "?", ".", 1023);
  str_replace(regexp, "[!", "[^", 1023);
  
  /* Compile the regular expression. */
  if (regcomp(&preg, regexp, REG_NEWLINE) != 0)
    return(FALSE);
  
  return(TRUE);
}



/*
 * Returns zero if the regular expression matches the string.
 * Returns -1 if the expression can't be compiled.
 * Returns 1 if it doesn't match.
 */
short int regexp_match(const char *string, const char *regexp, short int casess)
{
  regex_t preg;               /* Pre-compiled pattern buffer. */
  int     flags = 0;
  
  /* Compile the regular expression. */
  flags = REG_NEWLINE | REG_EXTENDED;
  if (!casess)
    flags |= REG_ICASE;
  if (regcomp(&preg, regexp, flags) != 0)
    return(-1);
  
  /* Execute the regular expression. */
  if (regexec(&preg, string, 0, NULL, REG_NOTBOL | REG_NOTEOL) != 0)
    return(1);
  
  return(0);
}


/*
 * Determine whether the regexp can be successfully compiled.
 * Returns TRUE if the regexp is valid, otherwise FALSE.
 */
short int regexp_is_valid(const char *regexp)
{
  regex_t preg;               /* Pre-compiled pattern buffer. */
  
  /* Compile the regular expression. */
  if (regcomp(&preg, regexp, REG_NEWLINE | REG_EXTENDED) != 0)
    return(FALSE);
  
  return(TRUE);
}

#ifdef _CPLUSPLUS_
}
#endif
