// textUtility.hw.cpp

#include "textUtility2.h"    // includes <iostream>


/*
 * int digitCount(int n)
 *
 * Counts base-10 digits in the specifed integer.
 *
 * Parameter:
 *    n - the integer whose digits are to be counted.
 *
 * Returns:
 *    The number of digits in a base-10
 *    representation of n.
 */
int digitCount(int n)
{
   if ( n < 0 )
      n = 0 - n;

   int numberOfDigits = 1;

   while ( n >= 10 )  {
      n = n / 10;
      numberOfDigits++;
   }  // while

   return numberOfDigits;
}  // function digitCount


/*
 * void printRightJustified(int numberToPrint,
 *                          int columnWidth,
 *                          ostream& output)
 *
 * Prints an integer with a suitable number
 * of leading spaces so that it can be lined
 * up in a column with a specified width,
 * provided that width is at least equal to
 * the number of digits in the printed number.
 *
 * Parameters:
 *    numberToPrint - the integer to print.
 *    columnWidth - total number of characters
 *        on each line of the column, including
 *        leading spaces.  Must be at least
 *        equal to the number of digits in
 *        numberToPrint.
 *    output - an output stream
 *        Precondition:  The state of output
 *           is true, and numberToPrint and
 *           its leading spaces have not yet
 *           been printed.
 *        Postcondition:  The state of output
 *           is still true, and numberToPrint
 *           has been printed.
 */
void printRightJustified(int numberToPrint,
                         int columnWidth,
                         ostream& output)
{
   int nonSpaceCharacters
            = digitCount(numberToPrint)
               + ( ( numberToPrint < 0 ) ? 1 : 0 );
   int leadingSpaces
            = columnWidth - nonSpaceCharacters;

   for ( int i = 0; i < leadingSpaces; i++ )
      output << " ";

   output << numberToPrint;
}  // function printRightJustified


/*
 * void printRightJustified(float numberToPrint,
 *                          int decimalPlaces,
 *                          int columnWidth,
 *                          ostream& output)
 *
 * Prints a floating-point number in fixed-point
 * format with a suitable number of leading
 * spaces so that it can be lined up in a column
 * with a specified width, provided that width
 * is at least one greater than the total the
 * number of digits in the printed number,
 * including digits in whole number portion and
 * the requested number of decimal places. and
 * provided the total number of digits does not
 * exceed the precision of the machine's
 * floating-point format.
 *
 * Parameters:
 *    numberToPrint - a floating-point number
 *        to be printed in fixed-point format.
 *        The total of the number of digits in
 *        the whole number portion plus
 *        decimalPlaces should not exceed the
 *        precision of the machine's
 *        floating-point format.  (If it does,
 *        the least significant digits will be
 *        meaningless.
 *    decimalPlaces - number of digits printed
 *        to the right of the decimal point.
 *    columnWidth - total number of characters
 *        on each line of the column, including
 *        leading spaces.  Must be at least
 *        one greater than the total number of
 *        printed digits.
 *    output - an output stream
 *        Precondition:  The state of output
 *           is true, and numberToPrint and
 *           its leading spaces have not yet
 *           been printed.
 *        Postcondition:  The state of output
 *           is still true, and numberToPrint
 *           has been printed.
 */
void printRightJustified(float numberToPrint,
                         int decimalPlaces,
                         int columnWidth,
                         ostream& output)
{
   bool negative = ( numberToPrint < 0 );

   if ( negative )
      numberToPrint = 0 - numberToPrint;

   // Assertion:  numberToPrint is now non-negative.
   // Its original sign is remembered by boolean
   // variable negative.

   // Print leading spaces, whole number part,
   // and decimal point:
   int wholeNumberPart = (int) numberToPrint;
   int nonSpaceCharacters
            = digitCount(wholeNumberPart)
               + ( negative ? 1 : 0 )    // minus sign
               + 1    // decimal point
               + decimalPlaces;
   int leadingSpaces
            = columnWidth - nonSpaceCharacters;
   for ( int i = 0; i < leadingSpaces; i++ )
      output << " ";
   output << (negative ? "-" : "" )
                << wholeNumberPart << ".";

   // Generate fractional part:
   float fractionalPart
             = numberToPrint - wholeNumberPart;
   for ( int i = 0; i < decimalPlaces; i++ )
      fractionalPart = fractionalPart * 10;
   int fractionalPartToPrint = (int) fractionalPart;

   // Print fractional part with leading zeroes, if any:
   int nonZeroDigits = digitCount(fractionalPartToPrint);
   int leadingZeroes = decimalPlaces - nonZeroDigits;
   for ( int i = 0; i < leadingZeroes; i++ )
      output << "0";
   output << fractionalPartToPrint;
}  // function printRightJustified(float, int, int, ostream&)


/*
 * void pause()
 *
 * Pauses the display. 
 * Prompts the user to press ENTER to continue.
 * Lets the program continue after the user presses ENTER.
 *
 * Global variables:
 *    cin -- an istream object for interactive input.
 *           cin is an external variable, declared
 *           in library header file <iostream>.
 *       Precondition:  The state of cin is true.  (All
 *           previous interactive inputs were successful.)
 *       Postcondition:  The state of cin is still true,
 *    cout -- an ostream object for output to the terminal.
 *           cout is an external variable, declared
 *           in library header file <iostream>.
 *       Precondition:  The state of cout is true.
 *       Postcondition:  The state of cout is still true.
 */
void pause()
{
   cout << endl << "Press ENTER to continue....";
   char x;
   do  {
      cin.get(x);
   } while ( x != '\n' );   // i.e. until reaching newline character
   cout << endl;
}  // function pause


/*
 * bool isAscii(char x)
 *
 * Tests whether the specified character is
 * in the ASCII range.
 *
 * Parameter:
 *    x - the character (byte) to be tested.
 *
 * Returns:
 *    true if x is in the ASCII range (0 to 127),
 *    false otherwise.
 */
bool isAscii(char x)
{
   return( x >= 0 );
}  // function isAscii


/*
 * bool isAsciiDigit(char x)
 *
 * Tests whether the specified character is an ASCII digit.
 *
 * Parameter:
 *    x - the character (byte) to be tested.
 *
 * Returns:
 *    true if x is an ASCII digit ('0' to '9'),
 *    false otherwise.
 */
bool isAsciiDigit(char x)
{
   return( (x >= '0' && x <= '9'));
}  // function isAsciiDigit


/*
 * bool isAsciiLetter(char x)
 *
 * Tests whether the specified character is an ASCII letter.
 *
 * Parameter:
 *    x - the character (byte) to be tested.
 *
 * Returns:
 *     true if x is an ASCII letter
 *     ('A' to 'Z' or 'a' to 'z'),
 *     false otherwise.
 */
bool isAsciiLetter(char x)
{
   return( (x >= 'A' && x <= 'Z') ||
           (x >= 'a' && x <= 'z'));
}  // function isAsciiLetter


/*
 * bool isSpace(char x)
 *
 * Tests whether the specified character is a space.
 *
 * Parameter:
 *    x - the character (byte) to be tested.
 *
 * Returns:
 *    true if x is a space (' '),
 *    false otherwise.
 */
bool isSpace(char x)
{
   return ( x == ' ' );
}  // function isSpace


/*
 * bool isAsciiControl(char x)
 *
 * Tests whether the specified character is an
 * ASCII control character.
 *
 * Parameter:
 *    x - the character (byte) to be tested.
 *
 * Returns:
 *    true if x is an ASCII control
 *    character (0 to 31, or 127),
 *    false otherwise.
 */
bool isAsciiControl(char x)
{
   return( x < 32 || x == 127 );
}  // function isAsciiControl


/*
 * bool containsAsciiControl(const char text[])
 *
 * Tests whether the specified string contains
 * ASCII control characters.
 *
 * Parameter:
 *    text -  the string to be tested.
 *
 * Returns:
 *    true if text contains one or more
 *    ASCII control characters,
 *    false otherwise.
 */
bool containsAsciiControl(const char text[])
{
   // Search for an ASCII control character,
   // and return true when one is found:
   int index = 0;
   while ( text[index] != '\0' )
      if ( isAsciiControl(text[index++]) )
          return true;

   // Assertion:  If we have reached this point,
   // text contains no ASCII control characters.

   return false;
}  // function containsAsciiControl


/*
 * bool isNaturalNumber(const char text[])
 *
 * Tests whether the specified string represents
 * a natural number, i.e. a non-negative integer.
 * A string represents a natural number if, and
 * only if, it contains at least one character
 * and all its characters are digits.
 *
 * Parameter:
 *    text -  the string to be tested.
 *
 * Returns:
 *    true if text represents a natural number,
 *    false otherwise.
 */
bool isNaturalNumber(const char text[])
{
   if ( text[0] == '\0' )
      return false;

   int index = 0;
   while ( text[index] != '\0' )   {
      if ( ! isAsciiDigit(text[index]) )
          return false;
      index++;
   }  // while

   return true;
}  // function isNaturalNumber


/*
 * int toDigitValue(char x)
 *
 * Converts a digit character to its
 * intended numeric value.
 *
 * Parameter:
 *    x - the character to be converted
 *
 * Returns:
 *    the intended numeric value of the
 *    character x, if x is a digit.
 *    Returns -1 if x is not a digit.
 */
int toDigitValue(char x)
{
   if ( isAsciiDigit(x) )
      return ( x - '0' );
   else
      return  -1;
}  // function toDigitValue


/*
 * bool parseNaturalNumber(const char text[],
 *                         int& number)
 *
 * Converts a string to a non-negative integer, if the
 * string consists solely of digit characters.
 *
 * Parameters:
 *    text - the string to be converted
 *    number -  the intended natural number value.
 *       Precondition: none
 *       Postcondition:  number is the intended
 *          integer value represented by the string,
 *          if the string represents a valid natural
 *          number value.  Otherwise, number is
 *          unchanged.
 *
 * Returns:
 *    true if a non-negative integer was successfully
 *    read from text, false otherwise.
 */
bool parseNaturalNumber(const char text[],
                        int& number)
{
   // Check validity of text:
   if ( !isNaturalNumber(text) )
      return false;

   // Assertion:  If we have reached this point,
   // text represents a valid natural number.

   // Read from text the natural number that
   // it represents:
   number = 0;
   int index = 0;
   while ( text[index] != '\0' )  {
      number = (number * 10) + toDigitValue(text[index]);
      index++;
   }  // while
   return true;
}  // function parseNaturalNumber


/*
 * bool isInteger(const char text[])
 *
 * Tests whether the specified string represents
 * an integer.  A string represents an integer if,
 * and only if, it consists of a string representing
 * a natural number preceded by an optional leading
 * minus sign. 
 *
 * Parameter:
 *    text -  the string to be tested.
 *
 * Returns:
 *    true if text represents an integer,
 *    false otherwise.
 */
bool isInteger(const char text[])
{
   if ( text[0] == '\0' )
      return false;

   if ( text[0] == '-' )
      text = text + 1;
   return isNaturalNumber(text);
}  // function isInteger


/*
 * int compare(const char text1[], const char text2[])
 *
 * Determines the lexicographical ordering of
 * two C-strings.
 *
 * Parameters:
 *    text1 - a C-string
 *    text2 - another C-string
 *
 * Returns:
 *    An integer < 0, if text1 precedes
 *      text2 in a lexicographical ordering;
 *    an integer > 0, if text2 precedes
 *      text1 in a lexicographical ordering; or
 *    0, if text1 equals text2.
 */
int compare(const char text1[], const char text2[])
{
   int index = 0;
   while ( (text1[index] == text2[index])
           && (text1[index] != '\0') )
      index++;
   return (text1[index] - text2[index]);
}  // function compare


/*
 * bool isForbinInt(const char text[])
 *
 * Tests whether the specified string represents
 * a value of type int on forbin.  A string represents
 * such a value if, and only if, it consists of a
 * string representing an integer and that integer is
 * within the int range on forbin, i.e. within the
 * range -2147483648 to 2147483647, inclusive.
 *
 * Parameter:
 *    text -  the string to be tested.
 *
 * Returns:
 *    true if text represents an int on forbin.
 *    false otherwise.
 */
bool isForbinInt(const char text[])
{
   if ( ! isInteger(text) )
      return false;

   bool negative = ( text[0] == '-' );

   if ( negative )
      text = text + 1;
   
   while ( text[0] == '0' )
      text = text + 1;

   int index = 0;
   while ( text[index] != 0 )
      index++;
   int length = index;

   if ( length > 10 )
      return false;

   if ( length < 10 )
      return true;

   if ( negative && compare(text, "2147483648") <= 0 )
      return true;

   if ( compare (text, "2147483648") < 0 )
      return true;

   return false;
}  // function isForbinInt


/*
 * bool parseForbinInt(const char text[],
 *                     int& number)
 *
 * Converts a string to an int value, if the
 * string represents an integer within forbin's
 * int range, i.e. within the range
 * -2147483648 to 2147483647, inclusive.
 *
 * Parameters:
 *    text - the string to be converted
 *    number -  the intended int value.
 *       Precondition: none
 *       Postcondition:  number is the intended
 *          integer value represented by the string,
 *          if the string represents a valid forbin
 *          int value.  Otherwise, number is
 *          unchanged.
 *
 * Returns:
 *    true if a non-negative integer was successfully
 *    read from text, false otherwise.
 */
bool parseForbinInt(const char text[],
                    int& number)
{
   // Check validity of text:
   if ( !isForbinInt(text) )
      return false;

   // Assertion:  If we have reached this point,
   // text represents a valid forbin int.

   bool negative = ( text[0] == '-' );

   if ( negative )
      text = text + 1;

   parseNaturalNumber(text, number);

   if ( negative )
      number = 0 - number;
   
   return true;
}  // method parseForbinInt