/**      ascii_functions.c
 **
 ** Author: Ted Schmitz
 ** Creation date: 10/17/2000
 **
 **      This module contains miscellaneous functions to convert and manipulate
 ** ASCII data.
 **
 ** REV  Date       Initls Comments
 ** 1.0  10-17-2000 TMS    Original creation
 ** 2.0  08-25-2003 TMS    Added ASCII decimal conversion routine
 ** 3.0  10-27-2005 TMS    Added ASCII decimal addition utility to add values
 ** ---                    larger than what will fit on one 32-bit variable.
 **/

#if !defined(__U_BOOT__)
#include <stdio.h>    /* standard library headers */
#include <string.h>
#else
#include "common.h"  /* common library headers for Uboot environment */
#endif

#include "ascii_functions.h"

/**   The following array represents the differnet values for the power of 10
 ** used to convert ASCII decimal to binary.
 **/
static int power_of_ten[] = {0,1,10,100,1000,10000,100000,1000000,10000000,
                             100000000,1000000000};

/*****************************************************************************
 **               Convert ASCII Hex Byte To Binary
 **
 **   This function converts a single hex data digit, represented in ASCII, to 
 ** its binary hex equivalent.  Each 8-bit ASCII hex character represents only
 ** 4 bits of actual binary data.
 **
 ** INPUT - "byte" ASCII byte to be converted.
 **
 ** OUTPUT - Nothing
 **
 ** RETURN - Returns the converted value back to calling code.
 **
 ****************************************************************************/
char conv_ascii_hex_byte_to_binary(char byte)
{
  char conv_byte;

  if (byte <= '9')
    conv_byte = (byte - '0');
  else
    conv_byte = ((byte & 0xdf) - '7');

  return conv_byte;
}

/*****************************************************************************
 **               Convert ASCII Hex String To Binary
 **
 **   This function converts an entire ASCII string of hex digits to its binary
 ** equivalent.  The ASCII string can be of any length as long as the binary
 ** field in which the converted data will be placed is at least 1/2 the length
 ** of the ASCII/Hex string to be converted.
 **
 ** INPUT - "hex_string" ASCII hex string to be converted.
 **       - "string_length" = how many ASCII bytes are to be converted.
 **         NOTE:  The "string_length" must always be an even number so we will
 **                always be converting at least 2 ASCII characters for every one
 **                binary byte converted.
 **       - "conv_data" = Destination of converted binary data.
 **       - "data_length" = length in bytes of binary destination field.
 **
 ** OUTPUT - Converted data will be stored in "conv_data" field supplied by 
 **          calling code.
 **
 ** RETURN - If no problems were encountered during the conversion, 
 **          "NO_PROBLEM_DETECTED" will be returned to the calling code,
 **          "PROBLEM_DETECTED" will be returned otherwise.
 **
 ****************************************************************************/
int conv_ascii_hex_string_to_binary(char * hex_string,
				    int string_length,
				    char * conv_data, 
				    int data_length)
{
#if 1
  unsigned char upper_nib;              /* upper nibble of next conv byte */
  unsigned char lower_nib;              /* lower nibble of next conv byte */
  int string_index;                     /* index into source string */
  int binary_index;                     /* index into binary dest location */
  int i;

#ifdef notdef
  printf("1.hex_string = [%s]\n",hex_string);
  printf("In conv_ascii_hex_string_to_binary...\n"
	 "hex_string = %p\n"
	 "string_length = %02x\n"
	 "conv_data = %p\n"
	 "data_length = %02x\n",
	 hex_string, string_length, conv_data, data_length);
#endif

  binary_index = (data_length -1);      /* point to last byte of dest */

  /* preclear all the destination bytes */
  for (i = 0; i < data_length ; i++)
    conv_data[i] = 0;

  /**   Convert the ASCII string starting from last char then prceeding 
   ** backwards to first character.  Do the same with the destination
   ** binary location.
   **/
  for (string_index = (string_length -1); string_index >= 0; string_index -=2)
    {
    /* Convert next char to upper nible of data */
    upper_nib = conv_ascii_hex_byte_to_binary(hex_string[string_index-1]);

    /* convert next char to lower nibble of data */
    lower_nib = conv_ascii_hex_byte_to_binary(hex_string[string_index]);

    /* combine upper and lower into one binary hex value and store */
    conv_data[binary_index] = (((upper_nib << 4) & 0xf0) | (lower_nib & 0x0f));

    binary_index--;                   /* dec to previous byte of dest */
    }

  return NO_PROBLEM_DETECTED;
#else
    unsigned int    val = 0;        /* Converted value */

    /* Don't penalize user for entering x or 0x preceeding string */
    if (*hex_string == 'x')
        hex_string++;
    if (*hex_string == '0' && hex_string[1] == 'x')
        hex_string += 2;

    /* Each byte of result requires upto 2 ascii characters */
    if (strlen(hex_string) > 2 * data_length)
    {
        printf("\nconv_ascii_hex_string_to_binary(): Input string too long!\n");
        return PROBLEM_DETECTED;
    }

    /* As we move left to right, shift previous result and add
       current character value */
    while (*hex_string != '\0') {
        val = val << 4 | conv_ascii_hex_byte_to_binary(*hex_string);
        hex_string++;
    }

    /* Cast value into requested data size */
    switch (data_length) {
    case 1:
        *(unsigned char *) conv_data = (unsigned char) val;
        break;
    case 2:
        *(unsigned short *) conv_data = (unsigned short) val;
        break;
    case 4:
        *(unsigned int *) conv_data = (unsigned int) val;
        break;
    default:        /* Size doesn't match known data type */
        return PROBLEM_DETECTED;
    }

    return NO_PROBLEM_DETECTED;
#endif  /* 0 */
}

/*****************************************************************************
 **               Convert ASCII Decimal String To Binary
 **
 **   This function converts an entire ASCII string of decimal digits to its 
 ** binary equivalent.  The ASCII string can be up to a maximum length
 ** of "MAX_ASCII_DEC_LEN" ASCII digits.
 **
 ** INPUT - "dec_string" = pointer to ASCII decimal string to be converted.
 **
 ** OUTPUT -Nothing
 **
 ** RETURN - If no problems were encountered during the conversion, the
 **          signed, converted number will be returned to the calling code.
 **          If any problems are detected in the source string, like a 
 **          non-decimal character is encountered, or the string exceeds
 **          the maximum digits allowed, "PROBLEM_DETECTED" will be returned 
 **          to the calling code.
 **
 ****************************************************************************/
int conv_ascii_decimal_string_to_binary(char * dec_string)
{
  int string_length;                    /* length of ASCII string */
  int ret_value;                        /* calc dec value ret to calling code */
  char conv_byte;                       /* decimal byte converted to binary */
#if 0 /* debug format */
  char asc_byte;                        /* next ASCII byte to be converted */
  int multiplier;                       /* power of ten used for next calc */
#endif
  int i;

  string_length = strlen(dec_string);

  if (string_length > MAX_ASCII_DEC_LEN)/* check string for max length */
    return PROBLEM_DETECTED;

  ret_value = 0;

  for (i = 0; i < string_length; i++)
    {
    /* make sure each character is an ASCII decimal number */
    if ((dec_string[i] < '0') || (dec_string[i] > '9'))
      return PROBLEM_DETECTED;

#if 0 /* debug format.  Both code segments below work correctly */
    asc_byte = dec_string[i];
    conv_byte = conv_ascii_hex_byte_to_binary(asc_byte);
    multiplier = power_of_ten[string_length - i];
    ret_value += (conv_byte * multiplier);
#else
    conv_byte = conv_ascii_hex_byte_to_binary(dec_string[i]);
    ret_value += (power_of_ten[string_length - i] * conv_byte);
#endif
    }

  return ret_value;                     /* return converted binary number */
}

/*****************************************************************************
 **                     Display And Space To Specified Column
 **
 **   This function calculates the length of the string supplied then
 ** spaces to the column specified after the string is displayed.
 **
 ** INPUT - "msg" = ASCII message to be displayed.
 **       - "column" = column where cursur is to end up after message is
 **         displayed.
 **
 ** OUTPUT - "msg" will be displayed on the screen followed by enough spaces
 **          to reach the column specified.
 **
 ** RETURN -Nothing
 **
 ****************************************************************************/
void disp_then_space(const char * msg, int column)
{
  int spaces;                       /* # of spaces to get to specified column */

  spaces = (column - (strlen(msg)));/* calc spaces to specified column */

  printf("%s",msg);                 /* print message */

  while (spaces > 0)                /* print approp # of spaces after msg */
    {
    printf(" ");
    spaces--;
    }
}

/*****************************************************************************
 **              Low Level String To Long Function (strtol)
 **
 **   This function performs a simpler form of  the "strtol" function but
 ** does not use any library utilities to do the conversion.
 **
 ** INPUT - "src_string" = null-terminated string to be converted.
 **       - "endptr" = dummy pointer to resolve "strtol" calls.  Not actually
 **          used in this function.
 **       - "radix" = number base in which the the value is to be converted as
 **          follows: "CONV_TO_DEC", "CONV_TO_HEX".
 **
 ** OUTPUT - Nothing
 **
 ** RETURN - Binary value is returned or zero if value in parameter "radix"
 **          is not recognized.
 **
 ****************************************************************************/
long ll_strtol(const char * src_string, char * endptr, int radix)
{
  char copy_of_string[MAX_ASCII_DEC_LEN+2];/* copy of orig string */
  long conv_num;                        /* converted long value */
  int str_length;                       /* length of source string */
  int i;

  /* count & copy source string */
  str_length = 0;
  for (i=0; i<10 ; i++)
    {
    if (src_string[i] != 0)
      {
      copy_of_string[i] = src_string[i]; /* copy next valid char */
      str_length++;                      /* inc char count */
      }
    else
      {
      copy_of_string[i] = 0;             /* terminate copy of string */
      break;                             /* exit when null is detected */
      }
    }

  if (radix == CONV_TO_HEX)
    {
    /* adjust length of source string to even bytes if necessary */
    if ((str_length % 2) == 1)
      {
      copy_of_string[str_length + 1] = 0; /* install termination null now */

      for (i=0; i < str_length; i++)      /* move string down one character */
        copy_of_string[(str_length - i)] = 
          copy_of_string[((str_length - i) - 1)];

      copy_of_string[0] = ASCII_ZERO;    /* add zero to beg of string */
      str_length++;                      /* inc char count */
      }

    /* convert the first 8 hex-ASCII chars to a 4-byte binary value */
    conv_ascii_hex_string_to_binary(copy_of_string, str_length, 
                                    (char *) &conv_num, 4);
    }
  else if (radix == CONV_TO_DEC)
    {
    /* convert up to "MAX_ASCII_DEC_LEN" decimal-ASCII chars to a 4-byte
       binary value */
    conv_num = conv_ascii_decimal_string_to_binary(copy_of_string);
    }
  else
    conv_num = 0;                  /* clear results if radix is unrecognized */

  return conv_num;                    /* ret converted number */
}

/*****************************************************************************
 **              Low Level String To Integer Function (atoi)
 **
 **   This function performs a simpler form of  the "atoi" function but
 ** does not use any library utilities to do the conversion.
 **
 ** INPUT - "src_string" = null-terminated ASCI decimal string to be converted.
 **
 ** OUTPUT - Nothing
 **
 ** RETURN - Binary value is returned.
 **
 ****************************************************************************/
int ll_atoi(const char * src_string)
{
  char * dummy_ptr;                     /* dummy pointer (not used) */
  int ret_value;                        /* data ret to calling code */

  dummy_ptr = 0;                        /* get rid of warning */
  ret_value = (int) ll_strtol(src_string, dummy_ptr, CONV_TO_DEC);

  return ret_value;
}

/*****************************************************************************
 **             Add Two Large ASCII Decimal Numeric Values
 **
 **   This function takes two decimal numbers, represented in ASCII format,
 ** and adds them together.  This function can add numbers much larger than
 ** can fit into a single 32-variable.  To do this, the ASCII decimal values 
 ** are first converted to BCD, then added together with a simple algorithm,
 ** then converted back to ASCII.  Virtually any length of ASCII decimal
 ** values can be added together as long as the destination string is large
 ** enough to handle the sum of the two values.
 **
 ** NOTE:  One way to manipulate and display integer values larger than will 
 **        fit into a single 32 bit location, without using floats, is to use 
 **        Binary Coded Decimal arithmatic across as many 32-values as needed.
 **        Here is an example of using a 64 bit integer counter:
 **        1 - Increment (or add to ) the low order 32-bit interger value.  
 **            If that caused the variable to overflow, then increment the 
 **            upper 32-bit portion of the 64-bit integer value.
 **        2 - To display the 64-bit value, convert the lower order 32-bit
 **            binary value to ASCII using a function similar to the one
 **            shown here: 
 **            length = sprintf(ascii_bytes_xfered, "%u",
 **                             dma_stats[dma_chan_no].bytes_xfered_lo);
 **        3 - Now use this function to add the ASCII decimal value 
 **            "4294967296" to the lower ASCII decimal value for each count
 **            in the upper 32-bit portion of the of the 64-bit value.
 **        4 - Function "format_ascii_dec_value()" below, can optionally be
 **            used to format the large decimal ASCII value with commas in
 **            the appropriate places before the value is displayed.
 **
 **        Using this method, your code manipulates two 32-bit binary integer
 **        values, then coverts each to ASCII decimal to add and display
 **        the entire decimal value as described above.
 **
 ** INPUT - "ascii_num_1" = pointer to 1st null-terminated ASCI decimal 
 **         string (up to "MAX_ASCII_DEC_DIGITS" digits).
 **       - "ascii_num_2" = pointer to 2nd null-terminated ASCI decimal 
 **         string (up to "MAX_ASCII_DEC_DIGITS" digits) in which the sum of
 **         both numbers is stored.
 **
 ** OUTPUT - The sum of the two ASCII decimal values will be written back to
 **          the string pointed to by "ascii_num_2" and terminated with a null.
 **
 ** RETURN - The length, in bytes (not including the null terminator), of the
 **          newly calculated and converted ASCII decimal sum.
 **
 ****************************************************************************/
int add_two_ascii_dec_values(const char * ascii_num_1, char * ascii_num_2)
{
  char bcd_num_1[MAX_ASCII_DEC_DIGITS]; /* 1st ASCII string converted to BCD */
  char bcd_num_2[MAX_ASCII_DEC_DIGITS]; /* 2nd ASCII string converted to BCD */
  int len1;                             /* length of 1st ASCII string */
  int len2;                             /* length of 2nd ASCII string */
  int offset;                           /* offset into ASCII sum string */

  int i;

  /* clear working array variables */
  for (i = 0; i < MAX_ASCII_DEC_DIGITS; i++)
    bcd_num_1[i] = bcd_num_2[i] = 0;

  /* convert 1st ASCII decimal string to BCD */
  len1 = strlen(ascii_num_1);
  for(i = 0; i < len1; i++)
    {
    bcd_num_1[((MAX_ASCII_DEC_DIGITS-1)-i)] =
      (ascii_num_1[((len1-1)-i)] - ASCII_ZERO);
    }

  /* convert 2nd ASCII decimal string to BCD */
  len2 = strlen(ascii_num_2);
  for(i = 0; i < len2; i++)
    {
    bcd_num_2[((MAX_ASCII_DEC_DIGITS-1)-i)] =
      (ascii_num_2[((len2-1)-i)] - ASCII_ZERO);
    }

  /* add the BCD numbers together */
  for (i = (MAX_ASCII_DEC_DIGITS - 1); i >= 0; i--)
    {
    /* if adding next two numbers causes an overflow? */
    if ((bcd_num_1[i] + bcd_num_2[i]) > 9)
      {
      bcd_num_2[(i-1)]++;                 /* inc next highest digit */
      bcd_num_2[i] += (bcd_num_1[i] - 10);/* adjust the overflow digit */
      }
    else
      bcd_num_2[i] += bcd_num_1[i];       /* else just add the 2 digits */

    } /* end "for (i = (MAX_ASCII_DEC_DIGITS..." */

  /* convert the BCD sum back to ASCII */
  for (i = 0; i < MAX_ASCII_DEC_DIGITS; i++)
    bcd_num_2[i] += ASCII_ZERO;

  /* find the 1st non-zero digit in the ASCII sum string */
  for (offset = 0; offset < MAX_ASCII_DEC_DIGITS; offset++)
    {
    if (bcd_num_2[offset] != ASCII_ZERO)
      break;
    }

  /* copy the ASCII sum back to the user-specified string */
  for(i = 0; offset < MAX_ASCII_DEC_DIGITS; i++)
    {
    ascii_num_2[i] = bcd_num_2[offset];
    offset++;
    }

  ascii_num_2[i] = 0x00;                /* terminate the ASCII string */

  return i;                      /* return length of new ASCII sum string */
}

/*****************************************************************************
 **               Insert Commas In Decimal Ascii String
 **
 **   This function inserts commas in the appropriate places in the ASCII
 ** decimal string supplied by the calling code.  No commas are added if the
 ** string is less than 4 characters long.
 **
 ** INPUT - "ascii_dec_value" = pointer to null-terminated ASCI decimal 
 **         source string value (up to "MAX_ASCII_DEC_DIGITS" digits).
 **       - "formatted_ascii_bytes" = pointer to destination of formatted 
 **         ASCII decimal string.
 **
 ** OUTPUT - The formated ASCII decimal value will be written back to
 **          the string pointed to by "formatted_ascii_bytes" and terminated 
 **          with a null.  The calling code must ensure the destination 
 **          variable is big enough to hold all the original ASCII decimal
 **          digits plus any additional commas that may be added to the
 **          string after formatting is complete.
 **
 ** RETURN - Nothing
 **
 ****************************************************************************/
void format_ascii_dec_value(const char * ascii_dec_value, 
                           char * formatted_ascii_bytes)
{
  int dest_index;                       /* index of dest string */
  int src_index;                        /* index of source string */
  int length;                           /* ascii string length */
  int work;                             /* working register */
  int j,k;

  length = strlen(ascii_dec_value);     /* get length of source string */
  src_index = dest_index = 0;           /* clear indices */
  if (length > 3)
    {
    work = (length % 3);                /* get odd bytes at beg */
    if (work > 0)
      { /* execute if any preceding digits */
      for (j = 0 ; j < work; j++)       /* copy 1st odd bytes */
        formatted_ascii_bytes[dest_index++] = ascii_dec_value[src_index++];

      formatted_ascii_bytes[dest_index++] = ',';  /* insert comma */
      }

    length = ((length - work) / 3);     /* calc remaining 3-digit groups */
    for (k = 0; k < length; k++)
      {
      for (j = 0; j < 3; j++)           /* copy next 3 digits to format str */
        formatted_ascii_bytes[dest_index++] = ascii_dec_value[src_index++];

      formatted_ascii_bytes[dest_index++] = ','; /* ins comma after digits */
      }

    /*  Adjust index to last comma inserted in output string. */
    dest_index--;
    }
  else
    /* if src string < 4 digits, just copy source string to formatted string */
    for(dest_index = 0; dest_index < length; dest_index++)
      formatted_ascii_bytes[dest_index] = ascii_dec_value[dest_index];

  /**    Terminate the string by writing over last comma at end of string
   ** or writing a NULL after 1st 3 digits of smaller string.
   **/
  formatted_ascii_bytes[dest_index] = 0x00;
}

/*****************************************************************************
 **                 Is this a valid symbol for given radix
 **
 ** This function returns true or false (1 or 0) is the symbol supplied
 ** is a valid "digit" for the given radix. Currently only handles base
 ** 10 and 16.
 **
 ** INPUT - "c" ASCII symbol in question
 **         "radix" base system to check
 **
 ** OUTPUT - Nothing
 **
 ** RETURNS - 0 if symbol not valid for given radix
 **           1 if symbol is valid for given radix
 **
 ****************************************************************************/
int isValidSymbol(char c, int radix)
{
    switch (radix) {
    case 10:
        return (c >= '0' && c <= '9');
    case 16:
        return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
                (c >= 'A' && c <= 'F'));
    default:
        return 0;
    }

    return 0;
}

/*****************************************************************************
 **             Returns the decimal value of the given "digit"
 **
 ** This function returns the "value" of the supplied ASCII charater.
 ** Currently supports characters in base 10 or base 16.
 **
 ** INPUT - "c" ASCII character whose value we want
 **
 ** OUTPUT - Nothing
 **
 ** RETURNS - "value" of symbol in decimal
 **
 ****************************************************************************/
int valueOf(char c)
{
    if (c >= '0' && c <= '9')
        return (c - '0');
    else if (c >= 'a' && c <= 'z')
        return (c - 'a' + 10);
    else if (c >= 'A' && c <= 'Z')
        return (c - 'A' + 10);
    else
        return 0;
}

/*****************************************************************************
 **                 My version of strtol()
 **
 ** This function implements a basic strtol() which will handle parsing
 ** base 10 and base 16 signed integers. Handles +/-, leading 0's, and
 ** leading 'x'/'X' to indicate base 16. Will happily parse values
 ** larger than supported by an unsigend but will only return a size
 ** of unsigned so information will be lost as it's multiplied off the
 ** upper end. [For example, this routine cannot be used to parse
 ** an ethernet mac address which is 6 bytes long.]
 **
 ** INTPUT - "inStr" is the string to be converted
 **          "radix" is the most highest base we expect to find
 **             symbols for
 **
 ** OUTPUT - Nothing
 **
 ** RETURNS - unsigned representation of value
 **
 ****************************************************************************/
unsigned my_strtol(const char *inStr, int radix)
{
    int negate = 0;
    int ret = 0;
    int rad = 10;       /* Assume base 10 */

    /* Only non-symbol allowed to start the line is the sign character */
    if (*inStr == '+')
    {
        negate = 0;
        inStr++;
    }
    else if (*inStr == '-')
    {
        negate = 1;
        inStr++;
    }
    else if (!isValidSymbol(*inStr, radix))
    {
        return 0;
    }

    /* Skip leading 0's */
    while (*inStr == '0')
        inStr++;

    /* Look for 'x'/'X' */
    if (*inStr == 'x' || *inStr == 'X')
    {
        rad = 16;
        inStr++;
    }

    while (isValidSymbol(*inStr, rad))
    {
        ret = (ret * rad) + valueOf(*inStr);
        inStr++;
    }

    if (negate)
        return (-ret);
    else
        return (ret);
}

