/*
 * find_pos.c file contains following procedures:
 * 	find_string_pos_in_buffer
 *	find_line_pos
 *	textPosToLineColumn
 * They are used in the asedit program.
 *
 * Last changes: 14 April 1994
 *
 */

/*
 * Copyright 1991 - 1994,  Andrzej Stochniol, London, UK
 *
 * ASEDIT text editor, both binary and source (hereafter, Software) is
 * copyrighted by Andrzej Stochniol (hereafter, AS) and ownership remains
 * with AS.
 *
 * AS grants you (hereafter, Licensee) a license to use the Software
 * for academic, research and internal business purposes only, without a
 * fee.  Licensee may distribute the binary and source code (if released)
 * to third parties provided that the copyright notice and this statement
 * appears on all copies and that no charge is associated with such copies.
 *
 * Licensee may make derivative works.  However, if Licensee distributes
 * any derivative work based on or derived from the Software, then
 * Licensee will:
 * (1) notify AS regarding its distribution of the derivative work, and
 * (2) clearly notify users that such derivative work is a modified version
 *      and not the original ASEDIT distributed by AS.
 *
 * Any Licensee wishing to make commercial use of the Software should
 * contact AS to negotiate an appropriate license for such commercial use.
 * Commercial use includes:
 * (1) integration of all or part of the source code into a product for sale
 *     or license by or on behalf of Licensee to third parties, or
 * (2) distribution of the binary code or source code to third parties that
 *     need it to utilize a commercial product sold or licensed by or on 
 *     behalf of Licensee.
 *
 * A. STOCHNIOL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS
 * SOFTWARE FOR ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR
 * IMPLIED WARRANTY.  IN NO EVENT SHALL A. STOCHNIOL BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * By using or copying this Software, Licensee agrees to abide by the
 * copyright law and all other applicable laws, and the terms of this
 * license.
 * AS shall have the right to terminate this license immediately by
 * written notice upon Licensee's breach of, or non-compliance with, any
 * of its terms.  Licensee may be held legally responsible for any
 * copyright infringement that is caused or encouraged by Licensee's
 * failure to abide by the terms of this license.
 *
 *
 * 	Andrzej Stochniol	(A.Stochniol@ic.ac.uk)
 * 	30 Hatch Road
 * 	London SW16 4PN
 * 	UK
 */


#include <stdio.h>
#include <string.h>
#include <ctype.h>		/* to define toupper function */
#include <X11/Intrinsic.h>	/* defines Boolean type */


/* find_string_pos_in_buffer returns position of the found string in the character buffer;
   if the text has not been found (or in case of error) returns negative number;
   The search is started from startpos and is carried forward when "forward"
   is True (when "forward" is False the search direction is backward).
   The search is case sensitive for case_sensitive=True and only whole
   words are found when whole_words_only=True.
   The startpos and endpos should contain the range where the string should
   be searched for. When startpos or endpos is < 0 the default value will be
   assigned for it inside the procedure (i.e. beginnning or end of the file );
 */

#ifdef _NO_PROTO
long find_string_pos_in_buffer(searchstring, buf,
			startpos, endpos, forward,
			case_sensitive, whole_words_only)
char *searchstring;
char *buf;
long startpos;
long endpos;
Boolean forward;
Boolean case_sensitive;
Boolean whole_words_only;
#else  /* _NO_PROTO */
long find_string_pos_in_buffer(char *searchstring, char *buf,
			long startpos, long endpos, Boolean forward,
			Boolean case_sensitive, Boolean whole_words_only)
#endif
{
    long pos = -1L;
    long searchlen = strlen(searchstring);	/* length of the search string
						   (should be > 0)*/
    long buf_size = (long) strlen(buf);
    register long i, n;
    char *s1, *s2;
    int  c1, c2;	/* integers used for not case-sensitive search (toupper
				is defined for integers; we use them to speed
				the process of search)*/

    if(searchlen <= 0L) return(-3L);
    if(buf_size <= 0L)      return(-2L);

    /* setting the default value for negative startpos and/or endpos */
    if(startpos < 0L)
    {
	if(forward) startpos = 0L;
	else	    startpos = buf_size;
    }
    if(endpos < 0L)
    {
	if(forward) endpos   = buf_size;
	else	    endpos   = 0L;
    }

    /* test (and correct) the search range */
    if(endpos > buf_size)     endpos=buf_size;
    if(startpos > buf_size) startpos=buf_size;

    /* there are two major cases case-sensitive and not (they are split to
       speed the process of the search */
    if(case_sensitive)
    {
      if(forward)
      {
	/* search forward starting from startpos position */

	for( i = startpos; i < endpos; i++)
	{
	  n = searchlen;
	  s1 = &buf[i];
	  s2 = searchstring;
	  while (--n >= 0L && *s1++ == *s2++);
	  if(n < 0L)
	  {
		pos = i;
		/* additional check for whole words only - check both ends*/
		if(whole_words_only)
		{
			if((i>0L && (isalnum((unsigned char)buf[i-1]) || buf[i-1] == '_')) ||
			    (i+searchlen<buf_size && (isalnum((unsigned char)buf[i+searchlen]) || buf[i+searchlen] == '_') ))
				pos = -1;	/* in one or another end there is a letter or digit 
					   or underscore - do not use that occurence */
			else	break;	/* on both ends there is no letter and no digit */
		}
		else break; /* string found: now "i" shows position of the search string
				in the buffer relatively to the buffer beginning */
	  }
	}
      }
      else
      {
	/* search backward starting from startpos-1 position */
	for( i = startpos-1; i >= endpos; i--)
	{
	  n = searchlen;
	  s1 = &buf[i];
	  s2 = searchstring;
	  while (--n >= 0L && *s1++ == *s2++);
	  if(n < 0L)
	  {
		pos = i;
		if(whole_words_only)  	/* as above ... */
		{
			if((i>0L && (isalnum((unsigned char)buf[i-1]) || buf[i-1] == '_')) ||
			    (i+searchlen<buf_size && (isalnum((unsigned char)buf[i+searchlen]) || buf[i+searchlen] == '_') ) )
				pos = -1;
			else	break;
		}
		else break; /* string found: now "i" shows position of the search string
				in the buffer relatively to the buffer beginning */
	  }
	}
      }
    }
    else
    {	/* not case-sensitive ... */
      if(forward)
      {
	/* search forward starting from startpos position */

	for( i = startpos; i < endpos; i++)
	{
	  n = searchlen;
	  s1 = &buf[i];
	  s2 = searchstring;
	  c1 = toupper((unsigned char)*s1);	c2=toupper((unsigned char)*s2);
	  while (--n >= 0L && c1 == c2)
	  {
		c1 = toupper((unsigned char) *(++s1));
		c2 = toupper((unsigned char) *(++s2));
	  }
	  if(n < 0L)
	  {
		pos = i;
		if(whole_words_only)  	/* as above ... */
		{
			if((i>0L && (isalnum((unsigned char)buf[i-1]) || buf[i-1] == '_')) ||
			    (i+searchlen<buf_size && (isalnum((unsigned char)buf[i+searchlen]) || buf[i+searchlen] == '_') ))
				pos = -1;	/* in one end or another there is a letter, or a digit
					   or the underscore - do not use that occurence */
			else	break;	/* on both ends there is no letter and no digit */
		}
		else break;
	  }
	}
      }
      else
      {
	/* search backward starting from startpos-1 position */
	for( i = startpos-1; i >= endpos; i--)
	{
	  n = searchlen;
	  s1 = &buf[i];
	  s2 = searchstring;
	  c1 = toupper((unsigned char)*s1);	c2=toupper((unsigned char)*s2);
	  while (--n >= 0L && c1 == c2)
	  {
		c1 = toupper((unsigned char) *(++s1));
		c2 = toupper((unsigned char) *(++s2));
	  }
	  if(n < 0L)
	  {
		pos = i;
		if(whole_words_only)  	/* as above ... */
		{
			if((i>0L && (isalnum((unsigned char)buf[i-1]) || buf[i-1] == '_')) ||
			    (i+searchlen<buf_size && (isalnum((unsigned char)buf[i+searchlen]) || buf[i+searchlen] == '_') ) )
				pos = -1;
			else	break;
		}
		else break;
	  }
	}
      }

    }
    /****** TEMP  - for backward DEBUG!!!!!!! 
    	fprintf(stderr, "find_string_pos_in_buffer, startpos , endpos, pos: %ld %ld %ld \n", startpos, endpos, pos);
    *******/
    return(pos);

}   /* find_string_pos_in_buffer */



/* find_line_pos returns position of the line in the character buffer;
   if such a line has not been found - returns -1 and the maximum
   number of lines in the buffer buf is passed in found_lines  for message
   purposes
 */

#ifdef _NO_PROTO
long find_line_pos(line, buf, found_lines)
long line;
char *buf;
long *found_lines;
#else  /* _NO_PROTO */
long find_line_pos(long line, char *buf, long *found_lines)
#endif
{
    long pos = -1L;
    char new_line = '\n';
    long size = strlen(buf);
    register long i;

    *found_lines = 0L;

    if(line == 1L)
	pos = 0L;
    else
    {
	for( i = 0L; i < size; i++)
	{
	  if(buf[i] == new_line) (*found_lines)++;
	  if(*found_lines == (line - 1L))	/* we are on the line ... */
		{pos = i + 1L; break; }		/* position of the first character of
						   that line */
	}
	/* extra check for the last line (if the last character was different from
	   line feed we have one extra line, because the last line did not end with
	   the line feed) */
	if(buf[size-1L] != new_line) (*found_lines)++;
    }
    return(pos);
}   /* find_line_pos */


/* textPosToLineColumn converts the text position in the buffer to the
   appropriate line and column values; buf_size was specially introduce
   to allow using this procedure for character buffers which may be not
   NULL terminated but with known size (usually buf_size is simply calculated
   as (long) strlen(buf) for NULL terminated strings).
   line -   returns number of lines
   column - returns column position (including an effect of the tab character(s))
   column_ntab - returns number of characters up to the column + 1 (tab is 1 "character",
		 so we simply disregard tab effect)
 */


#ifdef _NO_PROTO
void textPosToLineColumn(buf, buf_size, pos, tab_size, line, column, column_ntab)
char *buf;
long buf_size;
long pos;
int  tab_size;
long *line;
long *column;
long *column_ntab;
#else  /* _NO_PROTO */
void textPosToLineColumn(char *buf, long buf_size, long pos, int tab_size, long *line, long *column,
		long *column_ntab)
#endif
{
    char new_line = '\n';
    char tab_char = '\t';

    register long i, col;
    long latest_lf_pos = -1L;	/* remeber the position of the latest found line feed */
				/* in Motif XmTextPosition is defined as long ... */
    /* tab_size  		specifies how many characters will move the cursor
				   for each tab stop character (default - 8;
				   for programmers editors usually between 2 and 16)
				   This value should be exactly the same as used
				   internally in the text widget  */


    *line = 1L;
    *column_ntab = *column = 1L;

    if(pos <= 0L)   return;
    else
    {
	for( i = 0L; i < buf_size; i++)
	{

	  if(i == pos) break;   /* we have reached the right position */
	  if(buf[i] == new_line)
	  { (*line)++;
	   latest_lf_pos = i; }

	}
	/* setting the column number  - version without taking into account tab characters*/
	*column_ntab = *column = pos - latest_lf_pos;


	/* let's find column with tab characters taken into account ... */
	col = 1L;
	for( i = latest_lf_pos+1L; i <pos; i++)
	{
	  if(buf[i] == tab_char) col = ((col+tab_size-1)/tab_size)*tab_size + 1L;
	  else			 col++;
	}
	*column = col;
    }
    return;
}   /* textPosToLineColumn */


/* textPosToLineColumns converts the text position in the buffer to the
   appropriate line and column values; buf_size was specially introduce
   to allow using this procedure for character buffers which may be not
   NULL terminated but with known size (usually buf_size is simply calculated
   as (long) strlen(buf) for NULL terminated strings).
 */


#ifdef _NO_PROTO
void back_textPosToLineColumn(buf, buf_size, pos, tab_size, line, column, column_ntab)
char *buf;
long buf_size;
long pos;
int  tab_size;
long *line;
long *column;
long *column_ntab;
#else  /* _NO_PROTO */
void back_textPosToLineColumn(char *buf, long buf_size, long pos, int tab_size, long *line, long *column,
		long *column_ntab)
#endif
{
    char new_line = '\n';
    char tab_char = '\t';

    register long i, j, col;
    long latest_lf_pos = -1L;   /* remeber the position of the latest found line feed */
				/* in Motif XmTextPosition is defined as long ... */

    *line = 0L;
    *column_ntab = *column = 1L;

    if(pos > buf_size)   return;
    else
    {
	for( i = buf_size; i >= 0L; i--)
	{

          if(buf[i] == new_line)
		(*line)++;		/* count the number of skipped lines */
	  if(i == pos) break;   /* we have reached the right position */

        }

	/* go backward untill you find a new_line ... */

	for(j=i-1; j>= 0L; j--)
	{
	    if(buf[j] == new_line)
	    {
		latest_lf_pos = j;
		break;
	    }
	}
	/* setting the column number  - version without taking into account tab characters*/
	*column_ntab = *column = pos - latest_lf_pos;

	/* let's find column with tab characters taken into account ... */
	col = 1L;
	for( i = latest_lf_pos+1L; i <pos; i++)
	{
	  if(buf[i] == tab_char) col = ((col+tab_size-1)/tab_size)*tab_size + 1L;
	  else                   col++;
	}
	*column = col;
    }
    return;
}   /* back_textPosToLineColumn */



