/*********************************************************************
 *
 *   Module   : RegExp
 *   Fichier  : RegExpSelect.c (ex DilibGrep.c)
 *   Auteur   : F. PARMENTIER
 *   Date     : June 2003
 *   $Id: DilibGrep.c,v 1.11 2007/01/22 08:39:02 parmentf Exp $
 *********************************************************************
 *
 * Copyright (c) 2003 INIST - CNRS
 *
 *********************************************************************/
/**
   @file

   @brief DilibGrep command

   @section DilibGrepSYNOPSIS SYNOPSIS

   @code
   DilibGrep [-v][-i][-f patternfile]... [-e pattern]... [-E regexp]... [-F regexpfile]...
   @endcode

   @section DilibGrepDESCRIPTION DESCRIPTION

   This command (@p DilibGrep) searches standard input for a pattern,
   or patterns from a file and outputs the lines containing at least
   one of these pattern(s).

   @section DilibGrepOPTIONS OPTIONS
   
   @p -f takes each of the lines of @p patternfile as a pattern

   @p -e takes its argument as a pattern

   @p -E takes its argument as a regular expression pattern

   @p -F takes each line of @p regexpfile

   @p -v inverts the filter


   @section DilibGrepEXAMPLES EXAMPLES

   @code
   DilibGrep -e inside < fileLines
   @endcode

   @see grep (1), regexp(5)

   @author &copy; INIST-CNRS
   @author Fran&ccedil;ois PARMENTIER
           <DilibMaster@inist.fr>
   @since  June 2003
   @return 0 if OK
   @return 1 if there was no output.
   @return 2 if the arguments were not good or if a file is not readable.

*/

#include <stdlib.h>
#include <unistd.h>	/* getopt */
#include <stdio.h>
#include <string.h>
#include <ctype.h>      /* tolower */
#include "SxmlNode.h"
#include "Buffer.h"
#include "RegExp.h"


/**
   Translates the uppercase letters of the @a str to lowercase ones.

   @param str the string to convert to lowercase
   @returns the modified string
 */
char *StrToLower(char *str) 
{
  int i = 0;
  for(i=0; str[i]; i++) {
    str[i] = (char)tolower((int)str[i]);
  }
  return str;
} /* StrToLower() */

/**
   Adds @a strPatterns to @a nodPatterns.
   
   @param strPattern pattern to add to @a nodPatterns
   @param nodPatterns list of patterns to find
   @param blnInsensitive case insensitive filter (if true)
   @author F. PARMENTIER
   @since Jul 2003
 */
void StringAddToPatterns(char    *strPattern,
			 SxmlNode *nodPatterns,
			 int      blnInsensitive)
{
  if(blnInsensitive) {
    strPattern = StrToLower(strPattern);
  }
  SxmlAppendChild(nodPatterns,SxmlLeafCreate("p",strPattern));
} /* StringAddToPatterns() */


/**
   Adds @a regExp to @a nodRegPatterns.

   @param regExp         a regular expression to add to the patterns
   @param nodRegPatterns list of regular expressions to find
   @author F. PARMENTIER
   @since July 2003
 */
void RegExpAddToPatterns(char    *regExp,
			 SxmlNode *nodRegPatterns) 
{
  SxmlAppendChild(nodRegPatterns, SxmlContainerRegExpCreate(regExp));
} /* RegExpAddToPatterns() */

/**
   Adds the regular expression patterns of @a fileName to 
   @a nodRegPatterns.
   
   @param fileName       Name of the file in which patterns are found
   @param nodRegPatterns List of regular expression patterns to search
   @author F. PARMENTIER
   @since July 2003
 */
void FileRegExpAddToList(char    *fileName,
			 SxmlNode *nodRegPatterns)
{
  Buffer *buf = BufferCreate(10,20);
  FILE   *f   = fopen(fileName,"r");
  if(!f) {
    fprintf(stderr,"DilibGrep: Unable to open file \"%s\"!\n",fileName);
    BufferFree(buf);
    exit(2);
  }
  
  while(BufferFgets(buf,f)) {
    SxmlAppendChild(nodRegPatterns, SxmlContainerRegExpCreate(BufferString(buf)));
  }
  BufferFree(buf);
  fclose(f);  
} /* FileRegExpAddToList() */

/**
   Adds the patterns of @a fileName in the @a nodPatterns.

   @param fileName       Name of the file in which patterns are found
   @param nodPatterns    List of patterns to search
   @param blnInsensitive Flag for insensitive case (unused)
   @author F. PARMENTIER
   @since June 2003
 */
void FilePatternsAddToList(char    *fileName,
			   SxmlNode *nodPatterns,
			   int      blnInsensitive) {
  Buffer *buf = BufferCreate(10,20);
  FILE   *f   = fopen(fileName,"r");
  if(!f) {
    fprintf(stderr,"DilibGrep: Unable to open file \"%s\"!\n",fileName);
    BufferFree(buf);
    exit(2);
  }
  
  while(BufferFgets(buf,f)) {
    if(blnInsensitive) {
      SxmlAppendChild(nodPatterns, 
		     SxmlLeafCreate("p",StrToLower(BufferString(buf))));
    }
    else {
      SxmlAppendChild(nodPatterns, 
		     SxmlLeafCreate("p",BufferString(buf)));
    }
  }
  BufferFree(buf);
  fclose(f);
} /* FilePatternsAddToList */


/**
   Filters @a bufInput with @a nodPatterns: when a pattern of @a
   nodPatterns lies in @a bufInput, @a bufInput is printed on @c
   stdout.

   @warning does not yet treat regular expressions.

   @param nodPatterns    list of patterns to find
   @param nodRegPatterns list of regular expressions to find
   @param bufInput       input line to filter
   @param blnInverse     invert the filter
   @param blnInsensitive case insensitive filter (if true)
   @author F. PARMENTIER
   @since June 2003
 */
void PatternsFilterBuffer(SxmlNode *nodPatterns,
			  SxmlNode *nodRegPatterns,
			  Buffer  *bufInput,
			  int      blnInverse,
			  int      blnInsensitive) {
  char    *strToSearch = NULL;
  int      blnFound    = 0;
  SxmlNode *nodPattern  = NULL;
  if(blnInsensitive) {
    strToSearch = StrToLower(strdup(BufferString(bufInput)));
  }
  else {
    strToSearch = BufferString(bufInput);
  }
  SxmlReset(nodPatterns);
  while((nodPattern = SxmlNextNode(nodPatterns))) {
    char   *strPattern = SxmlLeafText(nodPattern);
    if(strstr(strToSearch,strPattern)) {
      if(!blnInverse) {
	printf("%s\n",BufferString(bufInput));
	return;
      }
      else {
	blnFound = 1;
      }
    }
  }
  if(blnInsensitive && strToSearch) {
    free(strToSearch);
    strToSearch = NULL;
  }
  SxmlReset(nodRegPatterns);
  while((nodPattern = SxmlNextNode(nodRegPatterns))) {
    RegExp *regExp = (RegExp *)SxmlNodeValue(nodPattern);
    if(RegExpFind(regExp,BufferString(bufInput))) {
      if(!blnInverse) {
	printf("%s\n",BufferString(bufInput));
	return;
      }
      else {
	blnFound = 1;
      }
    }
  }
  if(blnInverse && !blnFound)
    printf("%s\n",BufferString(bufInput));
}


/** 
    Main function of @p DilibGrep

    @param argc number of arguments
    @param argv value of the arguments
    @return 0 if all was OK.
    @return 1 if there was no output.
 */
int main(int argc, char **argv) 
{
  int          c;
  extern char *optarg;
  extern int   optind;
  SxmlNode     *nodPatterns    = SxmlElementCreate("patterns");
  SxmlNode     *nodRegPatterns = SxmlElementCreate("regpatterns");
  Buffer      *bufInput       = BufferCreate(500,1000);
  int          blnInverse     = 0;
  int          blnInsensitive = 0;

  while((c = getopt(argc, argv, "ivf:e:E:F:")) != EOF) {
    switch(c) {
    case 'e':
      StringAddToPatterns(optarg,nodPatterns,blnInsensitive);
      break;
    case 'f':
      FilePatternsAddToList(optarg,nodPatterns,blnInsensitive);
      break;
    case 'i':
      blnInsensitive = 1;
      break;
    case 'v':
      blnInverse = 1;
      break;
    case 'E':
      RegExpAddToPatterns(optarg,nodRegPatterns);
      break;
    case 'F':
      FileRegExpAddToList(optarg,nodRegPatterns);
      break;
    default:
      fprintf(stderr,"usage: DilibGrep [-v][-i][-f patternfile]... [-e pattern]... [-E regexp]... [-F regexpfile]...\n");
      return 2;
    }
  }

  if(!SxmlLength(nodPatterns) && !SxmlLength(nodRegPatterns))
    return 1;

  while(BufferGets(bufInput)) {
    PatternsFilterBuffer(nodPatterns,nodRegPatterns,bufInput,
			 blnInverse,blnInsensitive);
  }

  if(nodRegPatterns) {
    SxmlFree(nodRegPatterns);
    nodRegPatterns = NULL;
  }
  if(nodPatterns) {
    SxmlFree(nodPatterns);
    nodPatterns = NULL;
  }
  if(bufInput) {
    BufferFree(bufInput);
    bufInput = NULL;
  }
  return 0;
} /* main() */
