#include <xrpl/basics/strHex.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/UintTypes.h>

#include <algorithm>
#include <cstddef>
#include <string>
#include <string_view>

namespace xrpl {

// For details on the protocol-level serialization please visit
// https://xrpl.org/serialization.html#currency-codes

namespace detail {

// Characters we are willing to allow in the ASCII representation of a
// three-letter currency code.
constexpr std::string_view isoCharSet =
    "abcdefghijklmnopqrstuvwxyz"
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "0123456789"
    "<>(){}[]|?!@#$%^&*";

// The location (in bytes) of the 3 digit currency inside a 160-bit value
constexpr std::size_t isoCodeOffset = 12;

// The length of an ISO-4217 like code
constexpr std::size_t isoCodeLength = 3;

}  // namespace detail

std::string
to_string(Currency const& currency)
{
    if (currency == beast::zero)
        return systemCurrencyCode();

    if (currency == noCurrency())
        return "1";

    static constexpr Currency sIsoBits("FFFFFFFFFFFFFFFFFFFFFFFF000000FFFFFFFFFF");

    if ((currency & sIsoBits).isZero())
    {
        std::string const iso(
            currency.data() + detail::isoCodeOffset,
            currency.data() + detail::isoCodeOffset + detail::isoCodeLength);

        // Specifying the system currency code using ISO-style representation
        // is not allowed.
        if ((iso != systemCurrencyCode()) &&
            (iso.find_first_not_of(detail::isoCharSet) == std::string::npos))
        {
            return iso;
        }
    }

    return strHex(currency);
}

bool
to_currency(Currency& currency, std::string const& code)
{
    if (code.empty() || (code.compare(systemCurrencyCode()) == 0))
    {
        currency = beast::zero;
        return true;
    }

    // Handle ISO-4217-like 3-digit character codes.
    if (code.size() == detail::isoCodeLength)
    {
        if (code.find_first_not_of(detail::isoCharSet) != std::string::npos)
            return false;

        currency = beast::zero;

        std::copy(code.begin(), code.end(), currency.begin() + detail::isoCodeOffset);

        return true;
    }

    return currency.parseHex(code);
}

Currency
to_currency(std::string const& code)
{
    Currency currency;
    if (!to_currency(currency, code))
        currency = noCurrency();
    return currency;
}

Currency const&
xrpCurrency()
{
    static Currency const currency(beast::zero);
    return currency;
}

Currency const&
noCurrency()
{
    static Currency const currency(1);
    return currency;
}

Currency const&
badCurrency()
{
    static Currency const currency(0x5852500000000000);
    return currency;
}

}  // namespace xrpl
