/*
 * Copyright (c) 2019, The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *    * Neither the name of The Linux Foundation nor the names of its
 *      contributors may be used to endorse or promote products derived
 *      from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "AEEstd.h"
#include "AEEBufBound.h"
#include "AEEsmath.h"
#include "AEEStdErr.h"
#include "std_dtoa.h"
//#include "math.h"

//==============================================================================
//   Macro definitions
//==============================================================================

#define  ISDIGIT(c)              ( (c) >= '0' && (c) <= '9')
#define  TOLOWER(c)              ( (c) | 32 )   // works only for letters
#define  FAILED(b)               ( (b) != AEE_SUCCESS ? TRUE : FALSE )
#define  CLEANUP_ON_ERROR(b,l)   if( FAILED( b ) ) { goto l; }
#define  ROUND(d, p)             fp_round( d, p )
#define  FP_POW_10(n)            fp_pow_10(n)

//==============================================================================
//   Type definitions
//==============================================================================


// Formatting flags

#define FF_PLUS     1    // '+'
#define FF_MINUS    2    // '-'
#define FF_POUND    4    // '#'
#define FF_BLANK    8    // ' '
#define FF_ZERO    16    // '0'

typedef struct {

   // Parsed values (from "%..." expression)

   int      flags;          // FF_PLUS, FF_MINUS, etc.
   char     cType;          // d, s, c, x, X, etc.
   int32    nWidth;         // number preceding '.' : controls padding
   int32    nPrecision;     // number following '.'  (-1 if not given)

   // Computed values

   const char *  pszStr;         // string holding prefix + value
   int           nPrefix;        // # of numeric prefix bytes in pszStr[]
   int           nLen;           // length of string (after prefix)
   int           nNumWidth;      // minimum numeric value size (pad with '0')

} FieldFormat;

typedef int (*pfnFormatFloat)(FieldFormat* me, double dNumber, char* pcBuffer);

//==============================================================================
//   Function definitions
//==============================================================================

// Read an unsigned decimal integer
//
static int ScanDecimal(const char **ppsz)
{
   int n = 0;
   const char *psz;

   for (psz = *ppsz; ISDIGIT(*psz); ++psz) {
      n = n*10 + (int) (*psz - '0');
   }
   *ppsz = psz;
   return n;
}


#define FORMATNUMBER_SIZE   24   // octal: 22 + '0' + null ;  decimal: 20 + sign + null


// Convert number to string, setting computed fields in FieldFormat.
//
//  pcBuf[] must have room for at least FORMATNUMBER_SIZE characters
//  return value: length of string.
//
static __inline void
FormatNumber(FieldFormat *me, char pcBuf[FORMATNUMBER_SIZE], uint64 uNum64)
{
   char cType = me->cType;
   const char *cpszDigits;
   char *pc = pcBuf;
   int nBase;
   char *pcRev;

   if (cType == 'p') {
      cType = 'X';
      me->nPrecision = 8;
   }

   if (me->nPrecision >= 0) {
      me->nNumWidth = me->nPrecision;
      // Odd thing: '0' flag is ignored for numbers when precision is
      // specified.
      me->flags &= ~FF_ZERO;
   } else {
      me->nNumWidth = 1;
   }

   // Output prefix

   if (( 'd' == cType || 'i' == cType)) {
      if ((int64)uNum64 < 0) {
         *pc++ = '-';
         uNum64 = (uint64)-(int64)uNum64;
      } else if (me->flags & FF_PLUS) {
         *pc++ = '+';
      } else if (me->flags & FF_BLANK) {
         *pc++ = ' ';
      }
   }

   if ((me->flags & FF_POUND) && 0 != uNum64) {
      if ('x' == TOLOWER(cType)) {
         *pc++ = '0';
         *pc++ = cType;
      } else if ('o' == cType) {
         *pc++ = '0';
         // Odd thing about libc printf: "0" prefix counts as part of minimum
         // width, but "0x" prefix does not.
         --me->nNumWidth;
      }
   }
   me->nPrefix = pc - pcBuf;

   // Output unsigned numeric value

   nBase = ('o' == cType          ? 8 :
            'x' == TOLOWER(cType) ? 16 :
            10);
   cpszDigits = ((cType == 'X') ? "0123456789ABCDEF"
                                : "0123456789abcdef");

   pcRev = pc;

   while (uNum64) {
      *pc++ = cpszDigits[uNum64 % (unsigned)nBase];
      uNum64 /= (unsigned)nBase;
   }

   *pc = '\0';

   me->pszStr = pcBuf;
   me->nLen = pc - pcRev;

   // Reverse string

   --pc;
   for (; pcRev < pc; ++pcRev, --pc) {
      char c = *pc;
      *pc = *pcRev;
      *pcRev = c;
   }
}

//
// This function converts the input floating point number dNumber to an
// ASCII string using either %f or %F formatting. This functions assumes
// that dNumer is a valid floating point number (i.e., dNumber is NOT
// +/-INF or NaN). The size of the output buffer pcBuffer should be at
// least STD_DTOA_FORMAT_FLOAT_SIZE.
//
static int ConvertFloat(FieldFormat* me, double dNumber, char* pcBuffer,
                        int nBufSize)
{
   int nError = AEE_SUCCESS;
   int32 nPrecision = 0;
   int nIndex = 0;
   BufBound OutBuf;
   char szIntegerPart[STD_DTOA_FORMAT_INTEGER_SIZE] = {0};
   char szFractionPart[STD_DTOA_FORMAT_FRACTION_SIZE] = {0};
   int nExponent = 0;
   char cType = TOLOWER(me->cType);

   // Set the precision for conversion
   nPrecision = me->nPrecision;
   if (nPrecision < 0) {
      // No precision was specified, set it to the default value if the
      // format specifier is not %a
      if (cType != 'a') {
         nPrecision = STD_DTOA_DEFAULT_FLOAT_PRECISION;
      }
   }
   else if ((0 == nPrecision) && ('g' == cType)) {
      nPrecision = 1;
   }

   if (cType != 'a') {
      // For %g, check whether to use %e of %f formatting style.
      // Also, set the precision value accordingly since in this case the user
      // specified value is really the number of significant digits.
      // These next few steps should be skipped if the input number is 0.
      if (dNumber != 0.0) {
         nExponent = fp_log_10(dNumber);
         if ('g' == cType) {
            if ((nExponent < -4) || (nExponent >= nPrecision)) {
               cType = 'e';
               nPrecision = nPrecision - 1;
            }
            else {
               cType = 'f';
               nPrecision = nPrecision - nExponent - 1;
            }
         }

         // For %e, convert the number to the form d.ddd
         if ('e' == cType) {
            dNumber = dNumber / FP_POW_10(nExponent);
         }

         // Now, round the number to the specified precision
         dNumber = ROUND(dNumber, nPrecision);

         // For %e, the rounding operation may have resulted in a number dd.ddd
         // Reconvert it to the form d.ddd
         if (('e' == cType) && ((dNumber >= 10.0) || (dNumber <= -10.0))) {
            dNumber = dNumber / 10.0;
            nExponent++;
         }
      }

      // Convert the decmial number to string
      nError = std_dtoa_decimal(dNumber, nPrecision, szIntegerPart, szFractionPart);
      CLEANUP_ON_ERROR(nError, bail);
   }
   else
   {
      // Conver the hex floating point number to string
      nError = std_dtoa_hex(dNumber, nPrecision, me->cType, szIntegerPart,
                            szFractionPart, &nExponent);
      CLEANUP_ON_ERROR(nError, bail);
   }


   //
   // Write the output as per the specified format.
   // First: Check for any prefixes that need to be added to the output.
   // The only possible prefixes are '-', '+' or ' '. The following rules
   // are applicable:
   // 1. One and only one prefix will be applicable at any time.
   // 2. If the number is negative, then '+' and ' ' are not applicable.
   // 3. For positive numbers, the prefix '+' takes precedence over ' '.
   //
   // In addition, we were dealing with a hex floating point number (%a),
   // then we need to write of the 0x prefix.
   //
   BufBound_Init(&OutBuf, pcBuffer, nBufSize);
   if (dNumber < 0.0) {
      // The '-' sign would have already been added to the szIntegerPart by
      // the conversion function.
      me->nPrefix = 1;
   }
   if (dNumber >= 0.0){
      if (me->flags & FF_PLUS) {
         BufBound_Putc(&OutBuf, '+');
         me->nPrefix = 1;
      }
      else if(me->flags & FF_BLANK) {
         BufBound_Putc(&OutBuf, ' ');
         me->nPrefix = 1;
      }
   }

   // For %a, write out the 0x prefix
   if ('a' == cType) {
      BufBound_Putc(&OutBuf, '0');
      BufBound_Putc(&OutBuf, ('a' == me->cType) ? 'x' : 'X');
      me->nPrefix += 2;
   }

   // Second: Write the integer part
   BufBound_Puts(&OutBuf, szIntegerPart);

   // Third: Write the decimal point followed by the fraction part.
   // For %g, we need to truncate the trailing zeros in the fraction.
   // Skip this if the '#' flag is specified
   if (!(me->flags & FF_POUND) && ('g' == TOLOWER(me->cType))) {
      for (nIndex = std_strlen(szFractionPart) - 1;
           (nIndex >= 0) && (szFractionPart[nIndex] == '0'); nIndex--) {
         szFractionPart[nIndex] = '\0';
      }
   }

   // The decimal point is specified only if there are some decimal digits.
   // However, if the '#' format specifier is present then the decimal point
   // will be present.
   if ((me->flags & FF_POUND) || (*szFractionPart != 0)) {
      BufBound_Putc(&OutBuf, '.');

      // Write the fraction part
      BufBound_Puts(&OutBuf, szFractionPart);
   }

   // For %e and %a, write out the exponent
   if (('e' == cType) || ('a' == cType)) {
      char* pcExpStart = NULL;
      char* pcExpEnd = NULL;
      char cTemp = 0;

      if ('a' == me->cType) {
         BufBound_Putc(&OutBuf, 'p');
      }
      else if ('A' == me->cType) {
         BufBound_Putc(&OutBuf, 'P');
      }
      else if (('e' == me->cType) || ('g' == me->cType)) {
         BufBound_Putc(&OutBuf, 'e');
      }
      else {
         BufBound_Putc(&OutBuf, 'E');
      }

      // Write the exponent sign
      if (nExponent < 0) {
         BufBound_Putc(&OutBuf, '-');
         nExponent = -nExponent;
      }
      else {
         BufBound_Putc(&OutBuf, '+');
      }

      // Write out the exponent.
      // For %e, the exponent should at least be two digits.
      // The exponent to be written will be at most 4 digits as any
      // overflow would have been take care of by now.
      if (BufBound_Left(&OutBuf) >= 4) {
         if ('e' == cType) {
            if (nExponent < 10) {
               BufBound_Putc(&OutBuf, '0');
            }
         }

         pcExpStart = OutBuf.pcWrite;
         do {
            BufBound_Putc(&OutBuf, '0' + (nExponent % 10));
            nExponent /= 10;
         } while (nExponent);
         pcExpEnd = OutBuf.pcWrite - 1;

         // Reverse the exponent
         for (; pcExpStart < pcExpEnd; pcExpStart++, pcExpEnd--) {
            cTemp = *pcExpStart;
            *pcExpStart = *pcExpEnd;
            *pcExpEnd = cTemp;
         }
      }
   }

   // Null-terminate the string
   BufBound_ForceNullTerm(&OutBuf);

   // Set the output parameters
   // We do not care if there was enough space in the output buffer or not.
   // The output would be truncated to a maximum length of
   // STD_DTOA_FORMAT_FLOAT_SIZE.
   me->pszStr = OutBuf.pcBuf;
   me->nLen = BufBound_ReallyWrote(&OutBuf) - me->nPrefix - 1;

bail:

   return nError;
}

//
// This is a wrapper function that converts an input floating point number
// to a string based on a given format specifier %e, %f or %g. It first checks
// if the specified number is a valid floating point number before calling
// the function that does the conversion.
//
// The size of the output buffer pcBuffer should be at least STD_DTOA_FORMAT_FLOAT_SIZE.
//
static int FormatFloat(FieldFormat* me, double dNumber,
                       char pcBuffer[STD_DTOA_FORMAT_FLOAT_SIZE])
{
   int nError = AEE_SUCCESS;
   FloatingPointType NumberType = FP_TYPE_UNKOWN;

   // Check for error conditions
   if (NULL == pcBuffer) {
      nError = AEE_EBADPARM;
      goto bail;
   }

   // Initialize the output params first
   me->nLen = 0;
   me->nPrefix = 0;

   // Check for special cases such as NaN and Infinity
   nError = fp_check_special_cases(dNumber, &NumberType);
   CLEANUP_ON_ERROR(nError, bail);

   switch(NumberType) {
	  case FP_TYPE_NEGATIVE_INF:

		 if (('E' == me->cType) || ('F' == me->cType) || ('G' == me->cType)) {
			me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NEGATIVE_INF_UPPER_CASE,
								   STD_DTOA_FORMAT_FLOAT_SIZE);
		 }
		 else {
			me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NEGATIVE_INF_LOWER_CASE,
								   STD_DTOA_FORMAT_FLOAT_SIZE);
		 }

		 // Don't pad with 0's
		 me->flags &= ~FF_ZERO;

		 break;

	  case FP_TYPE_POSITIVE_INF:

		 if (('E' == me->cType) || ('F' == me->cType) || ('G' == me->cType)) {
			me->nLen = std_strlcpy(pcBuffer, STD_DTOA_POSITIVE_INF_UPPER_CASE,
								   STD_DTOA_FORMAT_FLOAT_SIZE);
		 }
		 else {
			me->nLen = std_strlcpy(pcBuffer, STD_DTOA_POSITIVE_INF_LOWER_CASE,
								   STD_DTOA_FORMAT_FLOAT_SIZE);
		 }

		 // Don't pad with 0's
		 me->flags &= ~FF_ZERO;

		 break;

	  case FP_TYPE_NAN:

		 if (('E' == me->cType) || ('F' == me->cType) || ('G' == me->cType)) {
			me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NAN_UPPER_CASE,
								   STD_DTOA_FORMAT_FLOAT_SIZE);
		 }
		 else
		 {
			me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NAN_LOWER_CASE,
								   STD_DTOA_FORMAT_FLOAT_SIZE);
		 }

		 // Don't pad with 0's
		 me->flags &= ~FF_ZERO;

		 break;

	  case FP_TYPE_GENERAL:

		 nError = ConvertFloat(me, dNumber, pcBuffer,
                               STD_DTOA_FORMAT_FLOAT_SIZE);
		 CLEANUP_ON_ERROR(nError, bail);

		 break;

	  default:

		 // This should only happen if this function has been modified
		 // to support other special cases and this block has not been
		 // updated.
		 nError = AEE_EFAILED;
		 goto bail;
   }

   // Set the output parameters
   me->pszStr = pcBuffer;


bail:

   return nError;
}

static int std_strlprintf_inner(char *pszDest, int nDestSize,
                                const char *cpszFmt, AEEVaList args,
                                pfnFormatFloat pfnFormatFloatFunc)
{
   BufBound bb;
   const char *pcIn = cpszFmt;

   BufBound_Init(&bb, pszDest, nDestSize);

   for (;;) {
      FieldFormat ff;
      const char *pcEsc;
      char achBuf[FORMATNUMBER_SIZE];
      char achBuf2[STD_DTOA_FORMAT_FLOAT_SIZE];
      char cType;
      boolean bLong = 0;

      pcEsc = std_strchrend(pcIn, '%');
      BufBound_Write(&bb, pcIn, pcEsc-pcIn);

      if (0 == *pcEsc) {
         break;
      }
      pcIn = pcEsc+1;

      //----------------------------------------------------
      // Consume "%..." specifiers:
      //
      //   %[FLAGS] [WIDTH] [.PRECISION] [{h | l | I64 | L}]
      //----------------------------------------------------

      std_memset(&ff, 0, sizeof(FieldFormat));
      ff.nPrecision = -1;

      // Consume all flags
      for (;;) {
         int f;

         f = (('+' == *pcIn) ? FF_PLUS  :
              ('-' == *pcIn) ? FF_MINUS :
              ('#' == *pcIn) ? FF_POUND :
              (' ' == *pcIn) ? FF_BLANK :
              ('0' == *pcIn) ? FF_ZERO  : 0);

         if (0 == f) {
            break;
         }

         ff.flags |= f;
         ++pcIn;
      }

      // Consume width
      if ('*' == *pcIn) {
         AEEVA_ARG(args, ff.nWidth, int32);
         pcIn++;
      } else {
         ff.nWidth = ScanDecimal(&pcIn);
      }
      if ((ff.flags & FF_MINUS) && ff.nWidth > 0) {
         ff.nWidth = -ff.nWidth;
      }

      // Consume precision
      if ('.' == *pcIn) {
         pcIn++;
         if ('*' == *pcIn) { // Can be *... (given in int * param)
            AEEVA_ARG(args, ff.nPrecision, int32);
            pcIn++;
         } else {
            ff.nPrecision = ScanDecimal(&pcIn);
         }
      }

      // Consume size designator
      {
         static const struct {
            char    szPre[3];
            boolean b64;
         } a[] = {
            { "l",  0, },
            { "ll", 1, },
            { "L",  1, },
            { "j",  1, },
            { "h",  0, },
            { "hh", 0, },
            { "z",  0 }
         };

         int n = STD_ARRAY_SIZE(a);

         while (--n >= 0) {
            const char *psz = std_strbegins(pcIn, a[n].szPre);
            if ((const char*)0 != psz) {
               pcIn = psz;
               bLong = a[n].b64;
               break;
            }
         }
      }

      //----------------------------------------------------
      //
      // Format output values
      //
      //----------------------------------------------------

      ff.cType = cType = *pcIn++;

      if ('s' == cType) {

         // String
         char *psz;

         AEEVA_ARG(args, psz, char*);
         ff.pszStr = psz;
         ff.nLen = std_strlen(psz);
         if (ff.nPrecision >= 0 && ff.nPrecision < ff.nLen) {
            ff.nLen = ff.nPrecision;
         }

      } else if ('c' == cType) {

         // char
         AEEVA_ARG(args, achBuf[0], int);
         achBuf[1] = '\0';
         ff.pszStr = achBuf;
         ff.nLen = 1;

      } else if ('u' == cType ||
                 'o' == cType ||
                 'd' == cType ||
                 'i' == cType ||
                 'p' == cType ||
                 'x' == TOLOWER(cType) ) {

         // int
         uint64 uArg64;

         if (bLong) {
            AEEVA_ARG(args, uArg64, int64);  // See how much room needed
         } else {
            uint32 uArg32;
            AEEVA_ARG(args, uArg32, int32);  // See how much room needed
            uArg64 = uArg32;
            if ('d' == cType || 'i' == cType) {
               uArg64 = (uint64)(int64)(int32)uArg32;
            }
         }

         FormatNumber(&ff, achBuf, uArg64);

      } else if (pfnFormatFloatFunc &&
                 ('e' == TOLOWER(cType) ||
                  'f' == TOLOWER(cType) ||
                  'g' == TOLOWER(cType) ||
                  'a' == TOLOWER(cType))) {

         // float
            int nError = AEE_SUCCESS;
            double dNumber;

            AEEVA_ARG(args, dNumber, double);
            nError = pfnFormatFloatFunc(&ff, dNumber, achBuf2);
            if (FAILED(nError)) {
               continue;
            }

      } else if ('\0' == cType) {

         // premature end
         break;

      } else {
         // Unknown type
         BufBound_Putc(&bb, cType);
         continue;
      }

      // FieldFormat computed variables + nWidth controls output

      if (ff.flags & FF_ZERO) {
         ff.nNumWidth = ff.nWidth - ff.nPrefix;
      }

      {
         int nLen1 = ff.nLen;
         int nLen2 = STD_MAX(ff.nNumWidth, nLen1) + ff.nPrefix;

         // Putnc() safely ignores negative sizes
         BufBound_Putnc(&bb, ' ', smath_Sub(ff.nWidth,nLen2));
         BufBound_Write(&bb, ff.pszStr, ff.nPrefix);
         BufBound_Putnc(&bb, '0', smath_Sub(ff.nNumWidth, nLen1));
         BufBound_Write(&bb, ff.pszStr+ff.nPrefix, nLen1);
         BufBound_Putnc(&bb, ' ', smath_Sub(-nLen2, ff.nWidth));
      }
   }

   AEEVA_END(args);

   BufBound_ForceNullTerm(&bb);

   /* Return number of bytes required regardless if buffer bound was reached */

   /* Note that we subtract 1 because the NUL byte which was added in
      BufBound_ForceNullTerm() is counted as a written byte; the semantics
      of both the ...printf() functions and the strl...() functions call for
      the NUL byte to be excluded from the count. */

   return BufBound_Wrote(&bb)-1;
}

int std_vstrlprintf(char *pszDest, int nDestSize,
                    const char *cpszFmt,
                    AEEVaList args)
{
   return std_strlprintf_inner(pszDest, nDestSize, cpszFmt, args, NULL);
}

int std_vsnprintf(char *pszDest, int nDestSize,
                  const char *cpszFmt,
                  AEEVaList args)
/*
   Same as std_vstrlprintf with the additional support of floating point
   conversion specifiers - %e, %f, %g and %a
*/
{
   return std_strlprintf_inner(pszDest, nDestSize, cpszFmt, args, FormatFloat);
}

int std_strlprintf(char *pszDest, int nDestSize, const char *pszFmt, ...)
{
   int nRet;
   AEEVaList args;

   AEEVA_START(args, pszFmt);

   nRet = std_vstrlprintf(pszDest, nDestSize, pszFmt, args);

   AEEVA_END(args);

   return nRet;
}

int std_snprintf(char *pszDest, int nDestSize, const char *pszFmt, ...)
/*
   Same as std_strlprintf with the additional support of floating point
   conversion specifiers - %e, %f, %g and %a
*/
{
   int nRet;
   AEEVaList args;

   AEEVA_START(args, pszFmt);

   nRet = std_vsnprintf(pszDest, nDestSize, pszFmt, args);

   AEEVA_END(args);

   return nRet;
}
