/*
 *  Copyright (c) 1994 John E. Davis  (davis@amy.tch.harvard.edu)
 *  All Rights Reserved.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "slang.h"
#include "vfile.h"

static int Case_Sensitive = 1;
static int File_Name_Only;
static int Do_Recursive = 0;
static int Recursive_Match = 0;
static int Highlight = 0;
static int Count_Matches = 0;
static int Line_Numbers = 0;
static int Follow_Links = 0;
static int Debug_Mode = 0;

#define HON_STR "\033[1m"
#define HON_STR_LEN 4
#define HOFF_STR "\033[0m"
#define HOFF_STR_LEN 4

void usage()
{
   fputs("rgrep (v1.0)\nUsage: rgrep [options..] pattern [files ...]\n\
Options:\n\
  -c         count matches\n\
  -h         highlight match (ANSI compatable terminal assumed)\n\
  -i         ignore case\n\
  -l         list filename only\n\
  -n         print line number of match\n\
  -F         follow links\n\
  -r         recursively scan through directory tree\n\
  -R 'pat'   like '-r' except that only those files matching 'pat' are checked\n\
\n\
'pattern' is a valid 'ex' type of regular expression.  See the man page for ex.\n\
It is best enclosed in single quotes to avoid shell expansion.\n\
Examples:\n\
 Look in all files in current directory and all subdirectories looking for\n\
 matches of 'int ' at the beginning of a line, printing the line with its\n\
 line number:\n\
    grep -n -R '*.c' '^int ' .\n\
 Highlight all matches of repeated words in file 'paper.tex':\n\
    grep -ih '\\( [a-z]\\) *\\1 ' paper.tex\n", stderr);
   
   exit(1);
}

void parse_flags(char *f)
{
   char ch;
   while ((ch = *f++) != 0)
     {
	switch (ch)
	  {
	   case 'i': Case_Sensitive = 0; break;
	   case 'l': File_Name_Only = 1; break;
	   case 'r': Do_Recursive = 1; break;
	   case 'h': Highlight = 1; break;
	   case 'c': Count_Matches = 1; break;
	   case 'n': Line_Numbers = 1; break;
	   case 'F': Follow_Links = 1; break;
	   case 'D': Debug_Mode = 1;
	  }
     }
}

/* 8bit clean upper and lowercase macros */
unsigned char Chg_LCase_Lut[256];
unsigned char Chg_UCase_Lut[256];

void SLang_define_case(int *u, int *l)
{
   unsigned char up = (unsigned char) *u, dn = (unsigned char) *l;
   
   Chg_LCase_Lut[up] = dn;
   Chg_UCase_Lut[dn] = up;
}

void init_lut(void)
{
   int i,j;
   
   for (i = 0; i < 256; i++) 
     {
	Chg_UCase_Lut[i] = i;
	Chg_LCase_Lut[i] = i;
     }
   
   for (i = 'A'; i <= 'Z'; i++) 
     {
	j = i + 32;
	Chg_UCase_Lut[j] = i;
	Chg_LCase_Lut[i] = j;
     }
#ifdef msdos
   /* Initialize for DOS code page 437. */
   Chg_UCase_Lut[135] = 128; Chg_LCase_Lut[128] = 135;
   Chg_UCase_Lut[132] = 142; Chg_LCase_Lut[142] = 132;
   Chg_UCase_Lut[134] = 143; Chg_LCase_Lut[143] = 134;
   Chg_UCase_Lut[130] = 144; Chg_LCase_Lut[144] = 130;
   Chg_UCase_Lut[145] = 146; Chg_LCase_Lut[146] = 145;
   Chg_UCase_Lut[148] = 153; Chg_LCase_Lut[153] = 148;
   Chg_UCase_Lut[129] = 154; Chg_LCase_Lut[154] = 129;
   Chg_UCase_Lut[164] = 165; Chg_LCase_Lut[165] = 164;
#else
   /* ISO Latin */
   for (i = 192; i <= 221; i++) 
     {
	j = i + 32;
	Chg_UCase_Lut[j] = i;
	Chg_LCase_Lut[i] = j;
     }
   Chg_UCase_Lut[215] = 215; Chg_LCase_Lut[215] = 215;
   Chg_UCase_Lut[223] = 223; Chg_LCase_Lut[223] = 223;
   Chg_UCase_Lut[247] = 247; Chg_LCase_Lut[247] = 247;
   Chg_UCase_Lut[255] = 255; Chg_LCase_Lut[255] = 255;
#endif

}


#define UPPER_CASE(x) (Chg_UCase_Lut[(unsigned char) (x)])
#define upcase(ch) (cs ? ch : UPPER_CASE(ch))

static int ind[256];

unsigned char *forw_search_region
    (register unsigned char *beg, unsigned char *end, unsigned char *key, register int key_len)
{
   register unsigned char char1;
   unsigned char *pos;
   int j, str_len;
   register unsigned char ch;
   register int db;
   int cs = Case_Sensitive;
   

   str_len = (int) (end - beg);
   if (str_len < key_len) return (NULL);

   char1 = key[key_len - 1];
   beg += (key_len - 1);

   while(1)
     {
	if (cs) while (beg < end)
	  {
	     ch = *beg;
	     db = ind[(unsigned char) ch];
	     if ((db < key_len) && (ch == char1)) break;
	     beg += db; /* ind[(unsigned char) ch]; */
	  }
	else while (beg < end)
	  {
	     ch = *beg;
	     db = ind[(unsigned char) ch];
	     if ((db < key_len) && 
		 (UPPER_CASE(ch) == char1)) break;
	     beg += db; /* ind[(unsigned char) ch]; */
	  }
	
	if (beg >= end) return(NULL);
	
	pos = beg - (key_len - 1);
	for (j = 0; j < key_len; j++)
	  {
	     ch = upcase(pos[j]);
	     if (ch != (unsigned char) key[j]) break;
	  }
	
	if (j == key_len) return(pos);
	beg += 1;
     }
}

static int key_len;
static unsigned char search_buf[256];


static void upcase_search_word(unsigned char *str)
{
   int i, maxi;
   int cs = Case_Sensitive;
   register int max = strlen((char *) str);
   char *w;
   register int *indp, *indpm;
   
   w = (char *) search_buf;
   indp = ind; indpm = ind + 256; while (indp < indpm) *indp++ = max;
   
   i = 0;
   while (i++ < max)
     {
	maxi = max - i;
	if (cs)
	  {
	     *w = *str;
	     ind[(unsigned char) *str] = maxi;
	  }
	else
	  {
	     *w = UPPER_CASE(*str);
	     ind[(unsigned char) *w] = maxi;
	     ind[(unsigned char) LOWER_CASE(*str)] = maxi;
	  }
	str++; w++;
     }
   search_buf[max] = 0;
   key_len = max;
}



void msg_error(char *str)
{
   fputs(str, stderr);
   putc('\n', stderr);
}

void exit_error(char *s)
{
   fprintf(stderr, "rgrep: %s\n", s);
   exit(1);
}

static SLRegexp_Type reg;
static SLRegexp_Type recurse_reg;
static int Must_Match;
static int print_file_too;

void output_line(unsigned char *s, unsigned int n, unsigned char *p, unsigned char *pmax)
{
   if (Highlight == 0)
     {
	fwrite(s, 1, n, stdout);
     }
   else
     {
	fwrite (s, 1, (int) (p - s), stdout);
	fwrite (HON_STR, 1, HON_STR_LEN, stdout);
	fwrite (p, 1, (int) (pmax - p), stdout);
	fwrite (HOFF_STR, 1, HOFF_STR_LEN, stdout);
	fwrite (pmax, 1, (int) n - (pmax - s), stdout);
     }
}

static VFILE *vfile_vp;

void grep(char *file)
{
   unsigned char *buf, *p, *pmax;
   unsigned int n;
   int line = 0, n_matches = 0;
   
   while (NULL != (buf = (unsigned char *) vgets(vfile_vp, &n)))
     {
	line++;
	if (Must_Match)
	  {
	     if (key_len > n) continue;
	     if (NULL == (p = forw_search_region(buf, buf + n, search_buf, key_len)))
	       {
		  continue;
	       }
	     if (reg.osearch) 
	       {
		  pmax = p + key_len;
		  goto match_found;
	       }
	  }
	
	if (!SLang_regexp_match(buf, (int) n, &reg)) continue;
	p = buf + reg.beg_matches[0];
	pmax = p + reg.end_matches[0];
	
	match_found:
	n_matches++;
	
	if (Count_Matches) continue;
	if (File_Name_Only)
	  {
	     puts(file);
	     return;
	  }
	if (print_file_too)
	  {
	     fputs(file, stdout);
	     putc(':', stdout);
	  }
	if (Line_Numbers)
	  {
	     fprintf(stdout, "%d:", line);
	  }
	     
	output_line(buf, n, p, pmax);
     }
   if (n_matches && Count_Matches)
     {
	if (print_file_too || File_Name_Only)
	  {
	     fputs(file, stdout);
	     putc(':', stdout);
	  }
	fprintf(stdout, "%d\n", n_matches);
     }
}

#ifdef msdos
#define MAX_PATH_LEN 128
#else
#define MAX_PATH_LEN 512
#endif

typedef struct
{
   char dir[MAX_PATH_LEN];
   int dir_len;
   char *file;			       /* pointer to place in dir */
   int isdir;
   long misc;
} Sys_Dir_Type;
   



#ifdef unix
#include <sys/types.h>
#include <sys/stat.h>
#ifdef sequent
# include <sys/dir.h>
# define NEED_D_NAMLEN
#else
# include <dirent.h>
#endif


int unix_is_dir(char *dir)
{
/* AIX requires this */
#ifdef _S_IFDIR
#ifndef S_IFDIR
#define S_IFDIR _S_IFDIR
#endif
#endif
   struct stat buf;
   int mode;

   if (Follow_Links)
     {
	if (stat(dir, &buf)) return -1;
     }
   else if (lstat(dir, &buf) < 0) return -1;

   mode = buf.st_mode & S_IFMT;
   
   if (mode == S_IFLNK) return (-1);
   if (mode == S_IFDIR) return (1);
   if (mode != S_IFREG) return (-1);
   
   return(0);
}
#endif
/* unix */

Sys_Dir_Type *sys_opendir(char *dir, Sys_Dir_Type *x)
{
   char *p;
#ifdef unix
   char slash = '/';
   DIR *dirp;
   if (NULL == (dirp = (DIR *) opendir(dir)))
     {
	fprintf (stderr, "rgrep: dir %s not readable.\n", dir);
	return NULL;
     }
   
   x->misc = (long) dirp;
#endif
   strcpy(x->dir, dir);
   x->dir_len = strlen(dir);
   if (dir[x->dir_len - 1] != slash) 
     {
	x->dir[x->dir_len++] = slash;
	x->dir[x->dir_len] = 0;
     }
   return (x);
}



void sys_closedir(Sys_Dir_Type *x)
{
#ifdef unix
   DIR *dirp;
   dirp =(DIR *) x->misc;
   if (dirp != NULL) closedir(dirp);
   x->misc = (long) NULL;
#endif
}


char *sys_dir_findnext(Sys_Dir_Type *x)
{
   char *file;
#ifdef unix
#  ifdef NEED_D_NAMLEN
#    define dirent direct
#  endif
   struct dirent *dp;
   DIR *d;
   d = (DIR *) x->misc;
   
   if (NULL == (dp = readdir(d))) return(NULL);
#  ifdef NEED_D_NAMLEN
     dp->d_name[dp->d_namlen] = 0;
#  endif
   file = dp->d_name;
   x->file = x->dir + x->dir_len;
   strcpy (x->file, dp->d_name);
   x->isdir = unix_is_dir(x->dir);
   
#endif
   /* exclude '.' and '..' */
   if (*file++ == '.')
     {
	if ((*file == 0) || 
	    ((*file == '.') && (*(file + 1) == 0))) x->isdir = -1;
     }
   return (x->dir);
}

char *sys_dir_findfirst(Sys_Dir_Type *x)
{
#ifdef unix
   return (sys_dir_findnext(x));
#endif
}

#define BUF_SIZE 4096

void grep_file(char *file, char *filename)
{
   if (Debug_Mode) return;
   if (Recursive_Match)
     {
	if (!SLang_regexp_match((unsigned char *) filename, strlen(filename), &recurse_reg)) return;
     }
   
   vfile_vp = vopen (file, BUF_SIZE, 0);
   if (vfile_vp != NULL)
     {
	grep(file);
	vclose(vfile_vp);
     }
   else fprintf(stderr, "rgrep: unable to read %s\n", file);
}

#define MAX_DEPTH 25
void grep_dir(char *dir)
{
   static int depth;
   Sys_Dir_Type x;
   char *file;
   
   if (NULL == sys_opendir(dir, &x)) return;
   if (depth >= MAX_DEPTH)
     {
	fprintf(stderr, "Maximum search depth exceeded.\n");
	return;
     }
   
   depth++;
   if (Debug_Mode) fprintf(stderr, "%s\n", dir);
   
   for (file = sys_dir_findfirst(&x); 
	file != NULL; file = sys_dir_findnext(&x))
     {
	if (x.isdir == 0) grep_file(file, x.file);
	else if (x.isdir == 1) grep_dir(file);
     }
   sys_closedir(&x);
   depth--;
}

   


int main(int argc, char **argv)
{
   unsigned char buf[256];
   unsigned char recurse_buf[256];
   int context; 
   char *file;
   
   argv++;
   argc--;
   
   while (argc && (**argv == '-') && *(*argv + 1))
     {
	if (!strcmp(*argv, "-R"))
	  {
	     argc--;
	     argv++;
	     if (!argc) usage();
	     recurse_reg.pat = (unsigned char *) *argv;
	     recurse_reg.buf = recurse_buf;
	     recurse_reg.buf_len = 256;
	     recurse_reg.case_sensitive = 1;
   
	     if (SLang_regexp_compile (&recurse_reg)) exit_error("Error compiling pattern.");
	     Do_Recursive = 1;
	     Recursive_Match = 1;
	  }
	else
	  {
	     parse_flags(*argv + 1);
	  }
	argv++; argc--;
     }
   
   if (!argc) usage();
   init_lut();
   
   reg.pat = (unsigned char *) *argv;
   reg.buf = buf;
   reg.buf_len = 256;
   reg.case_sensitive = Case_Sensitive;
   
   if (SLang_regexp_compile (&reg)) exit_error("Error compiling pattern.");
   argc--; argv++;

   Must_Match = 1;
   
   if (reg.osearch)
     {
        upcase_search_word(reg.pat);
     }
   else if (reg.must_match)
     {
	upcase_search_word(reg.must_match_str);
     }
   else Must_Match = 0;
   
	
   if (argc == 0)
     {
	vfile_vp = vstream(fileno(stdin), BUF_SIZE, 0);
	if (vfile_vp == NULL)
	  {
	     exit_error("Error vopening stdin.");
	  }
	grep("stdin");
	vclose(vfile_vp);
     }
   else
     {
	if (Do_Recursive || (argc != 1)) print_file_too = 1;
	while (argc--)
	  {
#ifdef unix
	     if (Do_Recursive && (1 == unix_is_dir (*argv))) grep_dir (*argv);
	     else grep_file(*argv, *argv);
#endif
	     argv++;
	  }
     }
   return (0);
}


/* ------------------------------------------------------------ */

#ifdef VMS

int vms_expand_filename(char *file,char *expanded_file)
{
    unsigned long status;
    static int context = 0;
    static char inputname[256] = "";
    $DESCRIPTOR(file_desc,inputname);
    $DESCRIPTOR(default_dsc,"SYS$DISK:[]*.*;");
    static struct dsc$descriptor_s  result =
	    {0, DSC$K_DTYPE_T, DSC$K_CLASS_D, NULL};

    if (strcmp(inputname, file))
      {
	  if (context)
	    {
		lib$find_file_end(&context);
	    }
	  context = 0;
	  strcpy(inputname, file);
	  file_desc.dsc$w_length = strlen(inputname);
      }

    if (RMS$_NORMAL == lib$find_file(&file_desc,&result,&context,
	           		     &default_dsc,0,0,&Number_Zero))
      {
	  MEMCPY(expanded_file, result.dsc$a_pointer, result.dsc$w_length);
	  expanded_file[result.dsc$w_length] = '\0';
          return (1);
      }
    else
      {
          /* expanded_file[0] = '\0'; */      /* so file comes back as zero width */
          return(0);
      }
}

static int context = 0;

static char inputname[256] = "";
$DESCRIPTOR(file_desc,inputname);
$DESCRIPTOR(default_dsc,"SYS$DISK:[]*.*;");

int sys_findnext(char *file)
{
   unsigned long status;
   static struct dsc$descriptor_s  result = {0, DSC$K_DTYPE_T, DSC$K_CLASS_D, NULL};

   if (RMS$_NORMAL == lib$find_file(&file_desc,&result,&context,
				    &default_dsc,0,0,&Number_Zero))
     {
	MEMCPY(file, result.dsc$a_pointer, result.dsc$w_length);
	file[result.dsc$w_length] = 0;
	return (1);
     }
   else return(0);
}

int sys_findfirst(char *file)
{
   char *file;
   strcpy(inputname, file);
   file_desc.dsc$w_length = strlen(inputname);
   if (context) lib$find_file_end(&context);
   context = 0;
   return sys_findnext(file);
}
#endif
/* VMS */
