//===-- Fixed Point Converter for printf ------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_FIXED_CONVERTER_H
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_FIXED_CONVERTER_H

#include "include/llvm-libc-macros/stdfix-macros.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/ctype_utils.h"
#include "src/__support/fixed_point/fx_bits.h"
#include "src/__support/fixed_point/fx_rep.h"
#include "src/__support/integer_to_string.h"
#include "src/__support/libc_assert.h"
#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/converter_utils.h"
#include "src/stdio/printf_core/core_structs.h"
#include "src/stdio/printf_core/writer.h"

#include <inttypes.h>
#include <stddef.h>

namespace LIBC_NAMESPACE_DECL {
namespace printf_core {

// This is just for assertions. It will be compiled out for release builds.
LIBC_INLINE constexpr uint32_t const_ten_exp(uint32_t exponent) {
  uint32_t result = 1;
  LIBC_ASSERT(exponent < 11);
  for (uint32_t i = 0; i < exponent; ++i)
    result *= 10;

  return result;
}

#define READ_FX_BITS(TYPE)                                                     \
  do {                                                                         \
    auto fixed_bits = fixed_point::FXBits<TYPE>(                               \
        fixed_point::FXRep<TYPE>::StorageType(to_conv.conv_val_raw));          \
    integral = fixed_bits.get_integral();                                      \
    fractional = fixed_bits.get_fraction();                                    \
    exponent = fixed_bits.get_exponent();                                      \
    is_negative = fixed_bits.get_sign();                                       \
  } while (false)

#define APPLY_FX_LENGTH_MODIFIER(LENGTH_MODIFIER)                              \
  do {                                                                         \
    if (to_conv.conv_name == 'r') {                                            \
      READ_FX_BITS(LENGTH_MODIFIER fract);                                     \
    } else if (to_conv.conv_name == 'R') {                                     \
      READ_FX_BITS(unsigned LENGTH_MODIFIER fract);                            \
    } else if (to_conv.conv_name == 'k') {                                     \
      READ_FX_BITS(LENGTH_MODIFIER accum);                                     \
    } else if (to_conv.conv_name == 'K') {                                     \
      READ_FX_BITS(unsigned LENGTH_MODIFIER accum);                            \
    } else {                                                                   \
      LIBC_ASSERT(false && "Invalid conversion name passed to convert_fixed"); \
      return FIXED_POINT_CONVERSION_ERROR;                                     \
    }                                                                          \
  } while (false)

template <WriteMode write_mode>
LIBC_INLINE int convert_fixed(Writer<write_mode> *writer,
                              const FormatSection &to_conv) {
  // Long accum should be the largest type, so we can store all the smaller
  // numbers in things sized for it.
  using LARep = fixed_point::FXRep<unsigned long accum>;
  using StorageType = LARep::StorageType;

  FormatFlags flags = to_conv.flags;

  bool is_negative;
  int exponent;
  StorageType integral;
  StorageType fractional;

  // r = fract
  // k = accum
  // lowercase = signed
  // uppercase = unsigned
  // h = short
  // l = long
  // any other length modifier has no effect

  if (to_conv.length_modifier == LengthModifier::h) {
    APPLY_FX_LENGTH_MODIFIER(short);
  } else if (to_conv.length_modifier == LengthModifier::l) {
    APPLY_FX_LENGTH_MODIFIER(long);
  } else {
    APPLY_FX_LENGTH_MODIFIER();
  }

  LIBC_ASSERT(static_cast<size_t>(exponent) <=
                  (sizeof(StorageType) - sizeof(uint32_t)) * CHAR_BIT &&
              "StorageType must be large enough to hold the fractional "
              "component multiplied by a 32 bit number.");

  // If to_conv doesn't specify a precision, the precision defaults to 6.
  const size_t precision = to_conv.precision < 0 ? 6 : to_conv.precision;
  bool has_decimal_point =
      (precision > 0) || ((flags & FormatFlags::ALTERNATE_FORM) != 0);

  // The number of non-zero digits below the decimal point for a negative power
  // of 2 in base 10 is equal to the magnitude of the power of 2.

  // A quick proof:
  // Let p be any positive integer.
  // Let e = 2^(-p)
  // Let t be a positive integer such that e * 10^t is an integer.
  // By definition: The smallest allowed value of t must be equal to the number
  // of non-zero digits below the decimal point in e.
  // If we evaluate e * 10^t we get the following:
  // e * 10^t = 2^(-p) * 10*t = 2^(-p) * 2^t * 5^t = 5^t * 2^(t-p)
  // For 5^t * 2^(t-p) to be an integer, both exponents must be non-negative,
  // since 5 and 2 are coprime.
  // The smallest value of t such that t-p is non-negative is p.
  // Therefor, the number of non-zero digits below the decimal point for a given
  // negative power of 2 "p" is equal to the value of p.

  constexpr size_t MAX_FRACTION_DIGITS = LARep::FRACTION_LEN;

  char fraction_digits[MAX_FRACTION_DIGITS];

  size_t valid_fraction_digits = 0;

  // TODO: Factor this part out
  while (fractional > 0) {
    uint32_t cur_digits = 0;
    // 10^9 is used since it's the largest power of 10 that fits in a uint32_t
    constexpr uint32_t TEN_EXP_NINE = 1000000000;
    constexpr size_t DIGITS_PER_BLOCK = 9;

    // Multiply by 10^9, then grab the digits above the decimal point, then
    // clear those digits in fractional.
    fractional = fractional * TEN_EXP_NINE;
    cur_digits = static_cast<uint32_t>(fractional >> exponent);
    fractional = fractional % (StorageType(1) << exponent);

    // we add TEN_EXP_NINE to force leading zeroes to show up, then we skip the
    // first digit in the loop.
    const IntegerToString<uint32_t> cur_fractional_digits(cur_digits +
                                                          TEN_EXP_NINE);
    for (size_t i = 0;
         i < DIGITS_PER_BLOCK && valid_fraction_digits < MAX_FRACTION_DIGITS;
         ++i, ++valid_fraction_digits)
      fraction_digits[valid_fraction_digits] =
          cur_fractional_digits.view()[i + 1];

    if (valid_fraction_digits >= MAX_FRACTION_DIGITS) {
      LIBC_ASSERT(fractional == 0 && "If the fraction digit buffer is full, "
                                     "there should be no remaining digits.");
      /*
        A visual explanation of what this assert is checking:

         32 digits (max for 32 bit fract)
         +------------------------------++--+--- must be zero
         |                              ||  |
         123456789012345678901234567890120000
         |       ||       ||       ||       |
         +-------++-------++-------++-------+
         9 digit blocks
      */
      LIBC_ASSERT(cur_digits % const_ten_exp(
                                   DIGITS_PER_BLOCK -
                                   (MAX_FRACTION_DIGITS % DIGITS_PER_BLOCK)) ==
                      0 &&
                  "Digits after the MAX_FRACTION_DIGITS should all be zero.");
      valid_fraction_digits = MAX_FRACTION_DIGITS;
    }
  }

  if (precision < valid_fraction_digits) {
    // Handle rounding. Just do round to nearest, tie to even since it's
    // unspecified.
    RoundDirection round;
    char first_digit_after = fraction_digits[precision];
    if (internal::b36_char_to_int(first_digit_after) > 5) {
      round = RoundDirection::Up;
    } else if (internal::b36_char_to_int(first_digit_after) < 5) {
      round = RoundDirection::Down;
    } else {
      // first_digit_after == '5'
      // need to check the remaining digits, but default to even.
      round = RoundDirection::Even;
      for (size_t cur_digit_index = precision + 1;
           cur_digit_index + 1 < valid_fraction_digits; ++cur_digit_index) {
        if (fraction_digits[cur_digit_index] != '0') {
          round = RoundDirection::Up;
          break;
        }
      }
    }

    // If we need to actually perform rounding, do so.
    if (round == RoundDirection::Up || round == RoundDirection::Even) {
      bool keep_rounding = true;
      int digit_to_round = static_cast<int>(precision) - 1;
      for (; digit_to_round >= 0 && keep_rounding; --digit_to_round) {
        keep_rounding = false;
        char cur_digit = fraction_digits[digit_to_round];
        // if the digit should not be rounded up
        if (round == RoundDirection::Even &&
            (internal::b36_char_to_int(cur_digit) % 2) == 0) {
          // break out of the loop
          break;
        }
        fraction_digits[digit_to_round] += 1;

        // if the digit was a 9, instead replace with a 0.
        if (cur_digit == '9') {
          fraction_digits[digit_to_round] = '0';
          keep_rounding = true;
        }
      }

      // if every digit below the decimal point was rounded up but we need to
      // keep rounding
      if (keep_rounding &&
          (round == RoundDirection::Up ||
           (round == RoundDirection::Even && ((integral % 2) == 1)))) {
        // add one to the integral portion to round it up.
        ++integral;
      }
    }

    valid_fraction_digits = precision;
  }

  const IntegerToString<StorageType> integral_str(integral);

  // these are signed to prevent underflow due to negative values. The
  // eventual values will always be non-negative.
  size_t trailing_zeroes = 0;
  int padding;

  // If the precision is greater than the actual result, pad with 0s
  if (precision > valid_fraction_digits)
    trailing_zeroes = precision - (valid_fraction_digits);

  constexpr cpp::string_view DECIMAL_POINT(".");

  char sign_char = 0;

  // Check if the conv name is uppercase
  if (internal::isupper(to_conv.conv_name)) {
    // These flags are only for signed conversions, so this removes them if the
    // conversion is unsigned.
    flags = FormatFlags(flags &
                        ~(FormatFlags::FORCE_SIGN | FormatFlags::SPACE_PREFIX));
  }

  if (is_negative)
    sign_char = '-';
  else if ((flags & FormatFlags::FORCE_SIGN) == FormatFlags::FORCE_SIGN)
    sign_char = '+'; // FORCE_SIGN has precedence over SPACE_PREFIX
  else if ((flags & FormatFlags::SPACE_PREFIX) == FormatFlags::SPACE_PREFIX)
    sign_char = ' ';

  padding = static_cast<int>(to_conv.min_width - (sign_char > 0 ? 1 : 0) -
                             integral_str.size() -
                             static_cast<int>(has_decimal_point) -
                             valid_fraction_digits - trailing_zeroes);
  if (padding < 0)
    padding = 0;

  if ((flags & FormatFlags::LEFT_JUSTIFIED) == FormatFlags::LEFT_JUSTIFIED) {
    // The pattern is (sign), integral, (.), (fraction), (zeroes), (spaces)
    if (sign_char > 0)
      RET_IF_RESULT_NEGATIVE(writer->write(sign_char));
    RET_IF_RESULT_NEGATIVE(writer->write(integral_str.view()));
    if (has_decimal_point)
      RET_IF_RESULT_NEGATIVE(writer->write(DECIMAL_POINT));
    if (valid_fraction_digits > 0)
      RET_IF_RESULT_NEGATIVE(
          writer->write({fraction_digits, valid_fraction_digits}));
    if (trailing_zeroes > 0)
      RET_IF_RESULT_NEGATIVE(writer->write('0', trailing_zeroes));
    if (padding > 0)
      RET_IF_RESULT_NEGATIVE(writer->write(' ', padding));
  } else {
    // The pattern is (spaces), (sign), (zeroes), integral, (.), (fraction),
    // (zeroes)
    if ((padding > 0) &&
        ((flags & FormatFlags::LEADING_ZEROES) != FormatFlags::LEADING_ZEROES))
      RET_IF_RESULT_NEGATIVE(writer->write(' ', padding));
    if (sign_char > 0)
      RET_IF_RESULT_NEGATIVE(writer->write(sign_char));
    if ((padding > 0) &&
        ((flags & FormatFlags::LEADING_ZEROES) == FormatFlags::LEADING_ZEROES))
      RET_IF_RESULT_NEGATIVE(writer->write('0', padding));
    RET_IF_RESULT_NEGATIVE(writer->write(integral_str.view()));
    if (has_decimal_point)
      RET_IF_RESULT_NEGATIVE(writer->write(DECIMAL_POINT));
    if (valid_fraction_digits > 0)
      RET_IF_RESULT_NEGATIVE(
          writer->write({fraction_digits, valid_fraction_digits}));
    if (trailing_zeroes > 0)
      RET_IF_RESULT_NEGATIVE(writer->write('0', trailing_zeroes));
  }
  return WRITE_OK;
}

} // namespace printf_core
} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_FIXED_CONVERTER_H
