/*
 * SPDX-FileCopyrightText: 2025 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */
#include "catch2_common.h"
#include "common.h"

#include <tango/client/EventQueue.h>

#include <catch2/catch_template_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>

#include <type_traits>

SCENARIO("Event queue defaults to being empty")
{
    // it is also unlimited by default but we can't check that
    Tango::EventQueue eq;

    REQUIRE(eq.size() == 0);
    REQUIRE(eq.is_empty() == true);

    using namespace Catch::Matchers;
    using namespace TangoTest::Matchers;

    Tango::EventDataList event_data_list;
    eq.get_events(event_data_list);
    REQUIRE_THAT(event_data_list, IsEmpty());

    Tango::AttrConfEventDataList attr_conf_list;
    eq.get_events(attr_conf_list);
    REQUIRE_THAT(attr_conf_list, IsEmpty());

    Tango::DataReadyEventDataList data_ready_list;
    eq.get_events(data_ready_list);
    REQUIRE_THAT(data_ready_list, IsEmpty());

    Tango::DataReadyEventDataList dev_intr_change_list;
    eq.get_events(dev_intr_change_list);
    REQUIRE_THAT(dev_intr_change_list, IsEmpty());

    Tango::DataReadyEventDataList pipe_event_list;
    eq.get_events(pipe_event_list);
    REQUIRE_THAT(pipe_event_list, IsEmpty());

    REQUIRE_THROWS_MATCHES(
        eq.get_last_event_date(), Tango::DevFailed, FirstErrorMatches(Reason(Tango::API_EventQueues)));
}

// clang-format off
using EventList = std::tuple<
                             Tango::EventDataList,
                             Tango::AttrConfEventDataList,
                             Tango::DataReadyEventDataList,
                             Tango::DevIntrChangeEventDataList,
                             Tango::PipeEventDataList
                             >;
// clang-format on

TEMPLATE_LIST_TEST_CASE("catch2 event queue can be filled", "", EventList)
{
    using Element = std::remove_pointer_t<typename TestType::value_type>;

    Tango::EventQueue eq;
    auto elem = new Element(TangoTest::construct_default<Element>());
    eq.insert_event(elem);

    REQUIRE(eq.size() == 1);
    REQUIRE(eq.is_empty() == false);
}

TEMPLATE_LIST_TEST_CASE("catch2 event queue can be filled and read out again", "", EventList)
{
    using namespace Catch::Matchers;

    using Element = std::remove_pointer_t<typename TestType::value_type>;

    Tango::EventQueue eq;
    auto elem = new Element(TangoTest::construct_default<Element>());
    eq.insert_event(elem);

    TestType list;
    eq.get_events(list);
    REQUIRE_THAT(list, SizeIs(1));
    REQUIRE(list[0] == elem);
}

TEMPLATE_LIST_TEST_CASE("catch2 event queue can be filled and read out again explicit ctor", "", EventList)
{
    using namespace Catch::Matchers;

    using Element = std::remove_pointer_t<typename TestType::value_type>;

    Tango::EventQueue eq{0};
    auto elem = new Element(TangoTest::construct_default<Element>());
    eq.insert_event(elem);

    TestType list;
    eq.get_events(list);
    REQUIRE_THAT(list, SizeIs(1));
    REQUIRE(list[0] == elem);
}

TEMPLATE_LIST_TEST_CASE("catch2 event queue last element can be retrieved I (circular)", "", EventList)
{
    using namespace Catch::Matchers;
    using namespace TangoTest::Matchers;
    using namespace std::literals::chrono_literals;

    using Element = std::remove_pointer_t<typename TestType::value_type>;

    auto now = Tango::make_TimeVal(std::chrono::system_clock::now());

    Tango::EventQueue eq{1};
    auto elem0 = new Element(TangoTest::construct_default<Element>());
    eq.insert_event(elem0);

    auto elem1 = new Element(TangoTest::construct_default<Element>());
    elem1->reception_date = now;
    eq.insert_event(elem1);
    // maximum size of 1
    REQUIRE(eq.size() == 1);

    REQUIRE_THAT(eq.get_last_event_date(), WithinTimeAbsMatcher(now, 0ns));

    TestType list;
    eq.get_events(list);
    REQUIRE_THAT(list, SizeIs(1));
    REQUIRE(list[0] == elem1);
}

TEMPLATE_LIST_TEST_CASE("catch2 event queue last element can be retrieved II (circular)", "", EventList)
{
    using namespace Catch::Matchers;

    long queue_size = GENERATE(5, 10);
    SECTION("Prepare queue size")
    {
        long num_elements = GENERATE(0, 1, 3, 5, 10, 20);

        using Element = std::remove_pointer_t<typename TestType::value_type>;

        Tango::EventQueue eq{queue_size};
        std::vector<Element *> vec;
        for(long i = 0; i < num_elements; i += 1)
        {
            auto elem = new Element(TangoTest::construct_default<Element>());
            eq.insert_event(elem);

            vec.push_back(elem);
        }

        auto present_elements = std::min(queue_size, num_elements);
        REQUIRE(eq.size() == present_elements);

        // check that we kept the correct ones

        TestType list;
        eq.get_events(list);
        REQUIRE_THAT(list, SizeIs(present_elements));

        for(long i = 0; i < present_elements; i += 1)
        {
            const long offset = num_elements - present_elements;
            REQUIRE(list[i] == vec[offset + i]);
        }
    }
}

TEMPLATE_LIST_TEST_CASE("catch2 event queue last element can be retrieved (unlimited)", "", EventList)
{
    using namespace Catch::Matchers;
    using namespace TangoTest::Matchers;
    using namespace std::literals::chrono_literals;

    using Element = std::remove_pointer_t<typename TestType::value_type>;

    auto now = Tango::make_TimeVal(std::chrono::system_clock::now());

    Tango::EventQueue eq;
    auto elem0 = new Element(TangoTest::construct_default<Element>());
    eq.insert_event(elem0);

    auto elem1 = new Element(TangoTest::construct_default<Element>());
    elem1->reception_date = now;
    eq.insert_event(elem1);
    REQUIRE(eq.size() == 2);

    REQUIRE_THAT(eq.get_last_event_date(), WithinTimeAbsMatcher(now, 0ns));

    TestType list;
    eq.get_events(list);
    REQUIRE_THAT(list, SizeIs(2));
    REQUIRE(list[0] == elem0);
    REQUIRE(list[1] == elem1);
}
