/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#pragma once

/// \file iceberg/util/decimal.h
/// \brief 128-bit fixed-point decimal numbers.
/// Adapted from Apache Arrow with only Decimal128 support.
/// https://github.com/apache/arrow/blob/main/cpp/src/arrow/util/decimal.h

#include <array>
#include <compare>
#include <cstdint>
#include <iosfwd>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>

#include "iceberg/iceberg_export.h"
#include "iceberg/result.h"
#include "iceberg/util/formattable.h"
#include "iceberg/util/int128.h"

namespace iceberg {

/// \brief Represents 128-bit fixed-point decimal numbers.
/// The max decimal precision that can be safely represented is
/// 38 significant digits.
class ICEBERG_EXPORT Decimal : public util::Formattable {
 public:
  static constexpr int32_t kBitWidth = 128;
  static constexpr int32_t kByteWidth = kBitWidth / 8;
  static constexpr int32_t kMaxPrecision = 38;
  static constexpr int32_t kMaxScale = 38;

  /// \brief Default constructor initializes to zero.
  constexpr Decimal() noexcept = default;

  /// \brief Create a Decimal from a 128-bit integer.
  constexpr Decimal(int128_t value) noexcept  // NOLINT implicit conversion
      : data_(value) {}

  /// \brief Create a Decimal from any integer not wider than 64 bits.
  template <typename T>
    requires(std::is_integral_v<T> && (sizeof(T) <= sizeof(uint64_t)))
  constexpr Decimal(T value) noexcept  // NOLINT implicit conversion
      : data_(static_cast<int128_t>(value)) {}

  /// \brief Parse a Decimal from a string representation.
  /// \throw This constructor throws an exception if parsing fails. Use
  /// Decimal::FromString() if you want to handle errors more gracefully.
  explicit Decimal(std::string_view str);

  /// \brief Create a Decimal from two 64-bit integers.
  constexpr Decimal(int64_t high, uint64_t low) noexcept {
    data_ = (static_cast<int128_t>(high) << 64) | low;
  }

  /// \brief Negate the current Decimal value (in place)
  Decimal& Negate();

  /// \brief Absolute value of the current Decimal value (in place)
  Decimal& Abs();

  /// \brief Absolute value of the current Decimal value
  static Decimal Abs(const Decimal& value);

  /// \brief Add a number to this one. The result is truncated to 128 bits.
  Decimal& operator+=(const Decimal& other);

  /// \brief Subtract a number from this one. The result is truncated to 128 bits.
  Decimal& operator-=(const Decimal& other);

  /// \brief Multiply this number by another. The result is truncated to 128 bits.
  Decimal& operator*=(const Decimal& other);

  /// \brief Divide this number by another.
  ///
  /// The operation does not modify the current Decimal value.
  /// The answer rounds towards zero. Signs work like:
  ///   21 /  5 ->  4,  1
  ///  -21 /  5 -> -4, -1
  ///   21 / -5 -> -4,  1
  ///  -21 / -5 ->  4, -1
  /// \param[in] divisor the number to divide by
  /// \return the pair of the quotient and the remainder
  Result<std::pair<Decimal, Decimal>> Divide(const Decimal& divisor) const;

  /// \brief In place division.
  Decimal& operator/=(const Decimal& other);

  /// \brief Bitwise OR operation.
  Decimal& operator|=(const Decimal& other);

  /// \brief Bitwise AND operation.
  Decimal& operator&=(const Decimal& other);

  /// \brief Shift left by the given number of bits (in place).
  Decimal& operator<<=(uint32_t shift);

  /// \brief Shift left by the given number of bits.
  Decimal operator<<(uint32_t shift) const {
    Decimal result(*this);
    result <<= shift;
    return result;
  }

  /// \brief Shift right by the given number of bits (in place).
  Decimal& operator>>=(uint32_t shift);

  /// \brief Shift right by the given number of bits.
  Decimal operator>>(uint32_t shift) const {
    Decimal result(*this);
    result >>= shift;
    return result;
  }

  /// \brief Get the underlying 128-bit integer representation of the number.
  constexpr int128_t value() const { return data_; }

  /// \brief Get the high bits of the two's complement representation of the number.
  constexpr int64_t high() const { return static_cast<int64_t>(data_ >> 64); }

  /// \brief Get the low bits of the two's complement representation of the number.
  constexpr uint64_t low() const { return static_cast<uint64_t>(data_); }

  /// \brief Convert the Decimal value to a base 10 decimal string with the given scale.
  /// \param scale The scale to use for the string representation.
  /// \return The string representation of the Decimal value.
  Result<std::string> ToString(int32_t scale) const;

  /// \brief Convert the Decimal value to an integer string.
  std::string ToIntegerString() const;

  /// \brief Returns an integer string representation of the decimal value.
  std::string ToString() const override { return ToIntegerString(); }

  /// \brief Convert the decimal string to a Decimal value, optionally including precision
  /// and scale if they are provided not null.
  /// \param str The string representation of the Decimal value.
  /// \param[out] precision Optional pointer to store the precision of the parsed value.
  /// \param[out] scale Optional pointer to store the scale of the parsed value.
  /// \return The Decimal value.
  static Result<Decimal> FromString(std::string_view str, int32_t* precision = nullptr,
                                    int32_t* scale = nullptr);

  /// \brief Convert from a big-endian byte representation. The length must be
  ///        between 1 and 16.
  /// \return error status if the length is an invalid value
  static Result<Decimal> FromBigEndian(const uint8_t* data, int32_t length);

  /// \brief Convert Decimal's unscaled value to two’s-complement big-endian binary, using
  ///        the minimum number of bytes for the value.
  /// \return A vector containing the big-endian bytes.
  std::vector<uint8_t> ToBigEndian() const;

  /// \brief Convert Decimal from one scale to another.
  Result<Decimal> Rescale(int32_t orig_scale, int32_t new_scale) const;

  /// \brief Whether this number fits in the given precision
  ///
  /// Returns true if the number of significant digits is less or equal to `precision`.
  bool FitsInPrecision(int32_t precision) const;

  /// \brief Spaceship operator for three-way comparison.
  std::strong_ordering operator<=>(const Decimal& other) const {
    if (high() != other.high()) {
      return high() <=> other.high();
    }
    return low() <=> other.low();
  }

  /// \brief Compare two Decimals with different scales.
  static std::partial_ordering Compare(const Decimal& lhs, const Decimal& rhs,
                                       int32_t lhs_scale, int32_t rhs_scale);

  const uint8_t* native_endian_bytes() const {
    return reinterpret_cast<const uint8_t*>(&data_);
  }

  /// \brief Returns the raw bytes of the value in native-endian byte order.
  std::array<uint8_t, kByteWidth> ToBytes() const;

  /// \brief Returns 1 if positive or zero, -1 if strictly negative.
  int64_t Sign() const { return 1 | (high() >> 63); }

  /// \brief Check if the Decimal value is negative.
  bool IsNegative() const { return (high() >> 63) < 0; }

  explicit operator bool() const { return data_ != 0; }

  friend bool operator==(const Decimal& lhs, const Decimal& rhs) {
    return lhs.data_ == rhs.data_;
  }

  friend bool operator!=(const Decimal& lhs, const Decimal& rhs) {
    return lhs.data_ != rhs.data_;
  }

  ICEBERG_EXPORT friend Decimal operator-(const Decimal& operand);
  ICEBERG_EXPORT friend Decimal operator~(const Decimal& operand);

  ICEBERG_EXPORT friend Decimal operator+(const Decimal& lhs, const Decimal& rhs);
  ICEBERG_EXPORT friend Decimal operator-(const Decimal& lhs, const Decimal& rhs);
  ICEBERG_EXPORT friend Decimal operator*(const Decimal& lhs, const Decimal& rhs);
  ICEBERG_EXPORT friend Decimal operator/(const Decimal& lhs, const Decimal& rhs);
  ICEBERG_EXPORT friend Decimal operator%(const Decimal& lhs, const Decimal& rhs);

 private:
  int128_t data_{0};
};

ICEBERG_EXPORT std::ostream& operator<<(std::ostream& os, const Decimal& decimal);

}  // namespace iceberg
