#include <library/cpp/testing/unittest/registar.h>

#include "actor.h"
#include "queue_in_actor.h"

#include <library/cpp/messagebus/misc/test_sync.h>

#include <util/generic/object_counter.h>
#include <util/system/event.h>

using namespace NActor;

template <typename TThis>
struct TTestActorBase: public TAtomicRefCount<TThis>, public TActor<TThis> {
    TTestSync Started;
    TTestSync Acted;

    TTestActorBase(TExecutor* executor)
        : TActor<TThis>(executor)
    {
    }

    void Act(TDefaultTag) {
        Started.Inc();
        static_cast<TThis*>(this)->Act2();
        Acted.Inc();
    }
};

struct TNopActor: public TTestActorBase<TNopActor> {
    TObjectCounter<TNopActor> AllocCounter;

    TNopActor(TExecutor* executor)
        : TTestActorBase<TNopActor>(executor)
    {
    }

    void Act2() {
    }
};

struct TWaitForSignalActor: public TTestActorBase<TWaitForSignalActor> {
    TWaitForSignalActor(TExecutor* executor)
        : TTestActorBase<TWaitForSignalActor>(executor)
    {
    }

    TSystemEvent WaitFor;

    void Act2() {
        WaitFor.Wait();
    }
};

struct TDecrementAndSendActor: public TTestActorBase<TDecrementAndSendActor>, public TQueueInActor<TDecrementAndSendActor, int> {
    TSystemEvent Done;

    TDecrementAndSendActor* Next;

    TDecrementAndSendActor(TExecutor* executor)
        : TTestActorBase<TDecrementAndSendActor>(executor)
        , Next(nullptr)
    {
    }

    void ProcessItem(TDefaultTag, TDefaultTag, int n) {
        if (n == 0) {
            Done.Signal();
        } else {
            Next->EnqueueAndSchedule(n - 1);
        }
    }

    void Act(TDefaultTag) {
        DequeueAll();
    }
};

struct TObjectCountChecker {
    TObjectCountChecker() {
        CheckCounts();
    }

    ~TObjectCountChecker() {
        CheckCounts();
    }

    void CheckCounts() {
        UNIT_ASSERT_VALUES_EQUAL(TAtomicBase(0), TObjectCounter<TNopActor>::ObjectCount());
        UNIT_ASSERT_VALUES_EQUAL(TAtomicBase(0), TObjectCounter<TWaitForSignalActor>::ObjectCount());
        UNIT_ASSERT_VALUES_EQUAL(TAtomicBase(0), TObjectCounter<TDecrementAndSendActor>::ObjectCount());
    }
};

Y_UNIT_TEST_SUITE(TActor) {
    Y_UNIT_TEST(Simple) {
        TObjectCountChecker objectCountChecker;

        TExecutor executor(4);

        TIntrusivePtr<TNopActor> actor(new TNopActor(&executor));

        actor->Schedule();

        actor->Acted.WaitFor(1u);
    }

    Y_UNIT_TEST(ScheduleAfterStart) {
        TObjectCountChecker objectCountChecker;

        TExecutor executor(4);

        TIntrusivePtr<TWaitForSignalActor> actor(new TWaitForSignalActor(&executor));

        actor->Schedule();

        actor->Started.WaitFor(1);

        actor->Schedule();

        actor->WaitFor.Signal();

        // make sure Act is called second time
        actor->Acted.WaitFor(2u);
    }

    void ComplexImpl(int queueSize, int actorCount) {
        TObjectCountChecker objectCountChecker;

        TExecutor executor(queueSize);

        TVector<TIntrusivePtr<TDecrementAndSendActor>> actors;
        for (int i = 0; i < actorCount; ++i) {
            actors.push_back(new TDecrementAndSendActor(&executor));
        }

        for (int i = 0; i < actorCount; ++i) {
            actors.at(i)->Next = &*actors.at((i + 1) % actorCount);
        }

        for (int i = 0; i < actorCount; ++i) {
            actors.at(i)->EnqueueAndSchedule(10000);
        }

        for (int i = 0; i < actorCount; ++i) {
            actors.at(i)->Done.WaitI();
        }
    }

    Y_UNIT_TEST(ComplexContention) {
        ComplexImpl(4, 6);
    }

    Y_UNIT_TEST(ComplexNoContention) {
        ComplexImpl(6, 4);
    }
}