//
// POSIX spec:
//   http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html
//
#include "absl/strings/internal/str_format/arg.h"

#include <cassert>
#include <cerrno>
#include <cstdlib>
#include <string>
#include <type_traits>

#include "absl/base/port.h"
#include "absl/strings/internal/str_format/float_conversion.h"

namespace absl {
namespace str_format_internal {
namespace {

const char kDigit[2][32] = { "0123456789abcdef", "0123456789ABCDEF" };

// Reduce *capacity by s.size(), clipped to a 0 minimum.
void ReducePadding(string_view s, size_t *capacity) {
  *capacity = Excess(s.size(), *capacity);
}

// Reduce *capacity by n, clipped to a 0 minimum.
void ReducePadding(size_t n, size_t *capacity) {
  *capacity = Excess(n, *capacity);
}

template <typename T>
struct MakeUnsigned : std::make_unsigned<T> {};
template <>
struct MakeUnsigned<absl::int128> {
  using type = absl::uint128;
};
template <>
struct MakeUnsigned<absl::uint128> {
  using type = absl::uint128;
};

template <typename T>
struct IsSigned : std::is_signed<T> {};
template <>
struct IsSigned<absl::int128> : std::true_type {};
template <>
struct IsSigned<absl::uint128> : std::false_type {};

class ConvertedIntInfo {
 public:
  template <typename T>
  ConvertedIntInfo(T v, ConversionChar conv) {
    using Unsigned = typename MakeUnsigned<T>::type;
    auto u = static_cast<Unsigned>(v);
    if (IsNeg(v)) {
      is_neg_ = true;
      u = Unsigned{} - u;
    } else {
      is_neg_ = false;
    }
    UnsignedToStringRight(u, conv);
  }

  string_view digits() const {
    return {end() - size_, static_cast<size_t>(size_)};
  }
  bool is_neg() const { return is_neg_; }

 private:
  template <typename T, bool IsSigned>
  struct IsNegImpl {
    static bool Eval(T v) { return v < 0; }
  };
  template <typename T>
  struct IsNegImpl<T, false> {
    static bool Eval(T) {
      return false;
    }
  };

  template <typename T>
  bool IsNeg(T v) {
    return IsNegImpl<T, IsSigned<T>::value>::Eval(v);
  }

  template <typename T>
  void UnsignedToStringRight(T u, ConversionChar conv) {
    char *p = end();
    switch (conv.radix()) {
      default:
      case 10:
        for (; u; u /= 10)
          *--p = static_cast<char>('0' + static_cast<size_t>(u % 10));
        break;
      case 8:
        for (; u; u /= 8)
          *--p = static_cast<char>('0' + static_cast<size_t>(u % 8));
        break;
      case 16: {
        const char *digits = kDigit[conv.upper() ? 1 : 0];
        for (; u; u /= 16) *--p = digits[static_cast<size_t>(u % 16)];
        break;
      }
    }
    size_ = static_cast<int>(end() - p);
  }

  const char *end() const { return storage_ + sizeof(storage_); }
  char *end() { return storage_ + sizeof(storage_); }

  bool is_neg_;
  int size_;
  // Max size: 128 bit value as octal -> 43 digits
  char storage_[128 / 3 + 1];
};

// Note: 'o' conversions do not have a base indicator, it's just that
// the '#' flag is specified to modify the precision for 'o' conversions.
string_view BaseIndicator(const ConvertedIntInfo &info,
                          const ConversionSpec conv) {
  bool alt = conv.flags().alt;
  int radix = conv.conv().radix();
  if (conv.conv().id() == ConversionChar::p)
    alt = true;  // always show 0x for %p.
  // From the POSIX description of '#' flag:
  //   "For x or X conversion specifiers, a non-zero result shall have
  //   0x (or 0X) prefixed to it."
  if (alt && radix == 16 && !info.digits().empty()) {
    if (conv.conv().upper()) return "0X";
    return "0x";
  }
  return {};
}

string_view SignColumn(bool neg, const ConversionSpec conv) {
  if (conv.conv().is_signed()) {
    if (neg) return "-";
    if (conv.flags().show_pos) return "+";
    if (conv.flags().sign_col) return " ";
  }
  return {};
}

bool ConvertCharImpl(unsigned char v, const ConversionSpec conv,
                     FormatSinkImpl *sink) {
  size_t fill = 0;
  if (conv.width() >= 0) fill = conv.width();
  ReducePadding(1, &fill);
  if (!conv.flags().left) sink->Append(fill, ' ');
  sink->Append(1, v);
  if (conv.flags().left) sink->Append(fill, ' ');
  return true;
}

bool ConvertIntImplInner(const ConvertedIntInfo &info,
                         const ConversionSpec conv, FormatSinkImpl *sink) {
  // Print as a sequence of Substrings:
  //   [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces]
  size_t fill = 0;
  if (conv.width() >= 0) fill = conv.width();

  string_view formatted = info.digits();
  ReducePadding(formatted, &fill);

  string_view sign = SignColumn(info.is_neg(), conv);
  ReducePadding(sign, &fill);

  string_view base_indicator = BaseIndicator(info, conv);
  ReducePadding(base_indicator, &fill);

  int precision = conv.precision();
  bool precision_specified = precision >= 0;
  if (!precision_specified)
    precision = 1;

  if (conv.flags().alt && conv.conv().id() == ConversionChar::o) {
    // From POSIX description of the '#' (alt) flag:
    //   "For o conversion, it increases the precision (if necessary) to
    //   force the first digit of the result to be zero."
    if (formatted.empty() || *formatted.begin() != '0') {
      int needed = static_cast<int>(formatted.size()) + 1;
      precision = std::max(precision, needed);
    }
  }

  size_t num_zeroes = Excess(formatted.size(), precision);
  ReducePadding(num_zeroes, &fill);

  size_t num_left_spaces = !conv.flags().left ? fill : 0;
  size_t num_right_spaces = conv.flags().left ? fill : 0;

  // From POSIX description of the '0' (zero) flag:
  //   "For d, i, o, u, x, and X conversion specifiers, if a precision
  //   is specified, the '0' flag is ignored."
  if (!precision_specified && conv.flags().zero) {
    num_zeroes += num_left_spaces;
    num_left_spaces = 0;
  }

  sink->Append(num_left_spaces, ' ');
  sink->Append(sign);
  sink->Append(base_indicator);
  sink->Append(num_zeroes, '0');
  sink->Append(formatted);
  sink->Append(num_right_spaces, ' ');
  return true;
}

template <typename T>
bool ConvertIntImplInner(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
  ConvertedIntInfo info(v, conv.conv());
  if (conv.flags().basic && conv.conv().id() != ConversionChar::p) {
    if (info.is_neg()) sink->Append(1, '-');
    if (info.digits().empty()) {
      sink->Append(1, '0');
    } else {
      sink->Append(info.digits());
    }
    return true;
  }
  return ConvertIntImplInner(info, conv, sink);
}

template <typename T>
bool ConvertIntArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
  if (conv.conv().is_float()) {
    return FormatConvertImpl(static_cast<double>(v), conv, sink).value;
  }
  if (conv.conv().id() == ConversionChar::c)
    return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink);
  if (!conv.conv().is_integral())
    return false;
  if (!conv.conv().is_signed() && IsSigned<T>::value) {
    using U = typename MakeUnsigned<T>::type;
    return FormatConvertImpl(static_cast<U>(v), conv, sink).value;
  }
  return ConvertIntImplInner(v, conv, sink);
}

template <typename T>
bool ConvertFloatArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
  return conv.conv().is_float() && ConvertFloatImpl(v, conv, sink);
}

inline bool ConvertStringArg(string_view v, const ConversionSpec conv,
                             FormatSinkImpl *sink) {
  if (conv.conv().id() != ConversionChar::s)
    return false;
  if (conv.flags().basic) {
    sink->Append(v);
    return true;
  }
  return sink->PutPaddedString(v, conv.width(), conv.precision(),
                               conv.flags().left);
}

}  // namespace

// ==================== Strings ====================
ConvertResult<Conv::s> FormatConvertImpl(const std::string &v,
                                         const ConversionSpec conv,
                                         FormatSinkImpl *sink) {
  return {ConvertStringArg(v, conv, sink)};
}

ConvertResult<Conv::s> FormatConvertImpl(string_view v,
                                         const ConversionSpec conv,
                                         FormatSinkImpl *sink) {
  return {ConvertStringArg(v, conv, sink)};
}

ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char *v,
                                                   const ConversionSpec conv,
                                                   FormatSinkImpl *sink) {
  if (conv.conv().id() == ConversionChar::p)
    return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
  size_t len;
  if (v == nullptr) {
    len = 0;
  } else if (conv.precision() < 0) {
    len = std::strlen(v);
  } else {
    // If precision is set, we look for the null terminator on the valid range.
    len = std::find(v, v + conv.precision(), '\0') - v;
  }
  return {ConvertStringArg(string_view(v, len), conv, sink)};
}

// ==================== Raw pointers ====================
ConvertResult<Conv::p> FormatConvertImpl(VoidPtr v, const ConversionSpec conv,
                                         FormatSinkImpl *sink) {
  if (conv.conv().id() != ConversionChar::p)
    return {false};
  if (!v.value) {
    sink->Append("(nil)");
    return {true};
  }
  return {ConvertIntImplInner(v.value, conv, sink)};
}

// ==================== Floats ====================
FloatingConvertResult FormatConvertImpl(float v, const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertFloatArg(v, conv, sink)};
}
FloatingConvertResult FormatConvertImpl(double v, const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertFloatArg(v, conv, sink)};
}
FloatingConvertResult FormatConvertImpl(long double v,
                                        const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertFloatArg(v, conv, sink)};
}

// ==================== Chars ====================
IntegralConvertResult FormatConvertImpl(char v, const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(signed char v,
                                        const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(unsigned char v,
                                        const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}

// ==================== Ints ====================
IntegralConvertResult FormatConvertImpl(short v,  // NOLINT
                                        const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(unsigned short v,  // NOLINT
                                        const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(int v, const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(unsigned v, const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(long v,  // NOLINT
                                        const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(unsigned long v,  // NOLINT
                                        const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(long long v,  // NOLINT
                                        const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(unsigned long long v,  // NOLINT
                                        const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(absl::int128 v,
                                        const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(absl::uint128 v,
                                        const ConversionSpec conv,
                                        FormatSinkImpl *sink) {
  return {ConvertIntArg(v, conv, sink)};
}

ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_();



}  // namespace str_format_internal

}  // namespace absl
