/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/FloatingPoint.h"

#include "txXSLTFunctions.h"
#include "nsGkAtoms.h"
#include "txIXPathContext.h"
#include "txStylesheet.h"
#include <math.h>
#include "txNamespaceMap.h"

#include "prdtoa.h"

#define INVALID_PARAM_VALUE u"invalid parameter value for function"_ns

const char16_t txFormatNumberFunctionCall::FORMAT_QUOTE = '\'';

/*
 * FormatNumberFunctionCall
 * A representation of the XSLT additional function: format-number()
 */

/*
 * Creates a new format-number function call
 */
txFormatNumberFunctionCall::txFormatNumberFunctionCall(
    txStylesheet* aStylesheet, txNamespaceMap* aMappings)
    : mStylesheet(aStylesheet), mMappings(aMappings) {}

void txFormatNumberFunctionCall::ReportInvalidArg(txIEvalContext* aContext) {
  nsAutoString err(INVALID_PARAM_VALUE);
#ifdef TX_TO_STRING
  err.AppendLiteral(": ");
  toString(err);
#endif
  aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG);
}

/*
 * Evaluates this Expr based on the given context node and processor state
 * @param context the context node for evaluation of this Expr
 * @param cs the ContextState containing the stack information needed
 * for evaluation
 * @return the result of the evaluation
 */
nsresult txFormatNumberFunctionCall::evaluate(txIEvalContext* aContext,
                                              txAExprResult** aResult) {
  *aResult = nullptr;
  if (!requireParams(2, 3, aContext)) return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;

  // Get number and format
  double value;
  txExpandedName formatName;

  nsresult rv = evaluateToNumber(mParams[0], aContext, &value);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString formatStr;
  rv = mParams[1]->evaluateToString(aContext, formatStr);
  NS_ENSURE_SUCCESS(rv, rv);

  if (mParams.Length() == 3) {
    nsAutoString formatQName;
    rv = mParams[2]->evaluateToString(aContext, formatQName);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = formatName.init(formatQName, mMappings, false);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  txDecimalFormat* format = mStylesheet->getDecimalFormat(formatName);
  if (!format) {
    nsAutoString err(u"unknown decimal format"_ns);
#ifdef TX_TO_STRING
    err.AppendLiteral(" for: ");
    toString(err);
#endif
    aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG);
    return NS_ERROR_XPATH_INVALID_ARG;
  }

  // Special cases
  if (std::isnan(value)) {
    return aContext->recycler()->getStringResult(format->mNaN, aResult);
  }

  if (value == mozilla::PositiveInfinity<double>()) {
    return aContext->recycler()->getStringResult(format->mInfinity, aResult);
  }

  if (value == mozilla::NegativeInfinity<double>()) {
    nsAutoString res;
    res.Append(format->mMinusSign);
    res.Append(format->mInfinity);
    return aContext->recycler()->getStringResult(res, aResult);
  }

  // Value is a normal finite number
  nsAutoString prefix;
  nsAutoString suffix;
  int minIntegerSize = 0;
  int minFractionSize = 0;
  int maxFractionSize = 0;
  int multiplier = 1;
  int groupSize = -1;

  uint32_t pos = 0;
  uint32_t formatLen = formatStr.Length();
  bool inQuote;

  // Get right subexpression
  inQuote = false;
  if (mozilla::IsNegative(value)) {
    while (pos < formatLen &&
           (inQuote || formatStr.CharAt(pos) != format->mPatternSeparator)) {
      if (formatStr.CharAt(pos) == FORMAT_QUOTE) inQuote = !inQuote;
      pos++;
    }

    if (pos == formatLen) {
      pos = 0;
      prefix.Append(format->mMinusSign);
    } else
      pos++;
  }

  // Parse the format string
  FormatParseState pState = Prefix;
  inQuote = false;

  char16_t c = 0;
  while (pos < formatLen && pState != Finished) {
    c = formatStr.CharAt(pos++);

    switch (pState) {
      case Prefix:
      case Suffix:
        if (!inQuote) {
          if (c == format->mPercent) {
            if (multiplier == 1)
              multiplier = 100;
            else {
              ReportInvalidArg(aContext);
              return NS_ERROR_XPATH_INVALID_ARG;
            }
          } else if (c == format->mPerMille) {
            if (multiplier == 1)
              multiplier = 1000;
            else {
              ReportInvalidArg(aContext);
              return NS_ERROR_XPATH_INVALID_ARG;
            }
          } else if (c == format->mDecimalSeparator ||
                     c == format->mGroupingSeparator ||
                     c == format->mZeroDigit || c == format->mDigit ||
                     c == format->mPatternSeparator) {
            pState = pState == Prefix ? IntDigit : Finished;
            pos--;
            break;
          }
        }

        if (c == FORMAT_QUOTE)
          inQuote = !inQuote;
        else if (pState == Prefix)
          prefix.Append(c);
        else
          suffix.Append(c);
        break;

      case IntDigit:
        if (c == format->mGroupingSeparator)
          groupSize = 0;
        else if (c == format->mDigit) {
          if (groupSize >= 0) groupSize++;
        } else {
          pState = IntZero;
          pos--;
        }
        break;

      case IntZero:
        if (c == format->mGroupingSeparator)
          groupSize = 0;
        else if (c == format->mZeroDigit) {
          if (groupSize >= 0) groupSize++;
          minIntegerSize++;
        } else if (c == format->mDecimalSeparator) {
          pState = FracZero;
        } else {
          pState = Suffix;
          pos--;
        }
        break;

      case FracZero:
        if (c == format->mZeroDigit) {
          maxFractionSize++;
          minFractionSize++;
        } else {
          pState = FracDigit;
          pos--;
        }
        break;

      case FracDigit:
        if (c == format->mDigit)
          maxFractionSize++;
        else {
          pState = Suffix;
          pos--;
        }
        break;

      case Finished:
        break;
    }
  }

  // Did we manage to parse the entire formatstring and was it valid
  if ((c != format->mPatternSeparator && pos < formatLen) || inQuote ||
      groupSize == 0) {
    ReportInvalidArg(aContext);
    return NS_ERROR_XPATH_INVALID_ARG;
  }

  /*
   * FINALLY we're done with the parsing
   * now build the result string
   */

  value = fabs(value) * multiplier;

  // Make sure the multiplier didn't push value to infinity.
  if (value == mozilla::PositiveInfinity<double>()) {
    return aContext->recycler()->getStringResult(format->mInfinity, aResult);
  }

  // Make sure the multiplier didn't push value to infinity.
  if (value == mozilla::PositiveInfinity<double>()) {
    return aContext->recycler()->getStringResult(format->mInfinity, aResult);
  }

  // Prefix
  nsAutoString res(prefix);

  int bufsize;
  if (value > 1)
    bufsize = (int)log10(value) + 30;
  else
    bufsize = 1 + 30;

  auto buf = mozilla::MakeUnique<char[]>(bufsize);
  int bufIntDigits, sign;
  char* endp;
  PR_dtoa(value, 0, 0, &bufIntDigits, &sign, &endp, buf.get(), bufsize - 1);

  int buflen = endp - buf.get();
  int intDigits;
  intDigits = bufIntDigits > minIntegerSize ? bufIntDigits : minIntegerSize;

  if (groupSize < 0) groupSize = intDigits + 10;  // to simplify grouping

  // XXX We shouldn't use SetLength.
  res.SetLength(res.Length() + intDigits +     // integer digits
                1 +                            // decimal separator
                maxFractionSize +              // fractions
                (intDigits - 1) / groupSize);  // group separators

  int32_t i = bufIntDigits + maxFractionSize - 1;
  bool carry = (0 <= i + 1) && (i + 1 < buflen) && (buf[i + 1] >= '5');
  bool hasFraction = false;

  // The number of characters in res that we haven't filled in.
  mozilla::CheckedUint32 resRemain = mozilla::CheckedUint32(res.Length());

#define CHECKED_SET_CHAR(c)                                           \
  --resRemain;                                                        \
  if (!resRemain.isValid() || !res.SetCharAt(c, resRemain.value())) { \
    ReportInvalidArg(aContext);                                       \
    return NS_ERROR_XPATH_INVALID_ARG;                                \
  }

#define CHECKED_TRUNCATE()             \
  --resRemain;                         \
  if (!resRemain.isValid()) {          \
    ReportInvalidArg(aContext);        \
    return NS_ERROR_XPATH_INVALID_ARG; \
  }                                    \
  res.Truncate(resRemain.value());

  // Fractions
  for (; i >= bufIntDigits; --i) {
    int digit;
    if (i >= buflen || i < 0) {
      digit = 0;
    } else {
      digit = buf[i] - '0';
    }

    if (carry) {
      digit = (digit + 1) % 10;
      carry = digit == 0;
    }

    if (hasFraction || digit != 0 || i < bufIntDigits + minFractionSize) {
      hasFraction = true;
      CHECKED_SET_CHAR((char16_t)(digit + format->mZeroDigit));
    } else {
      CHECKED_TRUNCATE();
    }
  }

  // Decimal separator
  if (hasFraction) {
    CHECKED_SET_CHAR(format->mDecimalSeparator);
  } else {
    CHECKED_TRUNCATE();
  }

  // Integer digits
  for (i = 0; i < intDigits; ++i) {
    int digit;
    if (bufIntDigits - i - 1 >= buflen || bufIntDigits - i - 1 < 0) {
      digit = 0;
    } else {
      digit = buf[bufIntDigits - i - 1] - '0';
    }

    if (carry) {
      digit = (digit + 1) % 10;
      carry = digit == 0;
    }

    if (i != 0 && i % groupSize == 0) {
      CHECKED_SET_CHAR(format->mGroupingSeparator);
    }

    CHECKED_SET_CHAR((char16_t)(digit + format->mZeroDigit));
  }

#undef CHECKED_SET_CHAR
#undef CHECKED_TRUNCATE

  if (carry) {
    if (i % groupSize == 0) {
      res.Insert(format->mGroupingSeparator, resRemain.value());
    }
    res.Insert((char16_t)(1 + format->mZeroDigit), resRemain.value());
  }

  if (!hasFraction && !intDigits && !carry) {
    // If we havn't added any characters we add a '0'
    // This can only happen for formats like '##.##'
    res.Append(format->mZeroDigit);
  }

  // Build suffix
  res.Append(suffix);

  return aContext->recycler()->getStringResult(res, aResult);
}  //-- evaluate

Expr::ResultType txFormatNumberFunctionCall::getReturnType() {
  return STRING_RESULT;
}

bool txFormatNumberFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
  return argsSensitiveTo(aContext);
}

#ifdef TX_TO_STRING
void txFormatNumberFunctionCall::appendName(nsAString& aDest) {
  aDest.Append(nsGkAtoms::formatNumber->GetUTF16String());
}
#endif

/*
 * txDecimalFormat
 * A representation of the XSLT element <xsl:decimal-format>
 */

txDecimalFormat::txDecimalFormat()
    : mInfinity(u"Infinity"_ns), mNaN(u"NaN"_ns) {
  mDecimalSeparator = '.';
  mGroupingSeparator = ',';
  mMinusSign = '-';
  mPercent = '%';
  mPerMille = 0x2030;
  mZeroDigit = '0';
  mDigit = '#';
  mPatternSeparator = ';';
}

bool txDecimalFormat::isEqual(txDecimalFormat* other) {
  return mDecimalSeparator == other->mDecimalSeparator &&
         mGroupingSeparator == other->mGroupingSeparator &&
         mInfinity.Equals(other->mInfinity) &&
         mMinusSign == other->mMinusSign && mNaN.Equals(other->mNaN) &&
         mPercent == other->mPercent && mPerMille == other->mPerMille &&
         mZeroDigit == other->mZeroDigit && mDigit == other->mDigit &&
         mPatternSeparator == other->mPatternSeparator;
}
