/*************************************************************************************************/
/*!
 *  \file   print.c
 *
 *  \brief  Utility functions.
 *
 *          $Date: 2016-03-29 14:55:12 -0700 (Tue, 29 Mar 2016) $
 *          $Revision: 6524 $
 *
 *  Copyright (c) 2015-2017 ARM Ltd., all rights reserved.
 *  ARM confidential and proprietary.
 *
 *  IMPORTANT.  Your use of this file is governed by a Software License Agreement
 *  ("Agreement") that must be accepted in order to download or otherwise receive a
 *  copy of this file.  You may not use or copy this file for any purpose other than
 *  as described in the Agreement.  If you do not agree to all of the terms of the
 *  Agreement do not use this file and delete all copies in your possession or control;
 *  if you do not have a copy of the Agreement, you must contact ARM Ltd. prior
 *  to any use, copying or further distribution of this software.
 */
/*************************************************************************************************/

#include <string.h>

#include "print.h"

/**************************************************************************************************
  Macros
**************************************************************************************************/

/*! \brief    Detect hexadecimal digits. */
#define PRINT_IS_XDIGIT(c)     (((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'f')) || \
                                ((c >= 'A') && (c <= 'F')))

/*! \brief    Convert hexadecimal digit to integer. */
#define PRINT_XDIGIT_TO_INT(c)  (((c >= '0') && (c <= '9')) ? (uint8_t)(c - '0') : \
                                ((c >= 'a') && (c <= 'f')) ? (uint8_t)(c - 'a' + 10u) : \
                                ((c >= 'A') && (c <= 'F')) ? (uint8_t)(c - 'A' + 10u) : 0u)

/*************************************************************************************************/
/*!
 *  \fn     printFmtInt
 *
 *  \brief  Format an integer.
 *
 *  \param  pStr    Storage for formatted integer.
 *  \param  maxLen  Maximum number of characters to store.
 *  \param  i       Integer to format.
 *  \param  base    Integer base.
 *  \param  sign    TRUE if sign should be printed.
 *  \param  width   Width of field.
 *
 *  \return Number of characters stored.
 */
/*************************************************************************************************/
static int printFmtInt(char *pStr, int maxLen, int i, uint8_t base, bool_t sign, int width)
{
  char *s, *p = pStr;
  unsigned int u = (unsigned int) i;
  uint8_t use_width;
  int t;

  use_width = width;

  if (i == 0)
  {
    s = "0\0";
    width--;
    goto almost;
  }

  if (sign && base == 10 && i < 0)
  {
    *pStr++ = '-';
    u = (unsigned int)-i;
    width--;
  }
  s = pStr + maxLen - 1;
  *s = '\0';

  while (u && (!use_width || (width > 0)))
  {
    t = (unsigned int)u % base;
    if (t >= 10)
    {
      t += 'A' - '0' - 10;
    }
    *--s = (char)(t + '0');
    u /= base;
    width--;
  }

almost:
  while (width > 0)
  {
    *pStr++ = '0';
    width--;
  }
  strcpy(pStr, s);

  return strlen(p);
}

/*************************************************************************************************/
/*!
 *  \fn     printParseInt
 *
 *  \brief  Parse an integer from a string.
 *
 *  \param  pStr      String to parse.
 *  \param  pInt      Storage for parsed integer.
 *  \param  base      Integer base.
 *
 *  \return Number of characters consumed.
 */
/*************************************************************************************************/
static int32_t printParseInt(const char *pStr, uint32_t *pInt, uint32_t base)
{
  int32_t r = 0;

  if (base == 0)
  {
    if (*pStr == '0')
    {
      if (((*(pStr + 1) == 'x') || (*(pStr + 1) == 'X')) && PRINT_IS_XDIGIT(*(pStr + 2)))
      {
        r    += 2;
        pStr += 2;
        base  = 16;
      }
      else
      {
        base = 8;
      }
    }
    else
    {
      base = 10;
    }
  }
  else if (base == 16)
  {
    if (*pStr == '0')
    {
      if (((*(pStr + 1) == 'x') || (*(pStr + 1) == 'X')) && PRINT_IS_XDIGIT(*(pStr + 2)))
      {
        r    += 2;
        pStr += 2;
      }
    }
  }

  /* One character is required; all characters must be consumed. */
  *pInt = 0;
  do
  {
    char    c;
    uint8_t t;

    c = *pStr++;
    if (!PRINT_IS_XDIGIT(c))
    {
      break;
    }

    t = PRINT_XDIGIT_TO_INT(c);
    if (t >= base)
    {
      break;
    }

    *pInt *= base;
    *pInt += t;
    r++;
  } while (*pStr != '\0');

  return r;
}

/*************************************************************************************************/
/*!
 *  \fn     PrintVsn
 *
 *  \brief  Print a trace message.
 *
 *  \param  pStr      Storage for formatted string.
 *  \param  size      Maximum number of characters to store.
 *  \param  pFmt      Format string.
 *  \param  ap        Arguments.
 *
 *  \return Number of characters stored.
 */
/*************************************************************************************************/
uint32_t PrintVsn(char *pStr, uint32_t size, const char *pFmt, va_list ap)
{
    size_t len = 0;

    *pStr = 0;
    size--; /* Ensure we null-terminate within our buffer */

    while ((*pFmt != '\0') && (len < size))
    {
      uint32_t width = 0;

      /* normal */
      if (*pFmt != '%')
      {
        *pStr++ = *pFmt++;
        len++;
        continue;
      }

      pFmt++;
      if (*pFmt == '%')
      {
        *pStr++ = '%';
        len++;
        pFmt++;
        continue;
      }

      /* width */
      if (*pFmt == '0')
      {
        pFmt += printParseInt(pFmt, &width, 10u);
      }

      /* long (ignore) */
      if (*pFmt == 'l')
      {
        pFmt++;
      }

      switch(*pFmt)
      {
        /* character */
        case 'c':
        {
          char tmp = va_arg(ap, int);
          *pStr++  = tmp;
          len++;
          break;
        }
        /* unsigned decimal integer */
        case 'u':
        {
          unsigned int tmp    = va_arg(ap, unsigned int);
          uint8_t      lenTmp = printFmtInt(pStr, size - len, tmp, 10, 0, width);
          pStr += lenTmp;
          len  += lenTmp;
          break;
        }
        /* signed decimal integer */
        case 'd':
        {
          int     tmp    = va_arg(ap, int);
          uint8_t lenTmp = printFmtInt(pStr, size - len, tmp, 10, 1, width);
          pStr += lenTmp;
          len  += lenTmp;
          break;
        }
        /* pointer */
        case 'p':
        {
          unsigned long tmp    = va_arg(ap, unsigned long);
          uint8_t       lenTmp = printFmtInt(pStr, size - len, tmp, 16, 1, 8u);
          pStr += lenTmp;
          len  += lenTmp;
          break;
        }
        /* unsigned hexadecimal integer */
        case 'x':
        case 'X':
        {
          unsigned int tmp    = va_arg(ap, unsigned int);
          uint8_t      lenTmp = printFmtInt(pStr, size - len, tmp, 16, 0, width);
          pStr += lenTmp;
          len  += lenTmp;
          break;
        }
        /* string */
        case 's':
        {
          char *tmp = va_arg(ap, char *);
          while ((tmp != NULL) && (*tmp != '\0') && (len < size))
          {
            *pStr++ = *tmp++;
            len++;
          }
          break;
        }
        default:
          break;
      }
      pFmt++;
    }

    /* Null-terminate output. */
    *pStr = 0;

    if (len > size)
    {
      /* Compensate for -1 earlier. */
      return size + 2;
    }

    return len;
}
