/*
 *  Copyright (c) 1992, 1994 John E. Davis  (davis@amy.tch.harvard.edu)
 *  All Rights Reserved.
 */
#include <stdio.h>
#include <string.h>
#include "slang.h"
#include "config.h"
#include "buffer.h"
#include "vfile.h"
#include "search.h"
#include "misc.h"
#include "ins.h"
#include "paste.h"
#include "ledit.h"
#include "mem.h"
int Case_Sensitive = 0;

#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;
     }
}

unsigned char *back_search_region(unsigned char *beg,unsigned char *end,
				  unsigned char *key, int key_len)
{
   unsigned char ch, char1;
   int j, str_len, ofs, cs = Case_Sensitive;

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

    char1 = key[0];

    while(1)
      {
	 while ((beg <= end) && (ch = *end, ch = upcase(ch), ch != char1))
	   {
	      ofs = ind[(unsigned char) ch];
#ifdef msdos
	      /* This is needed for msdos segment wrapping problems */
	      if (beg + ofs > end) return(NULL);
#endif
	      end -= ofs;
	   }
	 if (beg > end) return(NULL);
	 for (j = 1; j < key_len; j++)
	   {
	      ch = upcase(end[j]);
	      if (ch != key[j]) break;
	   }
	 if (j == key_len) return(end);
	 end--;
      }
}

static int upcase_search_word(char *str, char *work, int dir)
{
   int i, maxi;
   int cs = Case_Sensitive;
   register int max = strlen(str);
   char *w;
   register int *indp, *indpm;
   
   
   if (dir > 0) 
     {
	w = work;
     }
   else
     {
	maxi = max - 1;
	str = str + maxi;
	w = work + maxi;
     }
   
   /* for (i = 0; i < 256; i++) ind[i] = max; */
   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 += dir; w += dir;
     }
   work[max] = 0;
   return(max);
}

int search(char *str, int dir, int n)
{
   unsigned char *beg, *end, *p, work[256];
   Line *line;
   int key_len, tmp;
   unsigned int num = 0;

   key_len = upcase_search_word(str, (char *) work, dir);
   if (!key_len) return(0);
   line = CLine;

   if (dir == 1)
     {
	beg = line->data + Point;
	end = line->data + line->len;
	do
	  {
	     if (NULL !=
		 (p = forw_search_region(beg, end, (unsigned char *) work,
					 key_len)))
	       {
		  CLine = line;
		  LineNum += num;
		  Point = (int) (p - line->data);
		  return(1);
	       }
	     line = line->next; num++;
	     if (line == NULL) return(0);
	     beg = line->data;
	     end = line->data + line->len;
	  }
	while(--n);
     }
   else if (dir == -1)
     {
	beg = line->data - 1;
	if (Point == 0) end = beg;
	else
	  {
	     tmp = Point + key_len - 1;
	     if (tmp > line->len) tmp = line->len;
	     end = line->data + tmp - 1;
	  }
	
	do
	  {
	     if (NULL !=
		 (p = back_search_region(beg, end, (unsigned char *) work,
					 key_len)))
	       {
		  CLine = line;
		  LineNum -= num;

		  Point = (int) (p - line->data);
		  return(1);
	       }
	     line = line->prev;
	     num++;
	     if (line == NULL) return(0);
	     beg = line->data;
	     end = line->data + line->len;
	  }
	while (--n);
     }
   return(0);
}

int search_forward(char *s)
{
   return( search(s, 1, 0));
}
int search_backward(char *s)
{
   return( search(s, -1, 0));
}

int forward_search_line(char *s)
{
   return( search(s, 1, 1));
}

int backward_search_line(char *s)
{
   return( search(s, -1, 1));
}

int bol_search(char *str, int dir)
{
   Line *tthis;
   int max, n, cs = Case_Sensitive;
   unsigned int num;
   unsigned char *s, ch1, ch2;
   
   max = strlen(str);
   if ((dir > 0) && Point)
     {
	num = 1;
	tthis = CLine->next;
     }
   else if ((dir < 0) && !Point)
     {
	tthis = CLine->prev;
	num = 1;
     }
   else
     {
	tthis = CLine;
	num = 0;
     }   
	
   while (tthis != NULL)
     {
	if (max <= tthis->len)
	  {
	     s = tthis->data;
	     for (n = 0; n < max; n++)
	       {
		  ch1 = upcase(s[n]);
		  ch2 = upcase(str[n]);
		  if (ch1 != ch2) break;
	       }
	     if (n == max)
	       {
		  Point = 0;
		  CLine = tthis;
		  if (dir > 0) LineNum += num; else LineNum -= num;
		  return(1);
	       }
	  }
	num++;
	if (dir > 0) tthis = tthis->next; else tthis = tthis->prev;
     }
   return(0);
}

int bol_fsearch(char *s)
{
   return bol_search(s, 1);
}

int bol_bsearch(char *s)
{
   return bol_search(s, -1);
}


static SLRegexp_Type reg;

static int re_search_dir(unsigned char *pat, int dir)
{
   int psave, skip, n, epos;
   unsigned char rbuf[512], *match;
   Line *l;
   
   reg.case_sensitive = Case_Sensitive;
   reg.buf = rbuf;
   reg.pat = pat;
   reg.buf_len = 512;
   
   if (SLang_regexp_compile(&reg)) 
     {
	msg_error("Unable to compile pattern.");
	return(0);
     }
   
   if (reg.osearch)
     {
	if (reg.must_match_bol)
	  {
	     if (!bol_search((char *) pat, dir)) return (0);
	  }
	else if (!search((char *) pat, dir, 0)) return (0);
	
	reg.beg_matches[0] = Point;
	n = strlen((char *) pat);
	reg.end_matches[0] = n;
	return n + 1;
     }

   if (reg.must_match_bol)
     {
	if (dir < 0) 
	  {
	     if (Point == 0)
	       {
		  if (!backwchars(&Number_One)) return 0;
	       }
	  }
	else if (Point)
	  {
	     if (CLine->next == NULL) return (0);
	     CLine = CLine->next; LineNum++; Point = 0;
	  }
     }
   
   
   if (reg.must_match && (0 != reg.must_match_str[1])) skip = 0; else skip = 1;
   while (1)
     {
	psave = Point; 
	if (!skip)
	  {
	     l = CLine;
	     if (!search((char *) reg.must_match_str, dir, 0)) return (0);

	     if (l != CLine)
	       {
		  if (dir < 0) eol(); else Point = 0;
		  /* if ((dir == -1) && (!reg.must_match_bol)) eol(); else Point = 0; */
		  psave = Point;
	       }
	  }
	
	Point = psave;
	if (dir == 1)
	  {
	     match = SLang_regexp_match(CLine->data + Point, CLine->len - Point, &reg);
	     if (match != NULL)
	       {
		  /* adjust offsets */
		  reg.offset = Point;
	       }
	  }
	else if (NULL != (match = SLang_regexp_match(CLine->data, CLine->len, &reg)))
	  {
	     if (Point && (reg.beg_matches[0] >= Point)) match = NULL;
	     else
	       {
		  epos = Point - 1;
		  /* found a match on line now find one closest to current point */
		  while (epos >= 0)
		    {
		       match = SLang_regexp_match(CLine->data + epos, CLine->len - epos, &reg);
		       if (match != NULL) break;
		       epos--;
		    }
	       }
	  }
	if (match != NULL)
	  {
	     Point = (int) (match - CLine->data);
	     n = reg.end_matches[0];
	     return (n + 1);
	  }
	if (dir > 0)
	  {
	     if (CLine->next == NULL) break;
	     CLine = CLine->next; LineNum++; Point = 0;
	  }
	else 
	  {
	     if (CLine->prev == NULL) break;
	     CLine = CLine->prev; LineNum--;
	     eol ();
	  }
     }
   return (0);
}

int re_search_forward(char *pat)
{
   int n, p, len;
   Line *l;
   
   p = Point; n = LineNum; l = CLine;
   if (0 != (len = re_search_dir((unsigned char *) pat, 1))) return (len);
   Point = p; LineNum = n; CLine = l;
   return (0);
}

int re_search_backward(char *pat)
{
   int n, p, len;
   Line *l;
   
   p = Point; n = LineNum; l = CLine;
   if (0 != (len = re_search_dir((unsigned char *) pat, -1))) return (len);
   Point = p; LineNum = n; CLine = l;
   return (0);
}

int replace_match(char *s, int *literal)
{
   int n, nmax;
   char ch;
   unsigned char *p;
   
   if ((reg.pat == NULL) || (reg.beg_matches[0] == -1) 
       || (reg.end_matches[0] + reg.beg_matches[0] + reg.offset >= CLine->len)) return (0);
   
   if (*literal)
     {
	Point = reg.beg_matches[0] + reg.offset;
	n = reg.end_matches[0];
	generic_deln(&n);
	insert_string(s);
	return (1);
     }
   /* This is painful --- \& means whole expression, \x x = 1..9 means a 
      sub expression */
   
   /* must work with relative numbers since ins/del may do a realloc */
   Point = reg.end_matches[0] + reg.offset + reg.beg_matches[0];
   while ((ch = *s++) != 0)
     {
	if ((ch != '\\') || ((ch = *s++) == '\\'))
	  {
	     if (ch != '\n') ins(ch);
	     /* Note that I should do a 'newline' call.  However, as soon as
	        I do this, I lose the nth_match strings.  Clearly, I need to
		re-think this!  */
	     continue;
	  }
	if (ch == 0) break;
	if (ch == '&') ch = '0';
	
	if ((ch >= '0') && (ch <= '9'))
	  {
	     nmax = ch - '0';
	     if ((n = reg.beg_matches[nmax]) == -1) continue;
	     nmax = reg.end_matches[nmax] + reg.beg_matches[nmax];
	  }
	else continue;
	
	p = CLine->data + reg.offset;
	while (n < nmax)
	  {
	     ins((char) *(p + n));
	     n++;
	  }
     }
   push_spot();
   Point = reg.beg_matches[0] + reg.offset;
   n = reg.end_matches[0];
   generic_deln(&n);
   pop_spot();
   return (1);
}


static int push_string(char *b, int n)
{
   char *s;
   s = (char *) MALLOC(n + 1);
   if (s == NULL)
     {
	SLang_Error = SL_MALLOC_ERROR;
	return (0);
     }
   if (n) MEMCPY(s, b, n);
   *(s + n) = 0;
   
   SLang_push_malloced_string (s);
   return (SLang_Error == 0);
}
      
void regexp_nth_match (int *np)
{
   int b = 0, n = *np;
   
   if ((reg.pat == NULL) || (reg.beg_matches[0] == -1)
       || (reg.end_matches[0] + reg.beg_matches[0] + reg.offset >= CLine->len))
     {
	SLang_Error = UNKNOWN_ERROR;
	return;
     }

   if ((n <= 0) || (n > 9)) n = 0;
   else
     {
	if ((b = reg.beg_matches[n]) == -1) n = 0;
	else
	  {
	     n = reg.end_matches[n];
	  }
     }
   b += reg.offset;
   push_string((char *) CLine->data + b, n);
}



int search_file(char *file, char *pat, int *np)
{
   unsigned char rbuf[512], *buf;
   unsigned char search_buf[512];
   unsigned int n;
   VFILE *vp;
   int n_matches = 0, n_max = *np, key_len = 1;
   
   reg.case_sensitive = Case_Sensitive;
   reg.buf = rbuf;
   reg.pat = (unsigned char *) pat;
   reg.buf_len = 512;
   
   if (SLang_regexp_compile(&reg)) 
     {
	msg_error("Unable to compile pattern.");
	return(0);
     }
   
   if (reg.osearch)
     {
        key_len = upcase_search_word((char *) reg.pat, (char *) search_buf, 1);
     }
   else if (reg.must_match)
     {
	key_len = upcase_search_word((char *) reg.must_match_str, (char *) search_buf, 1);
     }

   if (!key_len) return 0;
   if (NULL == (vp = vopen(file, 0, VFILE_TEXT))) return(0);
   while (NULL != (buf = (unsigned char *) vgets(vp, &n)))
     {
	if (reg.must_match)
	  {
	     if (key_len > n) continue;
	     if (NULL == forw_search_region(buf, buf + n, search_buf, key_len))
	       {
		  continue;
	       }
	     if (reg.osearch) 
	       {
		  goto match_found;
	       }
	  }
	
	if (!SLang_regexp_match(buf, (int) n, &reg)) continue;
	
	match_found:
	n_matches++;
	n_max--;
	
	if (!push_string((char *) buf, n) || (n_max == 0)) break;
     }
   vclose(vp);
   return(n_matches);
}

