/*
 *  Copyright (c) 1992, 1993 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"
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[132];
   Line *line;
   int key_len;
   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) end = line->data + Point - 1; else end = beg;
	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)
     {
	num = 1;
	tthis = CLine->next;
     }
   else
     {
	if (Point == 0) 
	  {
	     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);
}


int search_file(char *file, char *str)
{
   unsigned char work[256];
   int key_len, ret;
   unsigned char *end, *beg;
   VFILE *vp;
   unsigned int n;
   
   key_len = upcase_search_word(str, (char *) work, 1);
   if (!key_len) return(0);
   
   ret = 0;
   if (NULL == (vp = vopen(file, 0))) return(ret);
   
   while (NULL != (beg = (unsigned char *) vgets(vp, &n)))
     {
	end = beg + n;
	if (NULL != forw_search_region(beg, end, work, key_len))
	  {
	     ret = 1;
	     break;
	  }
     }
   vclose(vp);
   return(ret);
}

static SLRegexp_Type reg;

static int re_search_dir(unsigned char *pat, int dir)
{
   int psave, skip, n, epos, bpos, *ibp, *iep;
   unsigned char rbuf[512], *match, *save_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 (!search((char *) pat, dir, 0)) return (0);
	reg.bpos = Point;
	n = strlen((char *) pat);
	reg.epos = Point + n;
	return n;
     }

   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 == -1) 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.epos += Point;
		  reg.bpos += Point;
		  ibp = reg.beg_matches; iep = reg.end_matches;
		  while (*ibp != -1)
		    {
		       *ibp += Point; ibp++;
		       *iep += Point; iep++;
		    }
	       }
	  }
	else if (NULL != (match = SLang_regexp_match(CLine->data, Point, &reg)))
	  {
	     if (reg.epos > Point) match = NULL; 
	     else
	       {
		  /* found a match on line now find one closest to current point */
		  Point = 1;
		  save_match = match;
		  bpos = reg.bpos; epos = reg.epos;
		  while ((Point <= psave) && (match != save_match))
		    {
		       save_match = match;
		       match = SLang_regexp_match(CLine->data + Point, CLine->len - Point, &reg);
		       Point++;
		    }
		  match = save_match;
		  reg.bpos = bpos; reg.epos = epos;
	       }
	  }
	if (match != NULL)
	  {
	     Point = (int) (match - CLine->data);
	     n = (int) (reg.epos - reg.bpos);
	     if (n == 0) n = 1;
	     return (n);
	  }
	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;
   
   if ((reg.pat == NULL) || (reg.bpos == -1) || (reg.epos >= CLine->len)) return (0);
   
   if (*literal)
     {
	Point = reg.bpos;
	n = (int) (reg.epos - reg.bpos);
	generic_deln(&n);
	insert_string(s);
	return (1);
     }
   /* This is painful --- \& means whole expression, \x x = 0..9 means a 
      sub expression */
   
   /* must work with relative numbers since ins/del may do a realloc */
   Point = reg.epos;
   while ((ch = *s++) != 0)
     {
	if ((ch != '\\') || ((ch = *s++) == '\\'))
	  {
	     ins(ch);
	     continue;
	  }
	if (ch == 0) break;
	if ((ch >= '1') && (ch <= '9'))
	  {
	     nmax = ch - '1';
	     if ((n = reg.beg_matches[nmax]) == -1) continue;
	     nmax = reg.end_matches[nmax];
	  }
	else if (ch == '&')
	  {
	     n = reg.bpos;
	     nmax = reg.epos;
	  }
	else continue;
	
	while (n < nmax)
	  {
	     ins((char) *(CLine->data + n));
	     n++;
	  }
     }
   push_spot();
   Point = reg.bpos;
   n = (int) (reg.epos - reg.bpos);
   generic_deln(&n);
   pop_spot();
   return (1);
}


