/*===========================================================================
 *
 * File:	DL_Chr.CPP
 * Author:	Dave Humphrey (uesp@m0use.net)
 * Created On:	Sunday, May 06, 2001
 *
 * Implementation of character related functions for Dave's Library of
 * common code.
 *
 *=========================================================================*/

	/* Include Files */
#include "dl_chr.h"
#include <ctype.h>


/*===========================================================================
 *
 * Begin Local Variable Definitions
 *
 *=========================================================================*/
  DEFINE_FILE();
/*===========================================================================
 *		End of Local Variable Definitions
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void chradd (pString, CharIndex, NewChar);
 *
 * Attempts to insert the specified character into the string at the
 * given position CharIndex.  First character in string is at position 0.
 * Assumes the string is long enough to add another character.  ASSERTs
 * if any invalid input is received.
 *
 *=========================================================================*/
void chradd (char* pString, const size_t CharIndex, const char NewChar) {
  DEFINE_FUNCTION("chradd()");
  char*  pInsertPosition;
  size_t StringLength;

	/* Ensure valid input */
  ASSERT(pString != NULL);
  StringLength = strlen(pString);
  ASSERT(CharIndex <= StringLength);

	/* Shift the characters after insert position right by one */
  pInsertPosition = pString + CharIndex;
  memmove (pInsertPosition + 1, pInsertPosition, StringLength - CharIndex + 1);

	/* Add the new character to the string */
  *pInsertPosition = NewChar;
 }
/*===========================================================================
 *		End of Function chradd()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void chrcat (char* pString, const char NewChar);
 *
 * Attempts to add a character to the end of the given string.  Assumes
 * that the string is long enough to add another character.  ASSERTs if
 * any bad input is received.
 *
 *=========================================================================*/
void chrcat (char* pString, const char NewChar) {
  DEFINE_FUNCTION("chrcat()");
  char* pInsertPosition;

	/* Ensure valid input */
  ASSERT(pString != NULL);

	/* Find the end of the string */
  pInsertPosition = pString + strlen(pString);

	/* Add the character to end of string, making sure we NULL terminate */
  pInsertPosition[1] = NULL_CHAR;
  pInsertPosition[0] = NewChar;
 }
/*===========================================================================
 *		End of Function chrcat()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void chrdel (pString, CharIndex);
 *
 * Attempts to delete the character at the given position from the string.
 * Position 0 is the first character in the string.  ASSERTs on any bad
 * input.
 *
 *=========================================================================*/
void chrdel (char* pString, const size_t CharIndex) {
  DEFINE_FUNCTION("chrdel()");
  char*  pDeletePosition;
  size_t StringLength;

	/* Ensure valid input */
  ASSERT(pString != NULL);
  StringLength = strlen(pString);
  ASSERT(CharIndex <= StringLength);

	/* Shift the characters left of the deletion position by one */
  pDeletePosition = pString + CharIndex;
  memmove (pDeletePosition, pDeletePosition + 1, StringLength - CharIndex);
 }
/*===========================================================================
 *		End of Function chrdel()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void chrdellast (pString);
 *
 * Deletes the last character from the string. ASSERTs on bad input.
 *
 *=========================================================================*/
void chrdellast (char* pString) {
  DEFINE_FUNCTION("chrdellast()");
  size_t StringLength;

	/* Ensure valid input */
  ASSERT(pString != NULL);

	/* Find last character in string, and ensure at least one exists */
  StringLength = strlen(pString);

	/* Delete the last character in the string, if any */
  if (StringLength > 0) pString[StringLength - 1] = NULL_CHAR;
 }
/*===========================================================================
 *		End of Function chrdellast()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - boolean chrrpunc (CharIndex, pString);
 *
 * Looks for the first punctuation character from the end of the string.
 * Saves the character index if one is found (0 is the first character
 * in the string) and returns TRUE.  Returns FALSE if no matching character
 * was found.  ASSERTs on bad input.
 *
 *=========================================================================*/
boolean chrrpunc (size_t& CharIndex, const char* pString) {
  DEFINE_FUNCTION("chrrpunc()");
  size_t StringPosition;

	/* Ensure valid input */
  ASSERT(pString != NULL);
  
	/* Start at the end of the string */
  StringPosition = strlen(pString);

	/* Look for a punctuating character until start of string is reached */
  while (StringPosition != 0) {
    StringPosition--;

    if (ispunct(pString[StringPosition])) {
      CharIndex = StringPosition;
      return (TRUE);
     }
   }

	/* Character not found in string */
  return (FALSE);
 }
/*===========================================================================
 *		End of Function chrrpunc()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - char* chrtok (pString, TokenChar);
 *
 * Seperates the given string into substrings divided by the given token 
 * character, as strtok().  Returns NULL when no substrings are 
 * left.  Uses an internal static variable which prevents use of nesting
 * chrtok() calls.  ASSERTs on invalid input.  The TokenChar cannot be
 * the terminating NULL character.
 *
 * Unlike strtok(), chrtok() ignores multiple occurences of the TokenChar,
 * for example, the TestString "11x22x33xxx44"
 *	strtok(TestString, "x") = "11", "22", "33", "44"
 *	chrtok(TestString, 'x') = "11", "22", "33", "", "", "44"
 * The use is otherwise identical to strok().
 *
 *=========================================================================*/
char* chrtok (char* pString, const char TokenChar) {
  DEFINE_FUNCTION("chrtok()");
  static char*  pParse = NULL;
  char*		pSearchChar;
  char*		pResult;

	/* Ensure valid input */
  ASSERT(TokenChar != NULL_CHAR);
  
	/* Check for end of parsed token string */
  if (pString == NULL && pParse == NULL) return (NULL);

	/* Start parsing a new string */
  if (pString != NULL) pParse = pString;

	/* Search for the next substring */
  pSearchChar = strchr(pParse, TokenChar);

	/* No more substrings */
  if (pSearchChar == NULL) {
    pResult = pParse;
    pParse = NULL;
    return (pResult);
   }

	/* Terminate substring */
  *pSearchChar = NULL_CHAR;
  pResult = pParse;
  pParse = pSearchChar + 1;
  return (pResult);
 }
/*===========================================================================
 *		End of Function chrtok()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - char* chrtrunc (pString, TruncateChar);
 *
 * Truncates the character at the first occurence of the given character.
 * Returns the string.  ASSERTs if given invalid input.
 *
 *=========================================================================*/
char* chrtrunc (char* pString, const char TruncateChar) {
  DEFINE_FUNCTION("chrtrunc()");
  char* pFindChar;

	/* Ensure valid input */
  ASSERT(pString != NULL && TruncateChar != NULL_CHAR);

	/* Attempt to find the first truncation character */
  pFindChar = strchr(pString, TruncateChar);
  if (pFindChar != NULL) *pFindChar = NULL_CHAR;

  return (pString);
 }
/*===========================================================================
 *		End of Function chrtrunc()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - char* chrrtrunc (pString, TruncateChar);
 *
 * Truncates the character at the last occurence of the given character.
 * Returns the string.  ASSERTs if given invalid input.
 *
 *=========================================================================*/
char* chrrtrunc (char* pString, const char TruncateChar) {
  DEFINE_FUNCTION("chrrtrunc()");
  char* pFindChar;

	/* Ensure valid input */
  ASSERT(pString != NULL && TruncateChar != NULL_CHAR);

	/* Attempt to find the last truncation character */
  pFindChar = strrchr(pString, TruncateChar);
  if (pFindChar != NULL) *pFindChar = NULL_CHAR;

  return (pString);
 }
/*===========================================================================
 *		End of Function chrrtrunc()
 *=========================================================================*/


/*===========================================================================
 *
 * Begin Module Test Routines
 *
 * Test routines for the functions in this module.  Only defined in 
 * DEBUG builds.
 *
 *=========================================================================*/
#if defined(_DEBUG)


/*===========================================================================
 *
 * Function - void Test_chradd (void);
 *
 * Tests the chradd() function.
 *	1. Ensures characters are correctly inserted in middle of string
 *	2. Check adding characters at start of string
 *	3. Check adding characters at end of string
 *
 *=========================================================================*/
void Test_chradd (void) {
  DEFINE_FUNCTION("Test_chradd()");
  char TestString[101] = "123456789";

  SystemLog.Printf(stdout, "============= Testing chradd() ====================");

	/* Add a character in the middle of the string */
  chradd(TestString, 4, '4');
  ASSERT(strcmp(TestString, "1234456789") == 0);

	/* Add a character at the start of the string */
  chradd(TestString, 0, '0');
  ASSERT(strcmp(TestString, "01234456789") == 0);

  	/* Add a character at the end of the string */
  chradd(TestString, 11, '0');
  ASSERT(strcmp(TestString, "012344567890") == 0);

 }
/*===========================================================================
 *		End of Function Test_chradd()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_chrcat (void);
 *
 * Test the chrcat() function.
 *	1. Tests adding a character to a regular string.
 *	2. Tests adding a character to an empty string.
 *
 *=========================================================================*/
void Test_chrcat (void) {
  DEFINE_FUNCTION("Test_chrcat()");
  char TestString[101] = "123456789";

  SystemLog.Printf(stdout, "============= Testing chrcat() ====================");

	/* Test adding character to regular string */
  chrcat(TestString, '0');
  ASSERT(strcmp(TestString, "1234567890") == 0);

	/* Test adding character to empty string */
  TestString[0] = NULL_CHAR;
  chrcat(TestString, '0');
  ASSERT(strcmp(TestString, "0") == 0);
 }
/*===========================================================================
 *		End of Function Test_chrcat()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_chrdel (void);
 *
 * Tests the chrdel() function
 *	1. Deletes characters from middle of regular string.
 *	2. Delete characters from start/end of string
 *	3. Test empty string deletion
 *
 *=========================================================================*/
void Test_chrdel (void) {
  DEFINE_FUNCTION("Test_chrdel()");
  char TestString[101] = "123456789";

  SystemLog.Printf(stdout, "============= Testing chrdel() ====================");
	
	/* Delete characters from middle of string */
  chrdel(TestString, 4);
  ASSERT(strcmp(TestString, "12346789") == 0);

  	/* Delete characters from end of string */
  chrdel(TestString, 7);
  ASSERT(strcmp(TestString, "1234678") == 0);

  	/* Delete characters from start of string */
  chrdel(TestString, 0);
  ASSERT(strcmp(TestString, "234678") == 0);

	/* Delete character from empty string */
  TestString[0] = NULL_CHAR;
  chrdel(TestString, 0);
  ASSERT(strcmp(TestString, "") == 0);
 }
/*===========================================================================
 *		End of Function Test_chrdel()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_chrdellast (void);
 *
 * Tests the chrdellast() function.
 *	1. Delete the last character from a regular string
 *	2. Delete character from empty string.
 *
 *=========================================================================*/
void Test_chrdellast (void) {
  DEFINE_FUNCTION("Test_chrdellast()");
  char TestString[101] = "123456789";

  SystemLog.Printf(stdout, "============= Testing chrdellast() ====================");

	/* Delete characters from regular of string */
  chrdellast(TestString);
  ASSERT(strcmp(TestString, "12345678") == 0);

	/* Delete character from empty string */
  TestString[0] = NULL_CHAR;
  chrdellast(TestString);
  ASSERT(strcmp(TestString, "") == 0);
 }
/*===========================================================================
 *		End of Function Test_chrdellast()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_chrtok (void);
 *
 * Tests the chrtok() function.
 *	1. Test a regular string, comparing results with strtok()
 *	2. Check the function enhanced cabapility.
 *
 *=========================================================================*/
void Test_chrtok (void) {
  DEFINE_FUNCTION("Test_chrtok()");
  char  TestString1[] = "111x222x333x444x555";
  char  TestString2[] = "111x222x333x444x555";
  char* pChrPtr;
  char* pStrPtr;

  SystemLog.Printf(stdout, "============= Testing chrtok() ====================");

	/* Compare chrtok() results with strtok() for a normal string */
  pChrPtr = chrtok(TestString1, 'x');
  pStrPtr = strtok(TestString2, "x");

  while (pChrPtr != NULL && pStrPtr != NULL) {
    SystemLog.Printf ("\t chrtok() / strtok() = %s / %s", pChrPtr, pStrPtr);
    ASSERT(strcmp(pChrPtr, pStrPtr) == 0);
    pChrPtr = chrtok(NULL, 'x');
    pStrPtr = strtok(NULL, "x");
   }

  ASSERT(pChrPtr == NULL && pStrPtr == NULL);

	/* Test the enhanced feature of chrtok() */
  strcpy (TestString1, "111x222xx333xx");
  pChrPtr = chrtok(TestString1, 'x');
  ASSERT(strcmp(pChrPtr, "111") == 0);
  pChrPtr = chrtok(NULL, 'x');
  ASSERT(strcmp(pChrPtr, "222") == 0);
  pChrPtr = chrtok(NULL, 'x');
  ASSERT(strcmp(pChrPtr, "") == 0);
  pChrPtr = chrtok(NULL, 'x');
  ASSERT(strcmp(pChrPtr, "333") == 0);
  pChrPtr = chrtok(NULL, 'x');
  ASSERT(strcmp(pChrPtr, "") == 0);
  pChrPtr = chrtok(NULL, 'x');
  ASSERT(strcmp(pChrPtr, "") == 0);
  pChrPtr = chrtok(NULL, 'x');
  ASSERT(pChrPtr == NULL);

 }
/*===========================================================================
 *		End of Function Test_chrtok()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_chrrpunc (void);
 *
 * Tests the chrrpunc() function.
 *	1. Ensures the function finds a punctuation in middle/start/end of string
 *	2. Check multiple punctuation case
 *	3. Check case where string contains no punctuation
 *	4. Check empty string case.
 *
 *=========================================================================*/
void Test_chrrpunc (void) {
  DEFINE_FUNCTION("Test_chrrpunc()");
  char    TestString[101] = "0123=5678";
  boolean Result;
  size_t  CharIndex;

  SystemLog.Printf(stdout, "============= Testing chrrpunc() ====================");

	/* Test for punctuation in middle of string */
  Result = chrrpunc(CharIndex, TestString);
  ASSERT(Result);
  ASSERT(CharIndex == 4);

  	/* Test for punctuation at end of string */
  strcpy (TestString, "012345678.");
  Result = chrrpunc(CharIndex, TestString);
  ASSERT(Result);
  ASSERT(CharIndex == 9);

  	/* Test for punctuation at start of string */
  strcpy (TestString, ".123456789");
  Result = chrrpunc(CharIndex, TestString);
  ASSERT(Result);
  ASSERT(CharIndex == 0);

  	/* Test for multiple punctuations in of string */
  strcpy (TestString, "01234.67.0");
  Result = chrrpunc(CharIndex, TestString);
  ASSERT(Result);
  ASSERT(CharIndex == 8);

  	/* Test for no punctuation in string */
  strcpy (TestString, "0123456789");
  Result = chrrpunc(CharIndex, TestString);
  ASSERT(Result == FALSE);

  	/* Test empty string case */
  strcpy (TestString, "");
  Result = chrrpunc(CharIndex, TestString);
  ASSERT(Result == FALSE);
 }
/*===========================================================================
 *		End of Function Test_chrrpunc()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_chrtrunc (void);
 *
 * Tests the chrtrunc() function.
 *	1. Test function with regular string with truncation character in
 *	   middle/start/end of string
 *	2. Check with no truncation character in string
 *	3. Test with empty string
 *
 *=========================================================================*/
void Test_chrtrunc (void) {
  DEFINE_FUNCTION("Test_chrtrunc()");
  char TestString[101];
  
  SystemLog.Printf(stdout, "============= Testing chrtrunc() ====================");

	/* Check with truncation character in middle of string */
  strcpy(TestString, "123456789");
  chrtrunc(TestString, '5');
  ASSERT(strcmp(TestString, "1234") == 0);

  	/* Check with truncation character at end of string */
  strcpy(TestString, "123456789");
  chrtrunc(TestString, '9');
  ASSERT(strcmp(TestString, "12345678") == 0);

  	/* Check with truncation character at start of string */
  strcpy(TestString, "123456789");
  chrtrunc(TestString, '1');
  ASSERT(strcmp(TestString, "") == 0);

  	/* Check with no truncation character in string */
  strcpy(TestString, "123456789");
  chrtrunc(TestString, 'a');
  ASSERT(strcmp(TestString, "123456789") == 0);

  	/* Check with empty string */
  strcpy(TestString, "");
  chrtrunc(TestString, '5');
  ASSERT(strcmp(TestString, "") == 0);
 }
/*===========================================================================
 *		End of Function Test_chrtrunc()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_chrtrunc (void);
 *
 * Tests the chrrtrunc() function.
 *	1. Test function with regular string with truncation character in
 *	   middle/start/end of string
 *	2. Check with no truncation character in string
 *	3. Test with empty string
 *
 *=========================================================================*/
void Test_chrrtrunc (void) {
  DEFINE_FUNCTION("Test_chrrtrunc()");
  char TestString[101];
  
  SystemLog.Printf(stdout, "============= Testing chrrtrunc() ====================");

	/* Check with truncation character in middle of string */
  strcpy(TestString, "123456789");
  chrrtrunc(TestString, '5');
  ASSERT(strcmp(TestString, "1234") == 0);

  	/* Check with truncation character at end of string */
  strcpy(TestString, "123456789");
  chrrtrunc(TestString, '9');
  ASSERT(strcmp(TestString, "12345678") == 0);

  	/* Check with truncation character at start of string */
  strcpy(TestString, "123456789");
  chrrtrunc(TestString, '1');
  ASSERT(strcmp(TestString, "") == 0);

  	/* Check with no truncation character in string */
  strcpy(TestString, "123456789");
  chrrtrunc(TestString, 'a');
  ASSERT(strcmp(TestString, "123456789") == 0);

  	/* Check with empty string */
  strcpy(TestString, "");
  chrrtrunc(TestString, '5');
  ASSERT(strcmp(TestString, "") == 0);
 }
/*===========================================================================
 *		End of Function Test_chrrtrunc()
 *=========================================================================*/



/*===========================================================================
 *
 * Function - void Test_DLChr (void);
 *
 * Tests all the functions in this module.
 *	1. Tests the chradd() function
 *	2. Tests the chrcat() function
 *	3. Tests the chrdel() and chrdellast() functions
 *	4. Tests the chrtok() function
 *	5. Tests the chrrpunc() function
 *	6. Tests the chrtrunc() and chrrtrunc() functions
 *
 *=========================================================================*/
void Test_DLChr (void) {
  //DEFINE_FUNCTION("Test_DLChr()");
  
  Test_chradd();
  Test_chrcat();
  Test_chrdel();
  Test_chrdellast();
  Test_chrtok();
  Test_chrrpunc();
  Test_chrtrunc();
  Test_chrrtrunc();
 }
/*===========================================================================
 *		End of Function Test_DLChr()
 *=========================================================================*/


#endif
/*===========================================================================
 *		End of Module Test Routines
 *=========================================================================*/