// Copyright (c) 2017-2025, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause

#include "app_helper.hpp"

#include "catch.hpp"

#include <algorithm>
#include <complex>
#include <cstdint>
#include <cstdlib>
#include <deque>
#include <forward_list>
#include <limits>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

using Catch::Matchers::WithinRel;

TEST_CASE_METHOD(TApp, "doubleFunction", "[optiontype]") {
    double res{0.0};
    app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
    args = {"--val", "-354.356"};
    run();
    CHECK_THAT(res, WithinRel(300.356));
    // get the original value as entered as an integer
    CHECK_THAT(app["--val"]->as<float>(), WithinRel(-354.356f));
}

TEST_CASE_METHOD(TApp, "doubleFunctionFail", "[optiontype]") {
    double res = NAN;
    app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
    args = {"--val", "not_double"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);
}

TEST_CASE_METHOD(TApp, "doubleVectorFunction", "[optiontype]") {
    std::vector<double> res;
    app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
        res = val;
        std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
    });
    args = {"--val", "5", "--val", "6", "--val", "7"};
    run();
    CHECK(3u == res.size());
    CHECK_THAT(res[0], WithinRel(10.0));
    CHECK_THAT(res[2], WithinRel(12.0));
}

TEST_CASE_METHOD(TApp, "doubleVectorFunctionFail", "[optiontype]") {
    std::vector<double> res;
    std::string vstring = "--val";
    app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) {
        res = val;
        std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
    });
    args = {"--val", "five", "--val", "nine", "--val", "7"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);
    // check that getting the results through the results function generates the same error
    CHECK_THROWS_AS(app[vstring]->results(res), CLI::ConversionError);
    auto strvec = app[vstring]->as<std::vector<std::string>>();
    CHECK(3u == strvec.size());
}

TEST_CASE_METHOD(TApp, "doubleVectorFunctionRunCallbackOnDefault", "[optiontype]") {
    std::vector<double> res;
    auto *opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
        res = val;
        std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
    });
    args = {"--val", "5", "--val", "6", "--val", "7"};
    run();
    CHECK(3u == res.size());
    CHECK(10.0 == res[0]);
    CHECK(12.0 == res[2]);
    CHECK(!opt->get_run_callback_for_default());
    opt->run_callback_for_default();
    opt->default_val(std::vector<int>{2, 1, -2});
    CHECK(7.0 == res[0]);
    CHECK(3.0 == res[2]);

    CHECK_THROWS_AS(opt->default_val("this is a string"), CLI::ConversionError);
    auto vec = opt->as<std::vector<double>>();
    REQUIRE(3U == vec.size());
    CHECK(5.0 == vec[0]);
    CHECK(7.0 == vec[2]);

#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) ||                                  \
    (!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
    opt->check(CLI::Number);
    opt->run_callback_for_default(false);
    CHECK_THROWS_AS(opt->default_val("this is a string"), CLI::ValidationError);
#endif
}

static const std::map<std::string, double> testValuesDouble{
    {"3.14159", 3.14159},
    {"-3.14159", -3.14159},
    {"-3.14159\t", -3.14159},
    {"-3.14159  ", -3.14159},
    {"+1.0", 1.0},
    {"-0.01", -0.01},
    {"-.01", -0.01},
    {"-.3251", -0.3251},
    {"+.3251", 0.3251},
    {"5e22", 5e22},
    {" 5e22", 5e22},
    {" 5e22  ", 5e22},
    {"-2E-2", -2e-2},
    {"5e+22", 5e22},
    {"1e06", 1e6},
    {"6.626e-34", 6.626e-34},
    {"6.626e+34", 6.626e34},
    {"-6.626e-34", -6.626e-34},
    {"224_617.445_991", 224617.445991},
    {"224'617.445'991", 224617.445991},
    {"inf", std::numeric_limits<double>::infinity()},
    {"+inf", std::numeric_limits<double>::infinity()},
    {"-inf", -std::numeric_limits<double>::infinity()},
    {"nan", std::numeric_limits<double>::signaling_NaN()},
    {"+nan", std::numeric_limits<double>::signaling_NaN()},
    {"-nan", -std::numeric_limits<double>::signaling_NaN()},

};

TEST_CASE_METHOD(TApp, "floatingConversions", "[optiontype]") {
    auto test_data = GENERATE(from_range(testValuesDouble));

    double val{0};
    app.add_option("--val", val);

    args = {"--val", test_data.first};

    run();
    if(std::isnan(test_data.second)) {
        CHECK(std::isnan(val));
    } else {

        CHECK_THAT(val, WithinRel(test_data.second, 1e-11));
    }
}

static const std::map<std::string, std::int64_t> testValuesInt{
    {"+99", 99},
    {"99", 99},
    {"-99", -99},
    {"-99 ", -99},
    {"0xDEADBEEF", 0xDEADBEEF},
    {"0xdeadbeef", 0xDEADBEEF},
    {"0XDEADBEEF", 0xDEADBEEF},
    {"0Xdeadbeef", 0xDEADBEEF},
    {"0xdead_beef", 0xDEADBEEF},
    {"0xdead'beef", 0xDEADBEEF},
    {"0o01234567", 001234567},
    {"0o755", 0755},
    {"0755", 0755},
    {"995862_262", 995862262},
    {"995862262", 995862262},
    {"-995862275", -995862275},
    {"\t-995862275\t", -995862275},
    {"-995'862'275", -995862275},
    {"0b11010110", 0xD6},
    {"0b1101'0110", 0xD6},
    {"0B11010110", 0xD6},
    {"0B1101'0110", 0xD6},
    {"1_2_3_4_5", 12345},
};

TEST_CASE_METHOD(TApp, "intConversions", "[optiontype]") {

    auto test_data = GENERATE(from_range(testValuesInt));

    std::int64_t val{0};
    app.add_option("--val", val);

    args = {"--val", test_data.first};

    run();

    CHECK(val == test_data.second);
}

TEST_CASE_METHOD(TApp, "intConversionsErange", "[optiontype]") {

    std::int64_t val{0};
    app.add_option("--val", val);

    args = {"--val", "0o11545241241415151512312415123125667"};

    CHECK_THROWS_AS(run(), CLI::ParseError);

    args = {"--val", "0b1011000001101011001100110011111000101010101011111111111111111111111001010111011100"};

    CHECK_THROWS_AS(run(), CLI::ParseError);

    args = {"--val", "0B1011000001101011001100110011111000101010101011111111111111111111111001010111011100"};

    CHECK_THROWS_AS(run(), CLI::ParseError);
}

static const std::map<std::string, std::uint64_t> testValuesUInt{
    {"+99", 99},
    {"99", 99},
    {" 99 ", 99},
    {"0xDEADBEEF", 0xDEADBEEF},
    {"0xdeadbeef", 0xDEADBEEF},
    {"0XDEADBEEF", 0xDEADBEEF},
    {"0Xdeadbeef", 0xDEADBEEF},
    {"0xdead_beef", 0xDEADBEEF},
    {"0xdead'beef", 0xDEADBEEF},
    {"0o01234567", 001234567},
    {"0o755", 0755},
    {"0o755\t", 0755},
    {"0755", 0755},
    {"995862_262", 995862262},
    {"995862262", 995862262},
    {"+995862275", +995862275},
    {"+995862275         \n\t", +995862275},
    {"995'862'275", 995862275},
    {"0b11010110", 0xD6},
    {"0b1101'0110", 0xD6},
    {"0b1101'0110                                                       ", 0xD6},
    {"0B11010110", 0xD6},
    {"0B1101'0110", 0xD6},
    {"1_2_3_4_5", 12345},
};

TEST_CASE_METHOD(TApp, "uintConversions", "[optiontype]") {

    auto test_data = GENERATE(from_range(testValuesUInt));

    std::uint64_t val{0};
    app.add_option("--val", val);

    args = {"--val", test_data.first};

    run();

    CHECK(val == test_data.second);
}

TEST_CASE_METHOD(TApp, "uintConversionsErange", "[optiontype]") {

    std::uint64_t val{0};
    app.add_option("--val", val);

    args = {"--val", "0o11545241241415151512312415123125667"};

    CHECK_THROWS_AS(run(), CLI::ParseError);

    args = {"--val", "0b1011000001101011001100110011111000101010101011111111111111111111111001010111011100"};

    CHECK_THROWS_AS(run(), CLI::ParseError);

    args = {"--val", "0B1011000001101011001100110011111000101010101011111111111111111111111001010111011100"};

    CHECK_THROWS_AS(run(), CLI::ParseError);
}

TEST_CASE_METHOD(TApp, "CharOption", "[optiontype]") {
    char c1{'t'};
    app.add_option("-c", c1);

    args = {"-c", "g"};
    run();
    CHECK('g' == c1);

    args = {"-c", "1"};
    run();
    CHECK('1' == c1);

    args = {"-c", "77"};
    run();
    CHECK(77 == c1);

    // convert hex for digit
    args = {"-c", "0x44"};
    run();
    CHECK(0x44 == c1);

    args = {"-c", "751615654161688126132138844896646748852"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);
}

TEST_CASE_METHOD(TApp, "IntegerOverFlowShort", "[optiontype]") {
    std::int16_t A{0};
    std::uint16_t B{0};

    app.add_option("-a", A);
    app.add_option("-b", B);

    args = {"-a", "2626254242"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "2626254242"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "-26262"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "-262624262525"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);
}

TEST_CASE_METHOD(TApp, "IntegerOverFlowInt", "[optiontype]") {
    int A{0};
    unsigned int B{0};

    app.add_option("-a", A);
    app.add_option("-b", B);

    args = {"-a", "262625424225252"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "262625424225252"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "-2626225252"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "-26262426252525252"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);
}

TEST_CASE_METHOD(TApp, "IntegerOverFlowLong", "[optiontype]") {
    std::int32_t A{0};
    std::uint32_t B{0};

    app.add_option("-a", A);
    app.add_option("-b", B);

    args = {"-a", "1111111111111111111111111111"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "1111111111111111111111111111"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "-2626225252"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "-111111111111111111111111"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);
}

TEST_CASE_METHOD(TApp, "IntegerOverFlowLongLong", "[optiontype]") {
    std::int64_t A{0};
    std::uint64_t B{0};

    app.add_option("-a", A);
    app.add_option("-b", B);

    args = {"-a", "1111111111111111111111111111111111111111111111111111111111"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "1111111111111111111111111111111111111111111111111111111111"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "-2626225252"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);

    args = {"-b", "-111111111111111111111111111111111111111111111111111111111"};
    CHECK_THROWS_AS(run(), CLI::ConversionError);
}

// now with tuple support this is possible
TEST_CASE_METHOD(TApp, "floatPair", "[optiontype]") {

    std::pair<float, float> custom_opt;

    auto *opt = app.add_option("--fp", custom_opt)->delimiter(',');
    opt->default_str("3.4,2.7");

    args = {"--fp", "12", "1.5"};

    run();
    CHECK(12.0f == Approx(custom_opt.first));
    CHECK(1.5f == Approx(custom_opt.second));
    args = {};
    opt->force_callback();
    run();
    CHECK(3.4f == Approx(custom_opt.first));
    CHECK(2.7f == Approx(custom_opt.second));
}

// now with tuple support this is possible
TEST_CASE_METHOD(TApp, "doubleVector", "[optiontype]") {

    std::vector<double> custom_opt;

    app.add_option("--fp", custom_opt);

    args = {"--fp", "12.7", "1.5"};
    run();
    CHECK(12.7 == Approx(custom_opt[0]));
    CHECK(1.5 == Approx(custom_opt[1]));
    args = {"--fp", "12.7", "-.5"};
    run();
    CHECK(12.7 == Approx(custom_opt[0]));
    CHECK(-0.5 == Approx(custom_opt[1]));

    args = {"--fp", "-.7", "+.5"};
    run();
    CHECK(-0.7 == Approx(custom_opt[0]));
    CHECK(0.5 == Approx(custom_opt[1]));
}
