/***************************************************************
  
   bwb_str.c       String-Management Routines
                        for Bywater BASIC Interpreter
  
                        Copyright (c) 1993, Ted A. Campbell
                        Bywater Software
  
                        email: tcamp@delphi.com
  
        Copyright and Permissions Information:
  
        All U.S. and international rights are claimed by the author,
        Ted A. Campbell.
  
   This software is released under the terms of the GNU General
   Public License (GPL), which is distributed with this software
   in the file "COPYING".  The GPL specifies the terms under
   which users may copy and use the software in this distribution.
  
   A separate license is available for commercial distribution,
   for information on which you should contact the author.
  
***************************************************************/

/*---------------------------------------------------------------*/
/* NOTE: Modifications marked "JBV" were made by Jon B. Volkoff, */
/* 11/1995 (eidetics@cerf.net).                                  */
/*                                                               */
/* Those additionally marked with "DD" were at the suggestion of */
/* Dale DePriest (daled@cadence.com).                            */
/*                                                               */
/* Version 3.00 by Howard Wulf, AF5NE                            */
/*                                                               */
/* Version 3.10 by Howard Wulf, AF5NE                            */
/*                                                               */
/*---------------------------------------------------------------*/



/***************************************************************

WARNING - WARNING - WARNING - WARNING - WARNING - WARNING

BASIC allows embedded NUL (0) characters.  C str*() does not. 

ALL the StringType code should use mem*() and ->length, but some does not.
ALL the StringType code should prevent string overflow, but some does not.

***************************************************************/




#include "bwbasic.h"





/***************************************************************
  
        FUNCTION:       str_btob()
  
        DESCRIPTION:    This C function assigns a bwBASIC string
         structure to another bwBASIC string
         structure.
  
***************************************************************/

int
str_btob(StringType * d, StringType * s)
{
   bwx_DEBUG(__FUNCTION__);

   /* get memory for new buffer */
   if ( d->sbuffer == NULL )
   {
      if ((d->sbuffer = (char *) CALLOC(BasicStringLengthMax + 1, 1, "str_btob")) == NULL)
      {
         WARN_OUT_OF_MEMORY;
         return FALSE;
      }
   }
   /* write the b string to the b string */
   if (s->length > BasicStringLengthMax)
   {
      WARN_STRING_TOO_LONG;
      s->length = BasicStringLengthMax;
   }
   d->length = s->length;
   bwb_memcpy(d->sbuffer, s->sbuffer, d->length);
   d->sbuffer[d->length] = BasicNulChar;

   return TRUE;
}

/***************************************************************
  
        FUNCTION:       str_ctob()
  
        DESCRIPTION:    This C function assigns a null-terminated
         C string to a bwBASIC string structure.
  
***************************************************************/

int
str_ctob(StringType * s, char *buffer)
{
   bwx_DEBUG(__FUNCTION__);

   /* get memory for new buffer */
   if ( s->sbuffer == NULL )
   {
      if ((s->sbuffer = (char *) CALLOC(BasicStringLengthMax + 1, 1, "str_ctob")) == NULL)
      {
         WARN_OUT_OF_MEMORY;
         return FALSE;
      }
   }
   /* write the c string to the b string */
   s->length = bwb_strlen(buffer);
   if (s->length > BasicStringLengthMax)
   {
      WARN_STRING_TOO_LONG;
      s->length = BasicStringLengthMax;
   }
   bwb_memcpy(s->sbuffer, buffer, s->length);
   s->sbuffer[s->length] = BasicNulChar;

   return TRUE;
}

/***************************************************************
  
        FUNCTION:       str_btoc()
  
        DESCRIPTION:    This C function assigns a null-terminated
         C string to a bwBASIC string structure.
  
***************************************************************/

int
str_btoc(char *buffer, StringType * s)
{

   bwx_DEBUG(__FUNCTION__);

   /* write the b string to the c string */
   if (s->length > BasicStringLengthMax)
   {
      WARN_STRING_TOO_LONG;
      s->length = BasicStringLengthMax;
   }
   bwb_memcpy(buffer, s->sbuffer, s->length);
   buffer[s->length] = BasicNulChar;

   return TRUE;

}

/***************************************************************
  
        FUNCTION:       str_cat()
  
        DESCRIPTION:    This C function performs the equivalent
         of the C strcat() function, using BASIC
         strings.
  
***************************************************************/

StringType        *
str_cat(StringType * a, StringType * b)
{
   int             i;

   bwx_DEBUG(__FUNCTION__);

   if (a->length > BasicStringLengthMax)
   {
      WARN_STRING_TOO_LONG;
      a->length = BasicStringLengthMax;
   }
   if (b->length > BasicStringLengthMax)
   {
      WARN_STRING_TOO_LONG;
      b->length = BasicStringLengthMax;
   }
   i = b->length;
   if (a->length + i > BasicStringLengthMax)
   {
      WARN_STRING_TOO_LONG;
      i = BasicStringLengthMax - a->length;
   }
   if (i > 0)
   {
      char           *t;

      t = a->sbuffer;
      t += a->length;
      bwb_memcpy(t, b->sbuffer, i);
      a->length += i;
      a->sbuffer[a->length] = BasicNulChar;
   }
   return a;
}

/***************************************************************
  
        FUNCTION:       str_cmp()
  
   DESCRIPTION:    This C function performs the equivalent
         of the C strcmp() function, using BASIC
         strings.
  
***************************************************************/

int
str_cmp(StringType * a, StringType * b)
{
   bwx_DEBUG(__FUNCTION__);

   if (a->length > BasicStringLengthMax)
   {
      WARN_STRING_TOO_LONG;
      a->length = BasicStringLengthMax;
   }
   if (b->length > BasicStringLengthMax)
   {
      WARN_STRING_TOO_LONG;
      b->length = BasicStringLengthMax;
   }
   if (a->sbuffer == NULL)
   {
      if (b->sbuffer == NULL)
      {
         return 0;
      }
      if (b->length == 0)
      {
         return 0;
      }
      return 1;
   }
   else
   {
      a->sbuffer[a->length] = BasicNulChar;
   }

   if (b->sbuffer == NULL)
   {
      if (a->sbuffer == NULL)
      {
         return 0;
      }
      if (a->length == 0)
      {
         return 0;
      }
      return -1;
   }
   else
   {
      b->sbuffer[b->length] = BasicNulChar;
   }

   if (My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT)
   {
      /* case insensitive */
      return bwb_stricmp(a->sbuffer, b->sbuffer); /* NOTE: embedded NUL characters terminate comparison */
   }
   else
   {
      /* case sensitive */
      return bwb_strcmp(a->sbuffer, b->sbuffer); /* NOTE: embedded NUL characters terminate comparison */
   }
}

/***************************************************************

                     MATCH

***************************************************************/

int str_match( char * A, int A_Length, char *B, int B_Length, int I_Start )
{
   /*
   SYNTAX: j% = MATCH( a$, b$, i% )

   MATCH returns the position of the first occurrence of a$ in b$ 
   starting with the character position given by the third parameter. 
   A zero is returned if no MATCH is found. 
   
   The following pattern-matching features are available: 
     # matches any digit (0-9). 
     ! matches any upper-or lower-case letter. 
     ? matches any character. 
     \ serves as an escape character indicating the following character does not have special meaning. 
   
   For example, a ? signifies any character is a MATCH unless preceded by a \. 
   a$ and b$ must be strings. 
   If either of these arguments are numeric, an error occurs. 
   If i% is real, it is converted to an integer. 
   If i% is a string, an error occurs. 
   If i% is negative or zero, a run-time error occurs. 
   When i% is greater than the length of b$, zero is returned. 
   If b$ is a null string, a 0 is returned. 
   If b$ is not null, but a$ is null, a 1 is returned.    
   
   Examples: 
   MATCH( "is", "Now is the", 1) returns 5
   MATCH( "##", "October 8, 1876", 1) returns 12
   MATCH( "a?", "character", 4 ) returns 5
   MATCH( "\#", "123#45", 1) returns 4
   MATCH( "ABCD", "ABC", 1 ) returns 0
   MATCH( "\#1\\\?", "1#1\?2#", 1 ) returns 2
   */
   
   
   int a; /* current position in A$ */
   int b; /* current position in B$ */
 
   if( I_Start <= 0 )
   {
      return 0;
   }   
   if( I_Start > B_Length )
   {
      return 0;
   }
   if( B_Length <= 0 )
   {
      return 0;
   }  
   if( A_Length <= 0 )
   {
      return 1;
   }  
   I_Start--; /* BASIC to C */
   for( b = I_Start; b < B_Length; b++ )
   {
      int n; /* number of characters in A$ matched with B$ */

      n = 0;
      for( a = 0; a < A_Length; a++ )
      {
         int bn;
         bn = b + n;
         if( A[ a ] == '#' && bwb_isdigit( B[ bn ] ) )
         {
            n++;
         }
         else
         if( A[ a ] == '!' && bwb_isalpha( B[ bn ] ) )
         {
            n++;
         }
         else
         if( A[ a ] == '?' )
         {
            n++;
         }
         else
         if( a < (A_Length - 1) && A[ a ] == '\\' && A[ a + 1 ] == B[ bn ] )
         {
            n++;
            a++;
         }
         else
         if( A[ a ] == B[ bn ] )
         {
            n++;
         }
         else
         {
            break;
         }
      }
      if( a == A_Length )
      {
         b++; /* C to BASIC */
         return b;
      }
   }
   return 0;
}




/***************************************************************
  
        FUNCTION:       str_like()
  
   DESCRIPTION:    This C function performs the equivalent
         of the BASIC LIKE operator, using BASIC
         strings.
  
***************************************************************/

/* 
inspired by http://www.blackbeltcoder.com/Articles/net/implementing-vbs-like-operator-in-c 
*/

/*
KNOWN ISSUES:
To match the character '[', use "[[]".
To match the character '?', use "[?]".
To match the character '*', use "[*]".
Does not match "" with "[]" or "[!]".
*/

#define CHAR_SET '*'
#define CHAR_CLR ' '

static char charList[ 256 ];

static int IndexOf( char * buffer, char find, int start )
{
   int buffer_count;
   int buffer_length;
   
   buffer_length = bwb_strlen( buffer );

    if( My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT )
    {
       find = bwb_toupper( find );
    }

   for( buffer_count = start; buffer_count < buffer_length; buffer_count++ )
   {
      char theChar;
      
      theChar = buffer[buffer_count];

       if( My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT )
       {
          theChar = bwb_toupper( theChar );
       }


      if( theChar == find )
      {
         /* FOUND */
         return buffer_count;
      }

   }
   /* NOT FOUND */
   return -1;
}


static int CharListToSet(char * pattern, int start, int stop)
 {
   
    /*
    Converts a string of characters to a HashSet of characters. If the string
    contains character ranges, such as A-Z, all characters in the range are
    also added to the returned set of characters.
    */
    
    int pattern_Count;
    
    bwb_memset( charList, CHAR_CLR, 256 );
    
    if( start > stop )
    {
       /* ERROR */
       return FALSE;
    }
    /* Leading '-' */
    while( pattern[start] == '-' )
    {
      /* Match character '-' */
      
      charList[ 0x00FF & pattern[start] ] = CHAR_SET;
      start++;
       if( start > stop )
       {
          /* DONE */
          return TRUE;
       }
    }
    /* Trailing '-' */
    while( pattern[stop] == '-' )
    {
      /* Match character '-' */
      charList[ 0x00FF & pattern[stop] ] = CHAR_SET;
      stop--;
       if( start > stop )
       {
          /* DONE */
          return TRUE;
       }
    }
   
   
     for (pattern_Count = start; pattern_Count <= stop; pattern_Count++)
     {
        if( pattern[pattern_Count] == '-' )
        {
             /* Character range */
             
             char startChar;
             char endChar;
             char theChar;
             
             if( pattern_Count > start )
             {
                startChar = pattern[pattern_Count-1];
                if( startChar == '-' )
                {
                   /* ERROR */
                   return FALSE;
                }
                if( My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT )
                {
                   startChar = bwb_toupper( startChar );
                }
             }
             else
             {
                /* ERROR */
                return FALSE;
             }
             if( pattern_Count < stop )
             {
                endChar = pattern[pattern_Count+1];
                if( endChar == '-' )
                {
                   /* ERROR */
                   return FALSE;
                }
                if( My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT )
                {
                   endChar = bwb_toupper( endChar );
                }
                if( endChar < startChar )
                {
                   /* ERROR */
                   return FALSE;
                }
             }
             else
             {
                /* ERROR */
                return FALSE;
             }
             /* 
             Although the startChar has already been set,
             and the endChar will be set on the next loop,
             we go ahead and set them here too.
             Not the most efficient, but easy to understand,
             and we do not have to do anything special
             for edge cases such as [A-A] and [A-B].  
             */
             for (theChar = startChar; theChar <= endChar; theChar++)
             {
                charList[ 0x00FF & theChar ] = CHAR_SET;
             }
         }
         else
         {
            /* Single character */
            char theChar;
            
            theChar = pattern[pattern_Count];
             if( My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT )
             {
                theChar = bwb_toupper( theChar );
             }
            charList[ 0x00FF & theChar  ] = CHAR_SET;
         }
     }

     return TRUE;
 }



int IsLike(char * buffer , int * buffer_count , int buffer_Length, 
           char * pattern, int * pattern_count, int pattern_Length)
 {

    /* Implement's VB's Like operator logic. */
    
    /* 
    if matched then
       buffer_count is updated
       pattern_count is updated
       returns TRUE
    else
       returns FALSE
    end if
     */
    int bc;
    int pc;

   bc = *buffer_count;
   pc = *pattern_count;


  /* Loop through pattern string */
  while ( pc < pattern_Length )
  {

      /* Get next pattern character */
      if ( pattern[pc] == '[' ) 
      {
         /* Character list */
         /* [] and [!] are special */
         char IsExclude = CHAR_CLR;
         
         pc++;
         /* pc is first character after '[' */
         if( pattern[pc] == '!' )
         {
            pc++;
            IsExclude = CHAR_SET;
         }
         /* pc is first character after '[' */
         if( pattern[pc] == ']' )
         {
            /* [] and [!] are special */
            /* pc is first character after '[' and is a ']' */
            pc++;
            if( IsExclude == CHAR_CLR )
            {
               /* [] */
               /* matches "" */
            }
            else
            {
               /* [!] */
               /* same as '?' */
               if (bc >= buffer_Length)
               {
                  /* we have completed the buffer without completing the pattern */
                  return FALSE;
               }
               bc++;
            }
         }
         else
         {
             /* Build character list */
             /* pc is first character after '[' and is not a ']' */
             int stop_count = IndexOf(pattern, ']', pc);
             /* stop_count is the character ']' */
             if (stop_count < 0)
             {
                /* NOT FOUND */
                return FALSE;
             }
             /* pc is first character after '['  */
             /* stop_count is the character ']' */
             CharListToSet(pattern, pc, stop_count - 1);
             pc = stop_count + 1;
             /* pc is first character after ']' */
             if (bc >= buffer_Length)
             {
               /* we have completed the buffer without completing the pattern */
                return FALSE;
             }
             if (charList[ 0x00FF & buffer[bc] ] == IsExclude)
             {
                /* not matched */
                 return FALSE;
             }
             bc++;
          }
      }
      else 
      if (pattern[pc] == '?' /* LIKE char */ ) 
      {
         /* Matches a single character */
         pc++;
         if (bc >= buffer_Length)
         {
            /* Check for end of string */
            /* we have completed the buffer without completing the pattern */
             return FALSE;
         }
         bc++;
      }
      else 
      if (pattern[pc] == '#' /* LIKE digit */ ) 
      {
         /* Matches a single digit */
         pc++;
         if (bc >= buffer_Length)
         {
            /* Check for end of string */
            /* we have completed the buffer without completing the pattern */
             return FALSE;
         }
          if (bwb_isdigit(buffer[bc]))
          {
             bc++;
          }
          else
          {
             /* not matched */
              return FALSE;
          }
      }
      else 
      if (pattern[pc] == '*' /* LIKE chars */ ) 
      {
         /* Zero or more characters */
         while(pattern[pc] == '*' /* LIKE chars */ ) 
         {
            pc++;
         }
          if (pc == pattern_Length)
          {
              /* Matches all remaining characters */
              bc = buffer_Length;
              pc = pattern_Length;
              break;
          }
          else
          {
             int p;
             int b;
             int IsMatched;
             
             p = pc;
             b = bc;
             IsMatched = FALSE;
             while( b <= buffer_Length && IsMatched == FALSE)
             {
                int last_b;
                
                last_b = b;
                IsMatched = IsLike(buffer, &b, buffer_Length, pattern, &p, pattern_Length);
                if( IsMatched == FALSE)
                {
                   /* not matched, try again begining at next buffer position */
                   p = pc;
                   b = last_b + 1;
                }
             }
             if( IsMatched == FALSE )
             {
                /* not matched */
                return FALSE;
             }
             pc = p;
             bc = b;
          }
      }
      else 
      {
         char pattChar;
         char buffChar;
         
         pattChar = pattern[pc];
         if (bc >= buffer_Length)
         {
            /* Check for end of string */
            /* we have completed the buffer without completing the pattern */
             return FALSE;
         }
         buffChar = buffer[bc];
         
          if( My->CurrentVersion->OptionFlags & OPTION_COMPARE_TEXT )
          {
             pattChar = bwb_toupper( pattChar );
             buffChar = bwb_toupper( buffChar );
          }
         
          if ( pattChar == buffChar )
          {
             /* matched specified character */
             pc++;
             bc++;
          }
         else
         {
            /* not matched */
              return FALSE;
         }
      }
  }
  /* Return true if all characters matched */
  if(pc < pattern_Length)
  {
     /* not matched */
     return FALSE;
  }
  if(bc < buffer_Length)
  {
     /* not matched */
     return FALSE;
  }
  /* all characters matched */
  *buffer_count = bc;
  *pattern_count = pc;
  return TRUE;
 }


int
str_like(StringType * a, StringType * b)
{
   /*
   'a' is buffer
   'b' is pattern
   returns TRUE if "a LIKE b",
   returns FALSE otherwise 
   and when ERROR occurs.
   */
   
   int a_count;
   int b_count;
   
   bwx_DEBUG(__FUNCTION__);

   if (a->length > BasicStringLengthMax)
   {
      /* INTERNAL ERROR, so truncate */
      a->length = BasicStringLengthMax;
   }
   if (b->length > BasicStringLengthMax)
   {
      /* INTERNAL ERROR, so truncate */
      b->length = BasicStringLengthMax;
   }
   if (a->sbuffer == NULL)
   {
      if (b->sbuffer == NULL)
      {
         return FALSE;
      }
      if (b->length == 0)
      {
         return FALSE;
      }
      return FALSE;
   }
   else
   {
      a->sbuffer[a->length] = BasicNulChar;
   }

   if (b->sbuffer == NULL)
   {
      if (a->sbuffer == NULL)
      {
         return FALSE;
      }
      if (a->length == 0)
      {
         return FALSE;
      }
      return FALSE;
   }
   else
   {
      b->sbuffer[b->length] = BasicNulChar;
   }
   
   a_count = 0;
   b_count = 0;

   return IsLike(a->sbuffer, &a_count, a->length, b->sbuffer, &b_count, b->length);
}



/* EOF */
