//------------------------------------------------------------------------------
/*
    This file is part of rippled: https://github.com/ripple/rippled
    Copyright (c) 2012, 2013 Ripple Labs Inc.

    Permission to use, copy, modify, and/or distribute this software for any
    purpose  with  or without fee is hereby granted, provided that the above
    copyright notice and this permission notice appear in all copies.

    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================

#include <ripple/basics/StringUtilities.h>
#include <ripple/basics/contract.h>
#include <ripple/basics/safe_cast.h>
#include <ripple/beast/core/LexicalCast.h>
#include <ripple/protocol/LedgerFormats.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STAmount.h>
#include <ripple/protocol/STArray.h>
#include <ripple/protocol/STBitString.h>
#include <ripple/protocol/STBlob.h>
#include <ripple/protocol/STInteger.h>
#include <ripple/protocol/STIssue.h>
#include <ripple/protocol/STParsedJSON.h>
#include <ripple/protocol/STPathSet.h>
#include <ripple/protocol/STVector256.h>
#include <ripple/protocol/TER.h>
#include <ripple/protocol/TxFormats.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/protocol/impl/STVar.h>
#include <cassert>
#include <charconv>
#include <memory>

namespace ripple {

namespace STParsedJSONDetail {

std::map<int, parsePluginValuePtr> pluginLeafParserMap{};

// This function is used by parseObject to parse any JSON type that doesn't
// recurse.  Everything represented here is a leaf-type.
static std::optional<detail::STVar>
parseLeaf(
    std::string const& json_name,
    std::string const& fieldName,
    SField const* name,
    Json::Value const& value,
    Json::Value& error)
{
    std::optional<detail::STVar> ret;

    auto const& field = SField::getField(fieldName);

    if (field == sfInvalid)
    {
        error = unknown_field(json_name, fieldName);
        return ret;
    }

    switch (field.fieldType)
    {
        case STI_UINT8:
            try
            {
                constexpr auto minValue =
                    std::numeric_limits<std::uint8_t>::min();
                constexpr auto maxValue =
                    std::numeric_limits<std::uint8_t>::max();
                if (value.isString())
                {
                    std::string const strValue = value.asString();

                    if (!strValue.empty() &&
                        ((strValue[0] < '0') || (strValue[0] > '9')))
                    {
                        if (field == sfTransactionResult)
                        {
                            auto ter = transCode(strValue);

                            if (!ter || TERtoInt(*ter) < minValue ||
                                TERtoInt(*ter) > maxValue)
                            {
                                error = out_of_range(json_name, fieldName);
                                return ret;
                            }

                            ret = detail::make_stvar<STUInt8>(
                                field,
                                static_cast<std::uint8_t>(TERtoInt(*ter)));
                        }
                        else
                        {
                            error = bad_type(json_name, fieldName);
                            return ret;
                        }
                    }
                    else
                    {
                        ret = detail::make_stvar<STUInt8>(
                            field,
                            beast::lexicalCastThrow<std::uint8_t>(strValue));
                    }
                }
                else if (value.isInt())
                {
                    if (value.asInt() < minValue || value.asInt() > maxValue)
                    {
                        error = out_of_range(json_name, fieldName);
                        return ret;
                    }

                    ret = detail::make_stvar<STUInt8>(
                        field, static_cast<std::uint8_t>(value.asInt()));
                }
                else if (value.isUInt())
                {
                    if (value.asUInt() > maxValue)
                    {
                        error = out_of_range(json_name, fieldName);
                        return ret;
                    }

                    ret = detail::make_stvar<STUInt8>(
                        field, static_cast<std::uint8_t>(value.asUInt()));
                }
                else
                {
                    error = bad_type(json_name, fieldName);
                    return ret;
                }
            }
            catch (std::exception const&)
            {
                error = invalid_data(json_name, fieldName);
                return ret;
            }
            break;

        case STI_UINT16:
            try
            {
                if (value.isString())
                {
                    std::string const strValue = value.asString();

                    if (!strValue.empty() &&
                        ((strValue[0] < '0') || (strValue[0] > '9')))
                    {
                        if (field == sfTransactionType)
                        {
                            ret = detail::make_stvar<STUInt16>(
                                field,
                                static_cast<std::uint16_t>(
                                    TxFormats::getInstance().findTypeByName(
                                        strValue)));

                            if (*name == sfGeneric)
                                name = &sfTransaction;
                        }
                        else if (field == sfLedgerEntryType)
                        {
                            ret = detail::make_stvar<STUInt16>(
                                field,
                                static_cast<std::uint16_t>(
                                    LedgerFormats::getInstance().findTypeByName(
                                        strValue)));

                            if (*name == sfGeneric)
                                name = &sfLedgerEntry;
                        }
                        else
                        {
                            error = invalid_data(json_name, fieldName);
                            return ret;
                        }
                    }
                    else
                    {
                        ret = detail::make_stvar<STUInt16>(
                            field,
                            beast::lexicalCastThrow<std::uint16_t>(strValue));
                    }
                }
                else if (value.isInt())
                {
                    ret = detail::make_stvar<STUInt16>(
                        field, to_unsigned<std::uint16_t>(value.asInt()));
                }
                else if (value.isUInt())
                {
                    ret = detail::make_stvar<STUInt16>(
                        field, to_unsigned<std::uint16_t>(value.asUInt()));
                }
                else
                {
                    error = bad_type(json_name, fieldName);
                    return ret;
                }
            }
            catch (std::exception const&)
            {
                error = invalid_data(json_name, fieldName);
                return ret;
            }

            break;

        case STI_UINT32:
            try
            {
                if (value.isString())
                {
                    ret = detail::make_stvar<STUInt32>(
                        field,
                        beast::lexicalCastThrow<std::uint32_t>(
                            value.asString()));
                }
                else if (value.isInt())
                {
                    ret = detail::make_stvar<STUInt32>(
                        field, to_unsigned<std::uint32_t>(value.asInt()));
                }
                else if (value.isUInt())
                {
                    ret = detail::make_stvar<STUInt32>(
                        field, safe_cast<std::uint32_t>(value.asUInt()));
                }
                else
                {
                    error = bad_type(json_name, fieldName);
                    return ret;
                }
            }
            catch (std::exception const&)
            {
                error = invalid_data(json_name, fieldName);
                return ret;
            }

            break;

        case STI_UINT64:
            try
            {
                if (value.isString())
                {
                    auto const str = value.asString();

                    std::uint64_t val;

                    auto [p, ec] = std::from_chars(
                        str.data(), str.data() + str.size(), val, 16);

                    if (ec != std::errc() || (p != str.data() + str.size()))
                        Throw<std::invalid_argument>("invalid data");

                    ret = detail::make_stvar<STUInt64>(field, val);
                }
                else if (value.isInt())
                {
                    ret = detail::make_stvar<STUInt64>(
                        field, to_unsigned<std::uint64_t>(value.asInt()));
                }
                else if (value.isUInt())
                {
                    ret = detail::make_stvar<STUInt64>(
                        field, safe_cast<std::uint64_t>(value.asUInt()));
                }
                else
                {
                    error = bad_type(json_name, fieldName);
                    return ret;
                }
            }
            catch (std::exception const&)
            {
                error = invalid_data(json_name, fieldName);
                return ret;
            }

            break;

        case STI_UINT128: {
            if (!value.isString())
            {
                error = bad_type(json_name, fieldName);
                return ret;
            }

            uint128 num;

            if (auto const s = value.asString(); !num.parseHex(s))
            {
                if (!s.empty())
                {
                    error = invalid_data(json_name, fieldName);
                    return ret;
                }

                num.zero();
            }

            ret = detail::make_stvar<STUInt128>(field, num);
            break;
        }

        case STI_UINT160: {
            if (!value.isString())
            {
                error = bad_type(json_name, fieldName);
                return ret;
            }

            uint160 num;

            if (auto const s = value.asString(); !num.parseHex(s))
            {
                if (!s.empty())
                {
                    error = invalid_data(json_name, fieldName);
                    return ret;
                }

                num.zero();
            }

            ret = detail::make_stvar<STUInt160>(field, num);
            break;
        }

        case STI_UINT256: {
            if (!value.isString())
            {
                error = bad_type(json_name, fieldName);
                return ret;
            }

            uint256 num;

            if (auto const s = value.asString(); !num.parseHex(s))
            {
                if (!s.empty())
                {
                    error = invalid_data(json_name, fieldName);
                    return ret;
                }

                num.zero();
            }

            ret = detail::make_stvar<STUInt256>(field, num);
            break;
        }

        case STI_VL:
            if (!value.isString())
            {
                error = bad_type(json_name, fieldName);
                return ret;
            }

            try
            {
                if (auto vBlob = strUnHex(value.asString()))
                {
                    ret = detail::make_stvar<STBlob>(
                        field, vBlob->data(), vBlob->size());
                }
                else
                {
                    Throw<std::invalid_argument>("invalid data");
                }
            }
            catch (std::exception const&)
            {
                error = invalid_data(json_name, fieldName);
                return ret;
            }

            break;

        case STI_AMOUNT:
            try
            {
                ret =
                    detail::make_stvar<STAmount>(amountFromJson(field, value));
            }
            catch (std::exception const&)
            {
                error = invalid_data(json_name, fieldName);
                return ret;
            }

            break;

        case STI_VECTOR256:
            if (!value.isArrayOrNull())
            {
                error = array_expected(json_name, fieldName);
                return ret;
            }

            try
            {
                STVector256 tail(field);
                for (Json::UInt i = 0; value.isValidIndex(i); ++i)
                {
                    uint256 s;
                    if (!s.parseHex(value[i].asString()))
                        Throw<std::invalid_argument>("invalid data");
                    tail.push_back(s);
                }
                ret = detail::make_stvar<STVector256>(std::move(tail));
            }
            catch (std::exception const&)
            {
                error = invalid_data(json_name, fieldName);
                return ret;
            }

            break;

        case STI_PATHSET:
            if (!value.isArrayOrNull())
            {
                error = array_expected(json_name, fieldName);
                return ret;
            }

            try
            {
                STPathSet tail(field);

                for (Json::UInt i = 0; value.isValidIndex(i); ++i)
                {
                    STPath p;

                    if (!value[i].isArrayOrNull())
                    {
                        std::stringstream ss;
                        ss << fieldName << "[" << i << "]";
                        error = array_expected(json_name, ss.str());
                        return ret;
                    }

                    for (Json::UInt j = 0; value[i].isValidIndex(j); ++j)
                    {
                        std::stringstream ss;
                        ss << fieldName << "[" << i << "][" << j << "]";
                        std::string const element_name(
                            json_name + "." + ss.str());

                        // each element in this path has some combination of
                        // account, currency, or issuer

                        Json::Value pathEl = value[i][j];

                        if (!pathEl.isObject())
                        {
                            error = not_an_object(element_name);
                            return ret;
                        }

                        Json::Value const& account = pathEl["account"];
                        Json::Value const& currency = pathEl["currency"];
                        Json::Value const& issuer = pathEl["issuer"];
                        bool hasCurrency = false;
                        AccountID uAccount, uIssuer;
                        Currency uCurrency;

                        if (account)
                        {
                            // human account id
                            if (!account.isString())
                            {
                                error =
                                    string_expected(element_name, "account");
                                return ret;
                            }

                            // If we have what looks like a 160-bit hex value,
                            // we set it, otherwise, we assume it's an AccountID
                            if (!uAccount.parseHex(account.asString()))
                            {
                                auto const a =
                                    parseBase58<AccountID>(account.asString());
                                if (!a)
                                {
                                    error =
                                        invalid_data(element_name, "account");
                                    return ret;
                                }
                                uAccount = *a;
                            }
                        }

                        if (currency)
                        {
                            // human currency
                            if (!currency.isString())
                            {
                                error =
                                    string_expected(element_name, "currency");
                                return ret;
                            }

                            hasCurrency = true;

                            if (!uCurrency.parseHex(currency.asString()))
                            {
                                if (!to_currency(
                                        uCurrency, currency.asString()))
                                {
                                    error =
                                        invalid_data(element_name, "currency");
                                    return ret;
                                }
                            }
                        }

                        if (issuer)
                        {
                            // human account id
                            if (!issuer.isString())
                            {
                                error = string_expected(element_name, "issuer");
                                return ret;
                            }

                            if (!uIssuer.parseHex(issuer.asString()))
                            {
                                auto const a =
                                    parseBase58<AccountID>(issuer.asString());
                                if (!a)
                                {
                                    error =
                                        invalid_data(element_name, "issuer");
                                    return ret;
                                }
                                uIssuer = *a;
                            }
                        }

                        p.emplace_back(
                            uAccount, uCurrency, uIssuer, hasCurrency);
                    }

                    tail.push_back(p);
                }
                ret = detail::make_stvar<STPathSet>(std::move(tail));
            }
            catch (std::exception const&)
            {
                error = invalid_data(json_name, fieldName);
                return ret;
            }

            break;

        case STI_ACCOUNT: {
            if (!value.isString())
            {
                error = bad_type(json_name, fieldName);
                return ret;
            }

            std::string const strValue = value.asString();

            try
            {
                if (AccountID account; account.parseHex(strValue))
                    return detail::make_stvar<STAccount>(field, account);

                if (auto result = parseBase58<AccountID>(strValue))
                    return detail::make_stvar<STAccount>(field, *result);

                error = invalid_data(json_name, fieldName);
                return ret;
            }
            catch (std::exception const&)
            {
                error = invalid_data(json_name, fieldName);
                return ret;
            }
        }
        break;

        case STI_ISSUE:
            try
            {
                ret = detail::make_stvar<STIssue>(issueFromJson(field, value));
            }
            catch (std::exception const&)
            {
                error = invalid_data(json_name, fieldName);
                return ret;
            }
            break;

        default:
            if (auto it = pluginLeafParserMap.find(field.fieldType);
                it != pluginLeafParserMap.end())
            {
                Buffer buf =
                    it->second(field, json_name, fieldName, name, value, error);
                if (!buf.empty())
                {
                    ret = detail::make_stvar<STPluginType>(
                        field, buf.data(), buf.size());
                }
                return ret;
            }

            error = unknown_type(json_name, fieldName, field.fieldType);
            return ret;
    }

    return ret;
}

static const int maxDepth = 64;

// Forward declaration since parseObject() and parseArray() call each other.
static std::optional<detail::STVar>
parseArray(
    std::string const& json_name,
    Json::Value const& json,
    SField const& inName,
    int depth,
    Json::Value& error);

static std::optional<STObject>
parseObject(
    std::string const& json_name,
    Json::Value const& json,
    SField const& inName,
    int depth,
    Json::Value& error)
{
    if (!json.isObjectOrNull())
    {
        error = not_an_object(json_name);
        return std::nullopt;
    }

    if (depth > maxDepth)
    {
        error = too_deep(json_name);
        return std::nullopt;
    }

    try
    {
        STObject data(inName);

        for (auto const& fieldName : json.getMemberNames())
        {
            Json::Value const& value = json[fieldName];

            auto const& field = SField::getField(fieldName);

            if (field == sfInvalid)
            {
                error = unknown_field(json_name, fieldName);
                return std::nullopt;
            }

            switch (field.fieldType)
            {
                // Object-style containers (which recurse).
                case STI_OBJECT:
                case STI_TRANSACTION:
                case STI_LEDGERENTRY:
                case STI_VALIDATION:
                    if (!value.isObject())
                    {
                        error = not_an_object(json_name, fieldName);
                        return std::nullopt;
                    }

                    try
                    {
                        auto ret = parseObject(
                            json_name + "." + fieldName,
                            value,
                            field,
                            depth + 1,
                            error);
                        if (!ret)
                            return std::nullopt;
                        data.emplace_back(std::move(*ret));
                    }
                    catch (std::exception const&)
                    {
                        error = invalid_data(json_name, fieldName);
                        return std::nullopt;
                    }

                    break;

                // Array-style containers (which recurse).
                case STI_ARRAY:
                    try
                    {
                        auto array = parseArray(
                            json_name + "." + fieldName,
                            value,
                            field,
                            depth + 1,
                            error);
                        if (!array.has_value())
                            return std::nullopt;
                        data.emplace_back(std::move(*array));
                    }
                    catch (std::exception const&)
                    {
                        error = invalid_data(json_name, fieldName);
                        return std::nullopt;
                    }

                    break;

                // Everything else (types that don't recurse).
                default: {
                    auto leaf =
                        parseLeaf(json_name, fieldName, &inName, value, error);

                    if (!leaf)
                        return std::nullopt;

                    data.emplace_back(std::move(*leaf));
                }

                break;
            }
        }

        // Some inner object types have templates.  Attempt to apply that.
        data.applyTemplateFromSField(inName);  // May throw

        return data;
    }
    catch (STObject::FieldErr const& e)
    {
        std::cerr << "template_mismatch: " << e.what() << "\n";
        error = template_mismatch(inName);
    }
    catch (std::exception const&)
    {
        error = invalid_data(json_name);
    }
    return std::nullopt;
}

static std::optional<detail::STVar>
parseArray(
    std::string const& json_name,
    Json::Value const& json,
    SField const& inName,
    int depth,
    Json::Value& error)
{
    if (!json.isArrayOrNull())
    {
        error = not_an_array(json_name);
        return std::nullopt;
    }

    if (depth > maxDepth)
    {
        error = too_deep(json_name);
        return std::nullopt;
    }

    try
    {
        STArray tail(inName);

        for (Json::UInt i = 0; json.isValidIndex(i); ++i)
        {
            bool const isObjectOrNull(json[i].isObjectOrNull());
            bool const singleKey(isObjectOrNull ? json[i].size() == 1 : true);

            if (!isObjectOrNull || !singleKey)
            {
                // null values are !singleKey
                error = singleton_expected(json_name, i);
                return std::nullopt;
            }

            // TODO: There doesn't seem to be a nice way to get just the
            // first/only key in an object without copying all keys into
            // a vector
            std::string const objectName(json[i].getMemberNames()[0]);
            ;
            auto const& nameField(SField::getField(objectName));

            if (nameField == sfInvalid)
            {
                error = unknown_field(json_name, objectName);
                return std::nullopt;
            }

            Json::Value const objectFields(json[i][objectName]);

            std::stringstream ss;
            ss << json_name << "."
               << "[" << i << "]." << objectName;

            auto ret = parseObject(
                ss.str(), objectFields, nameField, depth + 1, error);
            if (!ret)
            {
                std::string errMsg = error["error_message"].asString();
                error["error_message"] =
                    "Error at '" + ss.str() + "'. " + errMsg;
                return std::nullopt;
            }

            if (ret->getFName().fieldType != STI_OBJECT)
            {
                error = non_object_in_array(ss.str(), i);
                return std::nullopt;
            }

            tail.push_back(std::move(*ret));
        }

        return detail::make_stvar<STArray>(std::move(tail));
    }
    catch (std::exception const&)
    {
        error = invalid_data(json_name);
        return std::nullopt;
    }
}

}  // namespace STParsedJSONDetail

//------------------------------------------------------------------------------

STParsedJSONObject::STParsedJSONObject(
    std::string const& name,
    Json::Value const& json)
{
    using namespace STParsedJSONDetail;
    object = parseObject(name, json, sfGeneric, 0, error);
}

//------------------------------------------------------------------------------

STParsedJSONArray::STParsedJSONArray(
    std::string const& name,
    Json::Value const& json)
{
    using namespace STParsedJSONDetail;
    auto arr = parseArray(name, json, sfGeneric, 0, error);
    if (!arr)
        array.reset();
    else
    {
        auto p = dynamic_cast<STArray*>(&arr->get());
        if (p == nullptr)
            array.reset();
        else
            array = std::move(*p);
    }
}

void
registerLeafType(int type, parsePluginValuePtr functionPtr)
{
    STParsedJSONDetail::pluginLeafParserMap.insert({type, functionPtr});
}

}  // namespace ripple
