// -----------------------------------------------------------------------------------------------------
// Copyright (c) 2006-2020, Knut Reinert & Freie Universität Berlin
// Copyright (c) 2016-2020, Knut Reinert & MPI für molekulare Genetik
// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
// -----------------------------------------------------------------------------------------------------

#include <gtest/gtest.h>

#include <range/v3/algorithm/equal.hpp>

#include "configuration_mock.hpp"

#include <seqan3/core/algorithm/configuration.hpp>
#include <seqan3/test/expect_range_eq.hpp>

TEST(configuration, concept_check)
{
    EXPECT_TRUE(seqan3::detail::config_element_specialisation<bar>);
    EXPECT_FALSE(seqan3::detail::config_element_specialisation<int>);

    EXPECT_TRUE((seqan3::tuple_like<seqan3::configuration<bax, bar>>));
}

TEST(configuration, tuple_size)
{
    EXPECT_EQ((std::tuple_size_v<seqan3::configuration<bax, bar>>), 2u);
    EXPECT_EQ((std::tuple_size<seqan3::configuration<bax, bar>>::value), 2u);
}

TEST(configuration, tuple_element)
{
    EXPECT_TRUE((std::is_same_v<typename std::tuple_element<0, seqan3::configuration<bax, bar>>::type, bax>));
    EXPECT_TRUE((std::is_same_v<std::tuple_element_t<0, seqan3::configuration<bax, bar>>, bax>));
}

TEST(configuration, standard_construction)
{
    EXPECT_TRUE((std::is_default_constructible_v<seqan3::configuration<bax, bar>>));
    EXPECT_TRUE((std::is_copy_constructible_v<seqan3::configuration<bax, bar>>));
    EXPECT_TRUE((std::is_move_constructible_v<seqan3::configuration<bax, bar>>));
    EXPECT_TRUE((std::is_copy_assignable_v<seqan3::configuration<bax, bar>>));
    EXPECT_TRUE((std::is_move_assignable_v<seqan3::configuration<bax, bar>>));
}

TEST(configuration, construction_from_elements)
{
    seqan3::configuration cfg0{};       // empty
    seqan3::configuration cfg1{bax{}};  // one element

    EXPECT_EQ((std::tuple_size_v<decltype(cfg0)>), 0u);
    EXPECT_EQ((std::tuple_size_v<decltype(cfg1)>), 1u);
}

TEST(configuration, size)
{
    seqan3::configuration<foobar<>> cfg{};
    EXPECT_EQ(cfg.size(), 1u);
    EXPECT_EQ((seqan3::configuration<foo, foobar<>>{}.size()), 2u);
    EXPECT_EQ(seqan3::configuration{}.size(), 0u);
}

TEST(configuration, get_by_position)
{
    seqan3::configuration cfg = bax{2.2} | bar{1};

    { // l-value
        EXPECT_EQ(std::get<1>(cfg).value, 1);
        std::get<1>(cfg).value = 3;
        EXPECT_EQ(std::get<1>(cfg).value, 3);
        EXPECT_TRUE((std::is_same_v<decltype(std::get<1>(cfg)), bar &>));
    }

    { // const l-value
        seqan3::configuration<bax, bar> const cfg_c{cfg};
        EXPECT_EQ(std::get<1>(cfg_c).value, 3);

        EXPECT_TRUE((std::is_same_v<decltype(std::get<1>(cfg_c)), bar const &>));
    }

    { // r-value
        seqan3::configuration<bax, bar> cfg_r{cfg};
        EXPECT_EQ(std::get<1>(std::move(cfg_r)).value, 3);
        EXPECT_TRUE((std::is_same_v<decltype(std::get<1>(std::move(cfg_r))), bar &&>));
    }

    { // const r-value
        seqan3::configuration<bax, bar> const cfg_rc{cfg};
        EXPECT_EQ(std::get<1>(std::move(cfg_rc)).value, 3);
        // TODO(rrahn): Enable when seqan3::get(const &&) is fixed for gcc7 as well.
        // EXPECT_TRUE((std::is_same_v<decltype(std::get<1>(std::move(cfg_rc))), bar const &&>));
    }
}

TEST(configuration, get_by_type)
{
    seqan3::configuration cfg = bax{2.2} | bar{1};

    { // l-value
        EXPECT_FLOAT_EQ(std::get<bax>(cfg).value, 2.2);
        std::get<bax>(cfg).value = 3.1;
        std::get<bar>(cfg).value = 3;
        EXPECT_FLOAT_EQ(std::get<bax>(cfg).value, 3.1);
        EXPECT_TRUE((std::is_same_v<decltype(std::get<bax>(cfg)), bax &>));
    }

    { // const l-value
        seqan3::configuration<bax, bar> const cfg_c{cfg};
        EXPECT_EQ(std::get<bar>(cfg_c).value, 3);

        EXPECT_TRUE((std::is_same_v<decltype(std::get<bar>(cfg_c)), bar const &>));
    }

    { // r-value
        seqan3::configuration<bax, bar> cfg_r{cfg};
        EXPECT_EQ(std::get<bar>(std::move(cfg_r)).value, 3);
        EXPECT_TRUE((std::is_same_v<decltype(std::get<bar>(std::move(cfg_r))), bar &&>));
    }

    { // const r-value
        seqan3::configuration<bax, bar> const cfg_rc{cfg};
        EXPECT_EQ(std::get<bar>(std::move(cfg_rc)).value, 3);
        // TODO(rrahn): Enable when seqan3::get(const &&) is fixed for gcc7 as well.
        // EXPECT_TRUE((std::is_same_v<decltype(std::get<bar>(std::move(cfg_rc))), bar const &&>));
    }
}

TEST(configuration, get_by_type_template)
{
    seqan3::configuration cfg = bar{1} | foobar<>{std::vector{0, 1, 2, 3}};

    { // l-value
        EXPECT_TRUE(ranges::equal(seqan3::get<foobar>(cfg).value, std::vector{0, 1, 2, 3}));
        EXPECT_TRUE((std::is_same_v<decltype(seqan3::get<foobar>(cfg)), foobar<> &>));
    }

    { // const l-value
        seqan3::configuration<bar, foobar<>> const cfg_c{cfg};
        EXPECT_TRUE(ranges::equal(seqan3::get<foobar>(cfg_c).value, std::vector{0, 1, 2, 3}));
        EXPECT_TRUE((std::is_same_v<decltype(seqan3::get<foobar>(cfg_c)), foobar<> const &>));
    }

    { // r-value
        seqan3::configuration<bar, foobar<>> cfg_r{cfg};
        EXPECT_TRUE(ranges::equal(seqan3::get<foobar>(std::move(cfg_r)).value, std::vector{0, 1, 2, 3}));
        EXPECT_TRUE((std::is_same_v<decltype(seqan3::get<foobar>(std::move(cfg_r))), foobar<> &&>));
    }

    { // const r-value
        seqan3::configuration<bar, foobar<>> const cfg_cr{cfg};
        EXPECT_TRUE(ranges::equal(seqan3::get<foobar>(std::move(cfg_cr)).value, std::vector{0, 1, 2, 3}));
        // TODO(rrahn): Enable when seqan3::get(const &&) is fixed for gcc7 as well.
        // EXPECT_TRUE((std::is_same_v<decltype(seqan3::get<foobar>(std::move(cfg_cr))), foobar<> const &&>));
    }
}

TEST(configuration, exists_by_type)
{
    seqan3::configuration<bax, bar> cfg{};

    EXPECT_TRUE(std::remove_reference_t<decltype(cfg)>::exists<bax>());
    EXPECT_FALSE(decltype(cfg)::exists<foo>());
}

TEST(configuration, exists_by_type_template)
{
    seqan3::configuration<bax, foobar<>> cfg{};

    EXPECT_TRUE(decltype(cfg)::exists<foobar>());
    EXPECT_TRUE(decltype(cfg)::exists<bax>());
    EXPECT_FALSE(decltype(cfg)::exists<foo>());
}

TEST(configuration, remove_by_type)
{
    {
        seqan3::configuration<foo, bax, bar> cfg{};

        // remove middle
        EXPECT_TRUE((std::same_as<decltype(cfg.template remove<bax>()), seqan3::configuration<foo, bar>>));
        // remove head
        EXPECT_TRUE((std::same_as<decltype(cfg.template remove<bar>()), seqan3::configuration<foo, bax>>));
        // remove tail
        EXPECT_TRUE((std::same_as<decltype(cfg.template remove<foo>()), seqan3::configuration<bax, bar>>));

        seqan3::configuration<foo> single_cfg{};
        EXPECT_TRUE((std::same_as<decltype(single_cfg.template remove<foo>()), seqan3::configuration<>>));
    }

    {   // const cfg
        seqan3::configuration<foo, bax, bar> const cfg{};

        // remove middle
        EXPECT_TRUE((std::same_as<decltype(cfg.template remove<bax>()), seqan3::configuration<foo, bar>>));
        // remove head
        EXPECT_TRUE((std::same_as<decltype(cfg.template remove<bar>()), seqan3::configuration<foo, bax>>));
        // remove tail
        EXPECT_TRUE((std::same_as<decltype(cfg.template remove<foo>()), seqan3::configuration<bax, bar>>));

        seqan3::configuration<foo> const single_cfg{};
        EXPECT_TRUE((std::same_as<decltype(single_cfg.template remove<foo>()), seqan3::configuration<>>));
    }
}

TEST(configuration, remove_by_type_template)
{
    {
        seqan3::configuration<foo, foobar<>, bar> cfg{};

        EXPECT_TRUE((std::same_as<decltype(cfg.template remove<foobar>()), seqan3::configuration<foo, bar>>));

        seqan3::configuration<foobar<>> single_cfg{};
        EXPECT_TRUE((std::same_as<decltype(single_cfg.template remove<foobar>()), seqan3::configuration<>>));
    }

    {   //const cfg
        seqan3::configuration<foo, foobar<>, bar> const cfg{};

        EXPECT_TRUE((std::same_as<decltype(cfg.template remove<foobar>()), seqan3::configuration<foo, bar>>));

        seqan3::configuration<foobar<>> const single_cfg{};
        EXPECT_TRUE((std::same_as<decltype(single_cfg.template remove<foobar>()), seqan3::configuration<>>));
    }
}

TEST(configuration, get_or_by_type)
{
    seqan3::configuration cfg = bax{2.2} | bar{1};

    { // l-value
        EXPECT_FLOAT_EQ(cfg.get_or(bax{1.3}).value, 2.2);
        EXPECT_TRUE((std::same_as<decltype(cfg.get_or(bax{1.3})), bax &>));
        EXPECT_EQ(cfg.get_or(foo{"test"}).value, "test");
    }

    { // const l-value
        EXPECT_FLOAT_EQ(std::as_const(cfg).get_or(bax{1.3}).value, 2.2);
        EXPECT_TRUE((std::same_as<decltype(std::as_const(cfg).get_or(bax{1.3})), bax const &>));
        EXPECT_EQ(std::as_const(cfg).get_or(foo{"test"}).value, "test");
    }

    { // r-value
        EXPECT_FLOAT_EQ(std::move(seqan3::configuration<bax, bar>{cfg}).get_or(bax{1.3}).value, 2.2);
        EXPECT_TRUE((std::same_as<decltype(seqan3::configuration<bax, bar>{cfg}.get_or(bax{1.3})), bax &&>));
        EXPECT_EQ(std::move(seqan3::configuration<bax, bar>{cfg}).get_or(foo{"test"}).value, "test");
    }

    { // const r-value
        seqan3::configuration<bax, bar> const cfg_cr{cfg};
        EXPECT_FLOAT_EQ(std::move(cfg_cr).get_or(bax{1.3}).value, 2.2);
        // TODO: Enable when gcc7 support is dropped or seqan3::get is implemented as CPO.
        // seqan3::configuration<bax, bar> const cfg_cr2{cfg};
        // EXPECT_TRUE((std::same_as<decltype(std::move(cfg_cr2).get_or(bax{1.3})), bax const &&>));
        seqan3::configuration<bax, bar> const cfg_cr3{cfg};
        EXPECT_EQ(std::move(cfg_cr3).get_or(foo{"test"}).value, "test");
    }
}

TEST(configuration, get_or_by_type_constexpr)
{
    constexpr seqan3::configuration cfg = bax{2.2} | bar{1};

    { // l-value
        constexpr auto element = cfg.get_or(bax{1.3});
        EXPECT_FLOAT_EQ(element.value, 2.2);
    }

    { // const l-value
        constexpr auto element = std::as_const(cfg).get_or(bax{1.3});
        EXPECT_FLOAT_EQ(element.value, 2.2);
    }

    { // r-value
        constexpr auto element = seqan3::configuration{cfg}.get_or(bax{1.3});
        EXPECT_FLOAT_EQ(element.value, 2.2);
    }

    { // const r-value
        constexpr seqan3::configuration const cfg_c{cfg};
        constexpr auto element = std::move(cfg_c).get_or(bax{1.3});
        EXPECT_FLOAT_EQ(element.value, 2.2);
    }
}

TEST(configuration, get_or_by_type_template)
{
    seqan3::configuration cfg = bar{1} | foobar<>{std::vector{0, 1, 2, 3}};

    using double_vec_t = std::vector<double>;
    using alternative_t = foobar<double_vec_t>;
    alternative_t alternative{double_vec_t{3.3}};

    { // l-value
        EXPECT_RANGE_EQ(cfg.get_or(alternative_t{double_vec_t{3.3}}).value, (std::vector{0, 1, 2, 3}));
        EXPECT_RANGE_EQ(cfg.get_or(alternative).value, (std::vector{0, 1, 2, 3}));
        EXPECT_EQ(cfg.get_or(foo{"test"}).value, "test");
        EXPECT_TRUE((std::same_as<decltype(cfg.get_or(alternative)), foobar<std::vector<int>> &>));
    }

    { // const l-value
        EXPECT_RANGE_EQ(std::as_const(cfg).get_or(alternative_t{double_vec_t{3.3}}).value, (std::vector{0, 1, 2, 3}));
        EXPECT_RANGE_EQ(std::as_const(cfg).get_or(alternative).value, (std::vector{0, 1, 2, 3}));
        EXPECT_EQ(std::as_const(cfg).get_or(foo{"test"}).value, "test");
        EXPECT_TRUE((std::same_as<decltype(std::as_const(cfg).get_or(alternative)), foobar<std::vector<int>> const &>));
    }

    { // r-value;
        EXPECT_RANGE_EQ((seqan3::configuration<bar, foobar<>>{cfg}.get_or(alternative_t{double_vec_t{3.3}}).value),
                        (std::vector{0, 1, 2, 3}));
        EXPECT_RANGE_EQ((seqan3::configuration<bar, foobar<>>{cfg}.get_or(alternative).value),
                        (std::vector{0, 1, 2, 3}));
        EXPECT_EQ((seqan3::configuration<bar, foobar<>>{cfg}.get_or(foo{"test"}).value), "test");
        EXPECT_TRUE((std::same_as<decltype(seqan3::configuration<bar, foobar<>>{cfg}.get_or(alternative)),
                    foobar<std::vector<int>> &&>));
    }

    { // const r-value
        seqan3::configuration<bar, foobar<>> const cfg_cr{cfg};
        EXPECT_RANGE_EQ(std::move(cfg_cr).get_or(alternative_t{double_vec_t{3.3}}).value, (std::vector{0, 1, 2, 3}));
        seqan3::configuration<bar, foobar<>> const cfg_cr2{cfg};
        EXPECT_RANGE_EQ(std::move(cfg_cr2).get_or(alternative).value, (std::vector{0, 1, 2, 3}));
        seqan3::configuration<bar, foobar<>> const cfg_cr3{cfg};
        EXPECT_EQ(std::move(cfg_cr3).get_or(foo{"test"}).value, "test");
        // TODO: Enable when gcc7 support is dropped or seqan3::get is implemented as CPO.
        // seqan3::configuration<bar, foobar<>> const cfg_cr4{cfg};
        // EXPECT_TRUE((std::same_as<decltype(std::move(cfg_cr4).get_or(alternative)),
        //             foobar<std::vector<int>> const &&>));
    }
}

TEST(configuration, get_or_by_template_type_constexpr)
{
    constexpr seqan3::configuration cfg = bar{1} | foobar<bool>{true};

    { // l-value
        constexpr auto element = cfg.get_or(foobar<int>{1});
        EXPECT_EQ(element.value, true);
    }

    { // const l-value
        constexpr auto element = std::as_const(cfg).get_or(foobar<int>{1});
        EXPECT_EQ(element.value, true);
    }

    { // r-value
        constexpr auto element = seqan3::configuration{cfg}.get_or(foobar<int>{1});
        EXPECT_EQ(element.value, true);
    }

    { // const r-value
        constexpr seqan3::configuration const cfg_c{cfg};
        constexpr auto element = std::move(cfg_c).get_or(foobar<int>{1});
        EXPECT_EQ(element.value, true);
    }
}

TEST(configuration, get_or_perfectly_forwarded_alternative)
{
    seqan3::configuration<bar, foo> cfg{};

    using alternative_t = foobar<std::vector<double>>;
    alternative_t alternative{std::vector<double>{3.3}};
    alternative_t const const_alternative{alternative};

    EXPECT_RANGE_EQ(cfg.get_or(alternative_t{std::vector<double>{3.3}}).value, (std::vector<double>{3.3}));
    EXPECT_TRUE((std::same_as<decltype(cfg.get_or(alternative)), alternative_t &>));
    EXPECT_TRUE((std::same_as<decltype(cfg.get_or(const_alternative)), alternative_t const &>));
    EXPECT_TRUE((std::same_as<decltype(cfg.get_or(alternative_t{std::vector<double>{3.3}})), alternative_t>));
    EXPECT_TRUE((std::same_as<decltype(cfg.get_or(std::move(const_alternative))), alternative_t const>));
}
