#define BOOST_TEST_MODULE STGSubscriptions

#include "stg/subscriptions.h"

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wsign-compare"
#pragma GCC diagnostic ignored "-Wparentheses"
#include <boost/test/unit_test.hpp>
#pragma GCC diagnostic pop

using STG::Subscriptions;
using STG::ScopedConnection;

namespace
{

struct Receiver
{
    Receiver() noexcept
        : countR0(0),
          countR1(0),
          valueR1(0),
          countR2(0),
          value1R2(0)
    {}

    void r0() { ++countR0; }
    void r1(uint8_t v) { ++countR1; valueR1 = v; }
    void r2(uint8_t v1, const std::string& v2) { ++countR2; value1R2 = v1; value2R2 = v2; }

    size_t countR0;
    size_t countR1;
    uint8_t valueR1;
    size_t countR2;
    uint8_t value1R2;
    std::string value2R2;
};

}

BOOST_AUTO_TEST_SUITE(TestSubscriptions)

BOOST_AUTO_TEST_CASE(Construction)
{
    Subscriptions<> nullary;
    BOOST_CHECK(nullary.empty());
    BOOST_CHECK_EQUAL(nullary.size(), 0);

    Subscriptions<uint8_t> unary;
    BOOST_CHECK(unary.empty());
    BOOST_CHECK_EQUAL(unary.size(), 0);

    Subscriptions<uint8_t, std::string> binary;
    BOOST_CHECK(binary.empty());
    BOOST_CHECK_EQUAL(binary.size(), 0);
}

BOOST_AUTO_TEST_CASE(AddingAndRemoving)
{
    Receiver r;
    Subscriptions<> nullary;
    {
        ScopedConnection c1 = nullary.add(r, &Receiver::r0);
        ScopedConnection c2 = nullary.add([&r](){ r.r0(); });

        BOOST_CHECK(!nullary.empty());
        BOOST_CHECK_EQUAL(nullary.size(), 2);

        c1.disconnect();

        BOOST_CHECK(!nullary.empty());
        BOOST_CHECK_EQUAL(nullary.size(), 1);
    }
    BOOST_CHECK(nullary.empty());
    BOOST_CHECK_EQUAL(nullary.size(), 0);

    Subscriptions<uint8_t> unary;
    {
        ScopedConnection c1 = unary.add(r, &Receiver::r1);
        ScopedConnection c2 = unary.add([&r](const auto& v){ r.r1(v); });

        BOOST_CHECK(!unary.empty());
        BOOST_CHECK_EQUAL(unary.size(), 2);

        c1.disconnect();

        BOOST_CHECK(!unary.empty());
        BOOST_CHECK_EQUAL(unary.size(), 1);
    }
    BOOST_CHECK(unary.empty());
    BOOST_CHECK_EQUAL(unary.size(), 0);

    Subscriptions<uint8_t, std::string> binary;
    {
        ScopedConnection c1 = binary.add(r, &Receiver::r2);
        ScopedConnection c2 = binary.add([&r](const auto& v1, const auto& v2){ r.r2(v1, v2); });

        BOOST_CHECK(!binary.empty());
        BOOST_CHECK_EQUAL(binary.size(), 2);

        c1.disconnect();

        BOOST_CHECK(!binary.empty());
        BOOST_CHECK_EQUAL(binary.size(), 1);
    }
    BOOST_CHECK(binary.empty());
    BOOST_CHECK_EQUAL(binary.size(), 0);
}

BOOST_AUTO_TEST_CASE(Notification)
{
    Receiver r;

    BOOST_CHECK_EQUAL(r.countR0, 0);
    BOOST_CHECK_EQUAL(r.countR1, 0);
    BOOST_CHECK_EQUAL(r.valueR1, 0);
    BOOST_CHECK_EQUAL(r.countR2, 0);
    BOOST_CHECK_EQUAL(r.value1R2, 0);
    BOOST_CHECK_EQUAL(r.value2R2, "");

    Subscriptions<> nullary;
    {
        ScopedConnection c1 = nullary.add(r, &Receiver::r0);
        ScopedConnection c2 = nullary.add([&r](){ r.r0(); });

        nullary.notify();

        BOOST_CHECK_EQUAL(r.countR0, 2);
        BOOST_CHECK_EQUAL(r.countR1, 0);
        BOOST_CHECK_EQUAL(r.valueR1, 0);
        BOOST_CHECK_EQUAL(r.countR2, 0);
        BOOST_CHECK_EQUAL(r.value1R2, 0);
        BOOST_CHECK_EQUAL(r.value2R2, "");

        c1.disconnect();
        nullary.notify();

        BOOST_CHECK_EQUAL(r.countR0, 3);
        BOOST_CHECK_EQUAL(r.countR1, 0);
        BOOST_CHECK_EQUAL(r.valueR1, 0);
        BOOST_CHECK_EQUAL(r.countR2, 0);
        BOOST_CHECK_EQUAL(r.value1R2, 0);
        BOOST_CHECK_EQUAL(r.value2R2, "");
    }

    nullary.notify();

    BOOST_CHECK_EQUAL(r.countR0, 3);
    BOOST_CHECK_EQUAL(r.countR1, 0);
    BOOST_CHECK_EQUAL(r.valueR1, 0);
    BOOST_CHECK_EQUAL(r.countR2, 0);
    BOOST_CHECK_EQUAL(r.value1R2, 0);
    BOOST_CHECK_EQUAL(r.value2R2, "");

    Subscriptions<uint8_t> unary;
    {
        ScopedConnection c1 = unary.add(r, &Receiver::r1);
        ScopedConnection c2 = unary.add([&r](const auto& v){ r.r1(v); });

        unary.notify(42);

        BOOST_CHECK_EQUAL(r.countR0, 3);
        BOOST_CHECK_EQUAL(r.countR1, 2);
        BOOST_CHECK_EQUAL(r.valueR1, 42);
        BOOST_CHECK_EQUAL(r.countR2, 0);
        BOOST_CHECK_EQUAL(r.value1R2, 0);
        BOOST_CHECK_EQUAL(r.value2R2, "");

        c1.disconnect();
        unary.notify(13);

        BOOST_CHECK_EQUAL(r.countR0, 3);
        BOOST_CHECK_EQUAL(r.countR1, 3);
        BOOST_CHECK_EQUAL(r.valueR1, 13);
        BOOST_CHECK_EQUAL(r.countR2, 0);
        BOOST_CHECK_EQUAL(r.value1R2, 0);
        BOOST_CHECK_EQUAL(r.value2R2, "");
    }

    unary.notify(7);

    BOOST_CHECK_EQUAL(r.countR0, 3);
    BOOST_CHECK_EQUAL(r.countR1, 3);
    BOOST_CHECK_EQUAL(r.valueR1, 13);
    BOOST_CHECK_EQUAL(r.countR2, 0);
    BOOST_CHECK_EQUAL(r.value1R2, 0);
    BOOST_CHECK_EQUAL(r.value2R2, "");

    Subscriptions<uint8_t, std::string> binary;
    {
        ScopedConnection c1 = binary.add(r, &Receiver::r2);
        ScopedConnection c2 = binary.add([&r](const auto& v1, const auto& v2){ r.r2(v1, v2); });

        binary.notify(42, "Douglas");

        BOOST_CHECK_EQUAL(r.countR0, 3);
        BOOST_CHECK_EQUAL(r.countR1, 3);
        BOOST_CHECK_EQUAL(r.valueR1, 13);
        BOOST_CHECK_EQUAL(r.countR2, 2);
        BOOST_CHECK_EQUAL(r.value1R2, 42);
        BOOST_CHECK_EQUAL(r.value2R2, "Douglas");

        c1.disconnect();
        binary.notify(21, "Adams");

        BOOST_CHECK_EQUAL(r.countR0, 3);
        BOOST_CHECK_EQUAL(r.countR1, 3);
        BOOST_CHECK_EQUAL(r.valueR1, 13);
        BOOST_CHECK_EQUAL(r.countR2, 3);
        BOOST_CHECK_EQUAL(r.value1R2, 21);
        BOOST_CHECK_EQUAL(r.value2R2, "Adams");
    }

    binary.notify(13, "Devil's Dozen");

    BOOST_CHECK_EQUAL(r.countR0, 3);
    BOOST_CHECK_EQUAL(r.countR1, 3);
    BOOST_CHECK_EQUAL(r.valueR1, 13);
    BOOST_CHECK_EQUAL(r.countR2, 3);
    BOOST_CHECK_EQUAL(r.value1R2, 21);
    BOOST_CHECK_EQUAL(r.value2R2, "Adams");
}

BOOST_AUTO_TEST_SUITE_END()