/* wvfilter.c
 * author: Tom Menshik <tmenshik@hpcvitlm.cv.hp.com>
 *
 * $Id: wvfilter.c 1.3 1995/01/11 21:35:26 rushing Exp $
 */
#include <windows.h>
#include <windowsx.h>
#include "wvglob.h"
#include "winvn.h"
#pragma hdrstop

#include <stdio.h>
#include <string.h>
#include <ctype.h>

/*****************************************************************************/
/*  function: groupok                                                        */
/*  author: Tom Menshik                                                      */
/*  date  : 12/10/93                                                         */
/*  desc: Parse the filterBuf extracting keywords. If any keyword or         */
/*        keyword/wildcard appears in the news group, then return TRUE.      */
/*        Otherwise the group does not qualify and so return FALSE.          */
/*                                                                           */
/*        The token, '|', is used to separate keyword strings.               */
/*        The token, '*', is used as a wildcard. The wildcard can only       */
/*        appear before or after the keyword.                                */
/*                                                                           */
/*        For example, this string, "rec.*|*fun*|*windsurfing" would allow   */
/*        any group that started with "rec.", had the word "fun" in it, or   */
/*        ended with the word "windsurfing".                                 */
/*                                                                           */
/*  returns: TRUE : iff the group passes through the group filter.           */
/*           FALSE: otherwise.                                               */
/*****************************************************************************/

/* modified jsc 10/25/94.  made more robust (avoid string overruns)
 * added ability to negate pattern by placing ! as first character
 * i.e. !*sports*|*   (left-to-right order implied)
 */

#define DELIMETER '|'
#define NEGATE_PATTERN '!'
#define MAXKEYLEN 256
int wildmat (char *text, char *p);

int
MatchFilter (char *group, char *filterBuf)
{
  char keyword[MAXKEYLEN];
  int i, j;

  /* add a delimter to the end of the buffer if there is none */
  i = strlen (filterBuf);
  if (filterBuf[i - 1] != DELIMETER) {
	filterBuf[i] = DELIMETER;
	filterBuf[i + 1] = '\0';
  }

  i = 0;
  j = 0;
  while (filterBuf[i] != '\0' && j < MAXKEYLEN) {
	if (filterBuf[i] == DELIMETER) {
	  keyword[j] = '\0';
	  if (keyword[0] == NEGATE_PATTERN) {
		if (wildmat (group, &keyword[1])) {
		  return (FALSE);
		}
	  }
	  else {
		if (wildmat (group, keyword)) {
		  return (TRUE);
		}
	  }
	  i++;
	  j = 0;
	  memset (keyword, 0, MAXKEYLEN);
	}
	else {
	  keyword[j] = filterBuf[i];
	  i++;
	  j++;
	}
  }

  return (FALSE);
}

#define AbORT -1

/*  Revision: 1.9
   **
   **  Do shell-style pattern matching for ?, \, [], and * characters.
   **  Might not be robust in face of malformed patterns; e.g., "foo[a-"
   **  could cause a segmentation violation.  It is 8bit clean.
   **
   **  Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
   **  Rich $alz is now <rsalz@osf.org>.
   **  April, 1991:  Replaced mutually-recursive calls with in-line code
   **  for the star character.
   **
   **  Special thanks to Lars Mathiesen <thorinn@diku.dk> for the AbORT code.
   **  This can greatly speed up failing wildcard patterns.  For example:
   **   pattern: -*-*-*-*-*-*-12-*-*-*-m-*-*-*
   **   text 1:  -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1
   **   text 2:  -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1
   **  Text 1 matches with 51 calls, while text 2 fails with 54 calls.  Without
   **  the AbORT code, it takes 22310 calls to fail.  Ugh.  The following
   **  explanation is from Lars:
   **  The precondition that must be fulfilled is that DoMatch will consume
   **  at least one character in text.  This is true if *p is neither '*' nor
   **  '\0'.)  The last return has AbORT instead of FALSE to avoid quadratic
   **  behaviour in cases like pattern "*a*b*c*d" with text "abcxxxxx".  With
   **  FALSE, each star-loop has to run to the end of the text; with AbORT
   **  only the last one does.
   **
   **  Once the control of one instance of DoMatch enters the star-loop, that
   **  instance will return either TRUE or AbORT, and any calling instance
   **  will therefore return immediately after (without calling recursively
   **  again).  In effect, only one star-loop is ever active.  It would be
   **  possible to modify the code to maintain this context explicitly,
   **  eliminating all recursive calls at the cost of some complication and
   **  loss of clarity (and the AbORT stuff seems to be unclear enough by
   **  itself).  I think it would be unwise to try to get this into a
   **  released version unless you have a good test data base to try it out
   **  on.
 */

	/* What character marks an inverted character class? */
#define NEGATE_CLASS		'^'
	/* Is "*" a common pattern? */
#define OPTIMIZE_JUST_STAR
	/* Do tar(1) matching rules, which ignore a trailing slash? */
#undef MATCH_TAR_PATTERN

/*
   **  Match text and p, return TRUE, FALSE, or AbORT.
 */
static int DoMatch (const char *text, const char *p)
{
	register int last;
	register int matched;
	register int reverse;

	for (; *p; text++, p++) 
	{
		if (*text == '\0' && *p != '*')
			return AbORT;
		switch (*p) 
		{
			case '\\':  //Literal match with following character.
				p++;
				// FALLTHROUGH
			default:
				if (*text != *p)
					return FALSE;
				continue;
			case '?':		//Match anything.
				continue;
			case '*':
				while (*++p == '*') // Consecutive stars act just like one.
					continue;
				if (*p == '\0')		//Trailing star matches everything.
					return TRUE;
			while (*text)
				if ((matched = DoMatch (text++, p)) != FALSE)
					return matched;
			return AbORT;
			case '[':
				reverse = p[1] == NEGATE_CLASS ? TRUE : FALSE;
				if (reverse)	//Inverted character class
					p++;
				matched = FALSE;
				if (p[1] == ']' || p[1] == '-')
					if (*++p == *text)
						matched = TRUE;
				for (last = *p; *++p && *p != ']'; last = *p)
				// This next line requires a good C compiler. 
					if (*p == '-' && p[1] != ']' ? *text <= *++p && *text >= last : *text == *p)
						matched = TRUE;
				if (matched == reverse)
					return FALSE;
				continue;
		}
	}

#ifdef	MATCH_TAR_PATTERN
	if (*text == '/')
		return TRUE;
#endif /* MATCH_TAR_ATTERN */
	return *text == '\0';
}


/*
   **  User-level routine.  Returns TRUE or FALSE.
 */
int	wildmat (char *text, char *p)
{
#ifdef	OPTIMIZE_JUST_STAR
	if (p[0] == '*' && p[1] == '\0')
		return TRUE;
#endif /* OPTIMIZE_JUST_STAR */
	return DoMatch (text, p) == TRUE;
}

BOOL strmatch(const char far *text, const char far *pat)
{
	if (!text || !pat)
		return FALSE;
	return DoMatch(text,pat) == TRUE;
}

static int compare_num( const unsigned char *a,   const unsigned char *b)
{
	/* Compare two left-aligned numbers: the first to have a
	different value wins. */
	int na = 0, nb= 0;
	int la = 0, lb = 0;
	int nRet;

	while (1 )
	{	//count/skip leading zero
		if (*a != '0') 
			break;
		na++;
		a++;
	}
	while (1)
	{	//count/skip leading zero
		if (*b != '0') 
			break;
		nb++;
		b++;
	}
	//count digits
	while (isdigit(b[lb]))
		lb++;
	while (isdigit(a[la]))
		la++;

	nRet = la - lb;	//compare length of digits
	if (!nRet)
	{
		//same length, compare values
		nRet = strncmp( (char*)a, (char*)b, la);
	}
	if (!nRet)
		nRet = na - nb; //which one has more leading zeros?

	return nRet;
}

static int compare_num_len( const unsigned char *a,   const unsigned char *b, int nLen)
{
	/* Compare two left-aligned numbers: the first to have a
	different value wins. */
	int na = 0, nb= 0;
	int la = 0, lb = 0;
	int nRet;
	const unsigned char *ea = a;
	const unsigned char *eb = b;
	if (nLen >= 0)
	{
		ea += nLen;
		eb += nLen;
	}
	else
	{
		while (*ea)
			ea++;
		while (*eb)
			eb++;
	}

	while (a <= ea )
	{	//count/skip leading zero
		if (*a != '0') 
			break;
		na++;
		a++;
	}
	while (b <= eb)
	{	//count/skip leading zero
		if (*b != '0') 
			break;
		nb++;
		b++;
	}
	//count digits
	while (isdigit(b[lb]) && b+lb < eb)
		lb++;
	while (isdigit(a[la]) && a+la < ea)
		la++;

	nRet = la - lb;	//compare length of digits
	if (!nRet)
	{
		//same length, compare values
		nRet = strncmp( (char*)a, (char*)b, la);
	}
	if (!nRet)
		nRet = na - nb; //which one has more leading zeros?

	return nRet;
}

//
// Compare two strings, treating embedded numbers as values
// Thus "test12 test" is greater than "test1 test"
//
// AlphaNumOnly disregards non alphanumeric characters, so
// "***test" and "test" compare as the same string.
//
int NumStrCompare(LPCSTR str1, LPCSTR str2, BOOL IgnoreCase, 
                  BOOL IgnoreSpace, BOOL AlphaNumOnly)
{
	const unsigned char  *S1= (const unsigned char *)str1;
	const unsigned char  *S2= (const unsigned char *)str2;
	int Result = 0;


	if (!str1 || !str2) return 0; // No null pointers!

	//speed up comparison of (nearly) identical long length strings <some really long filename part 0234/19779>
	{
		int l = 0;
		while (str1[l] && str2[l] && str1[l] == str2[l])
			l++;
		if (!str1[l] && !str2[l])
			return 0;
		if (l)
			return NumStrCompare(str1+l, str2+l, IgnoreCase, IgnoreSpace, AlphaNumOnly);
	}
	
	while (!Result && *S1 && *S2)
	{
		if (AlphaNumOnly)
		{
			while(*S1 && !isalnum(*S1))
				S1++;
			while(*S2 && !isalnum(*S2))
				S2++;
		}

		if (IgnoreSpace)
		{
			while (*S1 && isspace((unsigned char)*S1)) S1++;
			while (*S2 && isspace((unsigned char)*S2)) S2++;
		}

		if (!*S1 || !*S2)
			break;

		if ((*S1 <= '9') && (*S2 <= '9') && (*S1 >= '0') && (*S2 >= '0'))
		{
  
			Result = compare_num(S1, S2);
			if (!Result)
			{
				//eat same value digits
				while (isdigit(*S1))
					S1++;
				while (isdigit(*S2))
					S2++;
			}
			else
				return Result;
  
		} 
		else 
		{
			Result = (IgnoreCase ? toupper(*S1) : *S1) - (IgnoreCase ? toupper(*S2) : *S2);
			S1++; S2++;
		}
	}
	if (!Result)
		Result = (int)*S1 - (int)*S2;

	if (Result > 0 )
		Result = 1;
	else if (Result < 0)
		Result = -1;

	return Result;
}

int NumStrNCompare(LPCSTR str1, LPCSTR str2, int nLen1, BOOL IgnoreCase, 
                  BOOL IgnoreSpace, BOOL AlphaNumOnly)
{
	LPCSTR S1=str1;
	LPCSTR S2=str2;
	//unsigned int S1V;
	//unsigned int S2V;
	int Result = 0;

	if (!str1 || !str2) return 0; // No null pointers!

	while (!Result && *S1 && *S2 && (S1-str1 < nLen1))
	{
		if (AlphaNumOnly)
		{
			while(*S1 && !isalnum((unsigned char) *S1) && (S1-str1) < nLen1)
				S1++;
			while(*S2 && !isalnum((unsigned char)*S2) && (S2-str2) < nLen1)
				S2++;
		}

		if (IgnoreSpace)
		{
			while (*S1 && isspace((unsigned char)*S1) && (S1-str1) < nLen1) 
				S1++;
			while (*S2 && isspace((unsigned char)*S2) && (S2-str2) < nLen1) 
				S2++;
		}

		if (!*S1 || !*S2)
			break;

		if ((*S1 <= '9') && (*S2 <= '9') && (*S1 >= '0') && (*S2 >= '0'))
		{
			
			Result = compare_num_len( (const unsigned char *) S1, (const unsigned char *) S2, nLen1 - (S1-str1));
			//eat same value digits
			while (isdigit(*S1) && (S1-str1) < nLen1)
				S1++;
			while (isdigit(*S2) && (S2-str2) < nLen1)
				S2++;

			/*
			S1V = S2V = 0;
			while ((*S1 <= '9') && (*S1 >= '0') && (S1-str1) < nLen1) 
				S1V = (S1V * 10) + *(S1++) - '0';
			while ((*S2 <= '9') && (*S2 >= '0') && (S2-str2) < nLen1) 
				S2V = (S2V * 10) + *(S2++) - '0';
			Result = S1V - S2V;
			*/
		} 
		else 
		{
			Result = (IgnoreCase ? toupper((unsigned char)*S1) : *S1) - (IgnoreCase ? toupper((unsigned char)*S2) : *S2);
			S1++; S2++;
		}
	}
	if (!Result) 
		Result = S1-str1 == nLen1 ? 0 : (int)*S1 - (int)*S2;

	if (Result > 0 )
		Result = 1;
	else if (Result < 0)
		Result = -1;

	return Result;
}