#include "catch2_common.h"

#include <memory>

namespace
{
using CallbackMockType = TangoTest::CallbackMock<Tango::EventData>;

constexpr short k_enum_default_value = 0;

enum class TestEnum : short
{
    ONE = 0,
    TWO,
    THREE,
    FOUR,
    FIVE,
    SIX,
    SEVEN
};
} // namespace

template <typename Base>
class ChangeEventDS : public Base
{
  public:
    using Base::Base;

    ~ChangeEventDS() override { }

    void init_device() override { }

    void read_attr(Tango::Attribute &att) override
    {
        att.set_value(&enum_value);
    }

    void write_attr(Tango::WAttribute &att)
    {
        att.get_write_value(enum_value);
        this->push_change_event("enum_attr", &enum_value);
    }

    static void attribute_factory(std::vector<Tango::Attr *> &attrs)
    {
        Tango::UserDefaultAttrProp props;
        std::vector<std::string> labels;
        labels.emplace_back("ONE");
        labels.emplace_back("TWO");
        labels.emplace_back("THREE");
        labels.emplace_back("FOUR");
        labels.emplace_back("FIVE");
        labels.emplace_back("SIX");
        labels.emplace_back("SEVEN");
        props.set_enum_labels(labels);

        using Attr = TangoTest::AutoEnumAttr<TestEnum, &ChangeEventDS::read_attr, &ChangeEventDS::write_attr>;
        auto attr = new Attr("enum_attr");

        attr->set_default_properties(props);
        attr->set_change_event(true, true);
        attrs.push_back(attr);
    }

  private:
    Tango::DevEnum enum_value{k_enum_default_value};
};

TANGO_TEST_AUTO_DEV_TMPL_INSTANTIATE(ChangeEventDS, 6)

SCENARIO("Change event detection works with enumerations")
{
    int idlver = GENERATE(TangoTest::idlversion(6));
    GIVEN("a device proxy to a simple IDLv" << idlver << " device")
    {
        TangoTest::Context ctx{"enum_attr", "ChangeEventDS", idlver};
        std::shared_ptr<Tango::DeviceProxy> device = ctx.get_proxy();

        REQUIRE(idlver == device->get_idl_version());

        THEN("we check that no polling is enabled")
        {
            std::string attr_name{"enum_attr"};
            REQUIRE(!device->is_attribute_polled(attr_name));

            AND_THEN("we subscribe to the attribute " << attr_name)
            {
                CallbackMockType cb;

                TangoTest::Subscription sub{device, attr_name, Tango::CHANGE_EVENT, &cb};

                require_initial_events(cb, k_enum_default_value);

                AND_THEN("write into it")
                {
                    const auto val1 = static_cast<short>(1);

                    {
                        Tango::DeviceAttribute daw{attr_name, val1};
                        REQUIRE_NOTHROW(device->write_attribute(daw));
                    }

                    AND_THEN("we get an event")
                    {
                        using namespace Catch::Matchers;
                        using namespace TangoTest::Matchers;

                        // no previous value branch in EventSupplier::detect_and_push_change_event
                        auto event = cb.pop_next_event();
                        REQUIRE(event != std::nullopt);
                        REQUIRE_THAT(event->errors, IsEmpty());
                        REQUIRE_THAT(event, EventType(Tango::CHANGE_EVENT));
                        REQUIRE_THAT(event, EventValueMatches(AttrQuality(Tango::ATTR_VALID)));
                        REQUIRE_THAT(event, EventValueMatches(AttrNameContains(attr_name)));
                        REQUIRE_THAT(event, EventValueMatches(AnyLikeContains(val1)));
                        REQUIRE_THAT(event, EventReason(Tango::EventReason::Update));

                        AND_THEN("write again")
                        {
                            const auto val2 = static_cast<short>(2);

                            {
                                Tango::DeviceAttribute daw{attr_name, val2};
                                REQUIRE_NOTHROW(device->write_attribute(daw));
                            }

                            AND_THEN("we get another event")
                            {
                                // detect_change called
                                auto event = cb.pop_next_event();
                                REQUIRE(event != std::nullopt);
                                REQUIRE_THAT(event->errors, IsEmpty());
                                REQUIRE_THAT(event, EventType(Tango::CHANGE_EVENT));
                                REQUIRE_THAT(event, EventValueMatches(AttrQuality(Tango::ATTR_VALID)));
                                REQUIRE_THAT(event, EventValueMatches(AttrNameContains(attr_name)));
                                REQUIRE_THAT(event, EventValueMatches(AnyLikeContains(val2)));
                                REQUIRE_THAT(event, EventReason(Tango::EventReason::Update));
                            }
                        }
                    }
                }
            }
        }
    }
}
