/* -*- mode: C; mode: fold; -*- */
/* Copyright (c) 1992, 1998, 2000, 2002 John E. Davis
 * This file is part of JED editor library source.
 *
 * You may distribute this file under the terms the GNU General Public
 * License.  See the file COPYING for more information.
 */
#include "config.h"
#include "jed-feat.h"

/*{{{ Include Files */

#include <stdio.h>
#include <slang.h>

#include "jdmacros.h"

#include <string.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;
static SLsearch_Type Search_Struct;

int search(char *str, int dir, int n) /*{{{*/
{
   unsigned char *beg, *end, *p;
   Line *line;
   int key_len;
   unsigned int num = 0;

   if ((key_len = SLsearch_init (str, dir, Case_Sensitive, &Search_Struct)) <= 0)
     return 0;
   
   line = CLine;

   if (dir == 1)
     {
	beg = line->data + Point;
	end = line->data + line->len;
	do
	  {
	     if (NULL != (p = SLsearch (beg, end, &Search_Struct)))
	       {
		  CLine = line;
		  LineNum += num;
		  Point = (int) (p - line->data);
		  return key_len;
	       }
	     line = line->next; num++;
	     if (line == NULL) return 0;
	     beg = line->data;
	     end = line->data + line->len;
	  }
	while (--n);
     }
   else if (dir == -1)
     {
	end = beg = line->data;
	if (Point)
	  {
	     int tmp = Point - 1 + key_len;
	     if (tmp > line->len) tmp = line->len;
	     end += tmp;
	  }
	
	do
	  {
	     if (NULL != (p = SLsearch (beg, end, &Search_Struct)))
	       {
		  CLine = line;
		  LineNum -= num;

		  Point = (int) (p - line->data);
		  return (key_len);
	       }
	     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_fsearch (char *str) /*{{{*/
{
   Line *tthis;
   int max, cs = Case_Sensitive;
   unsigned int num;
   unsigned char ch;

   if (Point)
     {
	tthis = CLine->next;
	if (tthis == NULL)
	  return 0;
	num = 1;
     }
   else
     {
	tthis = CLine;
	num = 0;
     }   
   
   max = strlen (str);
   if (max == 0)
     goto return_success;

   ch = str[0];
   
   if (cs != 0)
     {
	while (tthis != NULL)
	  {
	     if ((tthis->len >= max)
		 && (*tthis->data == ch))
	       {
		  int n;
		  unsigned char *s;

		  s = tthis->data;
		  n = 1;
		  while (1)
		    {
		       if (n == max)
			 goto return_success;
		       
		       if (s[n] != str[n])
			 break;
		       n++;
		    }
	       }
	     num++;
	     tthis = tthis->next;
	  }
	return 0;
     }
   
   /* Here we have the case-insensitive match */
   while (tthis != NULL)
     {
	if (tthis->len >= max)
	  {
	     unsigned char *s = tthis->data;
	     int n = 0;
	     
	     while (UPPER_CASE(s[n]) == UPPER_CASE(str[n]))
	       {
		  n++;
		  if (n == max)
		    goto return_success;
	       }
	  }
	num++;
	tthis = tthis->next;
     }
   return 0;
   
   return_success:
   Point = 0;
   LineNum += num;
   CLine = tthis;
   return max;
}

/*}}}*/

int bol_bsearch (char *str) /*{{{*/
{
   Line *tthis;
   int max, n, cs = Case_Sensitive;
   unsigned int num;
   unsigned char ch;

   if (Point == 0)
     {
	if (NULL == (tthis = CLine->prev))
	  return 0;
	num = 1;
     }
   else
     {
	tthis = CLine;
	num = 0;
     }   
   
   max = strlen (str);
   if (max == 0)
     {
	Point = 0;
	LineNum -= num;
	CLine = tthis;
	return 0;
     }

   ch = str[0];
   if (cs == 0) ch = UPPER_CASE(ch);

   while (tthis != NULL)
     {
	unsigned char *s;

	if (tthis->len < max)
	  {
	     num++;
	     tthis = tthis->prev;
	     continue;
	  }
	s = tthis->data;

	if ((*s != ch)
	    && ((cs != 0)
		|| (ch != UPPER_CASE(*s))))
	  {
	     num++;
	     tthis = tthis->prev;
	     continue;
	  }
	     
	if (cs) 
	  {
	     for (n = 1; n < max; n++)
	       {
		  if (s[n] != str[n])
		    break;
	       }
	  }
	else
	  {
	     for (n = 1; n < max; n++)
	       {
		  if (UPPER_CASE(s[n]) != UPPER_CASE(str[n]))
		    break;
	       }
	  }
	
	if (n == max)
	  {
	     Point = 0;
	     CLine = tthis;
	     LineNum -= num;
	     return max;
	  }
	  
	num++;
	tthis = tthis->prev;
     }
   return 0;
}

/*}}}*/

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;
   reg.offset = 0;

   if (SLang_regexp_compile(&reg)) 
     {
	msg_error("Unable to compile pattern.");
	return(0);
     }
   
   if (reg.osearch)
     {
	if (reg.must_match_bol)
	  {
	     if (dir > 0)
	       {
		  if (0 == bol_fsearch ((char *) pat))
		    return 0;
	       }
	     else if (0 == bol_bsearch ((char *) pat))
	       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,
						     Point, /* was CLine->len */
						     &reg)))
	  {
	     if (Point && (reg.beg_matches[0] >= Point)) match = NULL;
	     else if (reg.must_match_bol == 0)
	       {
		  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, 
						  Point - epos, /* was: CLine->len - epos, */
						  &reg);
		       if (match != NULL) 
			 {
			    reg.offset = epos;
			    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.beg_matches[0] + reg.offset + reg.end_matches[0] >= (unsigned int) CLine->len)) 
     return 0;
   
   if (*literal)
     {
	Point = reg.beg_matches[0] + reg.offset;
	n = reg.end_matches[0];
	if (-1 == jed_generic_deln (n))
	  return -1;
	jed_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') jed_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;
	
	while (n < nmax)
	  {
	     p = CLine->data + reg.offset;
	     (void) jed_ins((char) *(p + n));
	     n++;
	  }
     }
   push_spot();
   Point = reg.beg_matches[0] + reg.offset;
   n = reg.end_matches[0];
   (void) jed_generic_deln (n);
   pop_spot();
   return (1);
}

/*}}}*/

static int push_string(char *b, int n) /*{{{*/
{
   char *s;
#if 1
   /* Right now, slang cannot handle strings containing ASCII 0.  Thus,
    * use a simple string interface.
    */
   s = SLang_create_nslstring (b, n);
   if (s != NULL)
     {
	SLang_push_string (s);
	SLang_free_slstring (s);
     }
#else
   s = SLmalloc (n + 1);
   if (s == NULL)
     return 0;

   if (n) SLMEMCPY(s, b, n);
   *(s + n) = 0;
   
   SLang_push_malloced_string (s);
#endif
   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.beg_matches[0] + reg.offset + reg.end_matches[0] > (unsigned int) CLine->len))
     {
	SLang_Error = SL_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 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 = SLsearch_init ((char *) reg.pat, 1, reg.case_sensitive, &Search_Struct);
     }
   else if (reg.must_match)
     {
	key_len = SLsearch_init ((char *) reg.must_match_str, 1, reg.case_sensitive, &Search_Struct);
     }

   if (key_len <= 0) 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 ((unsigned int) key_len > n) continue;
	     if (NULL == SLsearch (buf, buf + n, &Search_Struct))
	       {
		  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);
}

/*}}}*/

int insert_file_region (char *file, char *rbeg, char *rend) /*{{{*/
{
   VFILE *vp;
   unsigned int n;
   unsigned int len = (unsigned int) strlen (rbeg);
   int num = 0;
   unsigned char *buf;
   
   if (NULL == (vp = vopen(file, 0, VFILE_TEXT))) return (-1);
   
   while (NULL != (buf = (unsigned char *) vgets(vp, &n)))
     {
	if ((len == 0) || 
	    ((n >= len) && !strncmp ((char *) buf, rbeg, len)))
	  {
	     Suspend_Screen_Update = 1;
	     if (-1 == jed_quick_insert (buf, (int) n))
	       {
		  vclose (vp);
		  return -1;
	       }

	     num++;
	     
	     len = (unsigned int) strlen (rend);
	     
	     while (NULL != (buf = (unsigned char *) vgets(vp, &n)))
	       {
		  if (len &&
		      ((n >= len) && !strncmp ((char *) buf, rend, len)))
		    break;

		  if (-1 == jed_quick_insert(buf, (int) n))
		    {
		       vclose (vp);
		       return -1;
		    }

		  if (SLang_Error) break;
		  num++;
	       }
	     break;
	  }
     }
   vclose (vp);
   return num;
}

/*}}}*/

