#include "ptr.h"
#include "vector.h"
#include "noncopyable.h"

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

#include <util/generic/hash_set.h>
#include <util/generic/is_in.h>
#include <util/system/thread.h>

class TPointerTest: public TTestBase {
    UNIT_TEST_SUITE(TPointerTest);
    UNIT_TEST(TestTypedefs);
    UNIT_TEST(TestSimpleIntrPtr);
    UNIT_TEST(TestHolderPtr);
    UNIT_TEST(TestHolderPtrMoveConstructor);
    UNIT_TEST(TestHolderPtrMoveConstructorInheritance);
    UNIT_TEST(TestHolderPtrMoveAssignment);
    UNIT_TEST(TestHolderPtrMoveAssignmentInheritance);
    UNIT_TEST(TestMakeHolder);
    UNIT_TEST(TestTrulePtr);
    UNIT_TEST(TestAutoToHolder);
    UNIT_TEST(TestCopyPtr);
    UNIT_TEST(TestIntrPtr);
    UNIT_TEST(TestIntrusiveConvertion);
    UNIT_TEST(TestIntrusiveConstConvertion);
    UNIT_TEST(TestIntrusiveConstConstruction);
    UNIT_TEST(TestMakeIntrusive);
    UNIT_TEST(TestCopyOnWritePtr1);
    UNIT_TEST(TestCopyOnWritePtr2);
    UNIT_TEST(TestOperatorBool);
    UNIT_TEST(TestMakeShared);
    UNIT_TEST(TestComparison);
    UNIT_TEST(TestSimpleIntrusivePtrCtorTsan);
    UNIT_TEST(TestRefCountedPtrsInHashSet);
    UNIT_TEST(TestSharedPtrDowncast);
    UNIT_TEST_SUITE_END();

private:
    void TestSimpleIntrusivePtrCtorTsan() {
        struct S: public TAtomicRefCount<S> {
        };

        struct TLocalThread: public ISimpleThread {
            void* ThreadProc() override {
                TSimpleIntrusivePtr<S> ptr;
                return nullptr;
            }
        };

        // Create TSimpleIntrusivePtr in different threads
        // Its constructor should be thread-safe

        TLocalThread t1, t2;

        t1.Start();
        t2.Start();
        t1.Join();
        t2.Join();
    }

    inline void TestTypedefs() {
        TAtomicSharedPtr<int>(new int(1));
        TSimpleSharedPtr<int>(new int(1));
    }

    void TestSimpleIntrPtr();
    void TestHolderPtr();
    void TestHolderPtrMoveConstructor();
    void TestHolderPtrMoveConstructorInheritance();
    void TestHolderPtrMoveAssignment();
    void TestHolderPtrMoveAssignmentInheritance();
    void TestMakeHolder();
    void TestTrulePtr();
    void TestAutoToHolder();
    void TestCopyPtr();
    void TestIntrPtr();
    void TestIntrusiveConvertion();
    void TestIntrusiveConstConvertion();
    void TestIntrusiveConstConstruction();
    void TestMakeIntrusive();
    void TestCopyOnWritePtr1();
    void TestCopyOnWritePtr2();
    void TestOperatorBool();
    void TestMakeShared();
    void TestComparison();
    template <class T, class TRefCountedPtr>
    void TestRefCountedPtrsInHashSetImpl();
    void TestRefCountedPtrsInHashSet();
    void TestSharedPtrDowncast();
};

UNIT_TEST_SUITE_REGISTRATION(TPointerTest);

static int cnt = 0;

class A: public TAtomicRefCount<A> {
public:
    inline A() {
        ++cnt;
    }

    inline A(const A&)
        : TAtomicRefCount<A>(*this)
    {
        ++cnt;
    }

    inline ~A() {
        --cnt;
    }
};

static A* MakeA() {
    return new A();
}

/*
 * test compileability
 */
class B;
static TSimpleIntrusivePtr<B> GetB() {
    throw 1;
}

void Func() {
    TSimpleIntrusivePtr<B> b = GetB();
}

void TPointerTest::TestSimpleIntrPtr() {
    {
        TSimpleIntrusivePtr<A> a1(MakeA());
        TSimpleIntrusivePtr<A> a2(MakeA());
        TSimpleIntrusivePtr<A> a3 = a2;

        a1 = a2;
        a2 = a3;
    }

    UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
}

void TPointerTest::TestHolderPtr() {
    {
        THolder<A> a1(MakeA());
        THolder<A> a2(a1.Release());
    }

    UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
}

THolder<int> CreateInt(int value) {
    THolder<int> res(new int);
    *res = value;
    return res;
}

void TPointerTest::TestHolderPtrMoveConstructor() {
    THolder<int> h = CreateInt(42);
    UNIT_ASSERT_VALUES_EQUAL(*h, 42);
}

void TPointerTest::TestHolderPtrMoveAssignment() {
    THolder<int> h(new int);
    h = CreateInt(42);
    UNIT_ASSERT_VALUES_EQUAL(*h, 42);
}

struct TBase {
    virtual ~TBase() = default;
};

struct TDerived: public TBase {
};

void TPointerTest::TestHolderPtrMoveConstructorInheritance() {
    // compileability test
    THolder<TBase> basePtr(THolder<TDerived>(new TDerived));
}

void TPointerTest::TestHolderPtrMoveAssignmentInheritance() {
    // compileability test
    THolder<TBase> basePtr;
    basePtr = THolder<TDerived>(new TDerived);
}

void TPointerTest::TestMakeHolder() {
    {
        auto ptr = MakeHolder<int>(5);
        UNIT_ASSERT_VALUES_EQUAL(*ptr, 5);
    }
    {
        struct TRec {
            int X, Y;
            TRec()
                : X(1)
                , Y(2)
            {
            }
        };
        THolder<TRec> ptr = MakeHolder<TRec>();
        UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
        UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
    }
    {
        struct TRec {
            int X, Y;
            TRec(int x, int y)
                : X(x)
                , Y(y)
            {
            }
        };
        auto ptr = MakeHolder<TRec>(1, 2);
        UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
        UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
    }
    {
        class TRec {
        private:
            int X_, Y_;

        public:
            TRec(int x, int y)
                : X_(x)
                , Y_(y)
            {
            }

            int GetX() const {
                return X_;
            }
            int GetY() const {
                return Y_;
            }
        };
        auto ptr = MakeHolder<TRec>(1, 2);
        UNIT_ASSERT_VALUES_EQUAL(ptr->GetX(), 1);
        UNIT_ASSERT_VALUES_EQUAL(ptr->GetY(), 2);
    }
}

void TPointerTest::TestTrulePtr() {
    {
        TAutoPtr<A> a1(MakeA());
        TAutoPtr<A> a2(a1);
        a1 = a2;
    }

    UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
}

void TPointerTest::TestAutoToHolder() {
    {
        TAutoPtr<A> a1(MakeA());
        THolder<A> a2(a1);

        UNIT_ASSERT_EQUAL(a1.Get(), nullptr);
        UNIT_ASSERT_VALUES_EQUAL(cnt, 1);
    }

    UNIT_ASSERT_VALUES_EQUAL(cnt, 0);

    {
        TAutoPtr<A> x(new A());
        THolder<const A> y = x;
    }

    UNIT_ASSERT_VALUES_EQUAL(cnt, 0);

    {
        class B1: public A {
        };

        TAutoPtr<B1> x(new B1());
        THolder<A> y = x;
    }

    UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
}

void TPointerTest::TestCopyPtr() {
    TCopyPtr<A> a1(MakeA());
    {
        TCopyPtr<A> a2(MakeA());
        TCopyPtr<A> a3 = a2;
        UNIT_ASSERT_VALUES_EQUAL(cnt, 3);

        a1 = a2;
        a2 = a3;
    }
    UNIT_ASSERT_VALUES_EQUAL(cnt, 1);
    a1.Destroy();

    UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
}

class TOp: public TSimpleRefCount<TOp>, public TNonCopyable {
public:
    static int Cnt;

public:
    TOp() {
        ++Cnt;
    }
    virtual ~TOp() {
        --Cnt;
    }
};

int TOp::Cnt = 0;

class TOp2: public TOp {
public:
    TIntrusivePtr<TOp> Op;

public:
    TOp2(const TIntrusivePtr<TOp>& op)
        : Op(op)
    {
        ++Cnt;
    }
    ~TOp2() override {
        --Cnt;
    }
};

class TOp3 {
public:
    TIntrusivePtr<TOp2> Op2;
};

void Attach(TOp3* op3, TIntrusivePtr<TOp>* op) {
    TIntrusivePtr<TOp2> op2 = new TOp2(*op);
    op3->Op2 = op2.Get();
    *op = op2.Get();
}

void TPointerTest::TestIntrPtr() {
    {
        TIntrusivePtr<TOp> p, p2;
        TOp3 op3;
        {
            TVector<TIntrusivePtr<TOp>> f1;
            {
                TVector<TIntrusivePtr<TOp>> f2;
                f2.push_back(new TOp);
                p = new TOp;
                f2.push_back(p);
                Attach(&op3, &f2[1]);
                f1 = f2;
                UNIT_ASSERT_VALUES_EQUAL(f1[0]->RefCount(), 2);
                UNIT_ASSERT_VALUES_EQUAL(f1[1]->RefCount(), 3);
                UNIT_ASSERT_EQUAL(f1[1].Get(), op3.Op2.Get());
                UNIT_ASSERT_VALUES_EQUAL(op3.Op2->RefCount(), 3);
                UNIT_ASSERT_VALUES_EQUAL(op3.Op2->Op->RefCount(), 2);
                UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 4);
            }
            p2 = p;
        }
        UNIT_ASSERT_VALUES_EQUAL(op3.Op2->RefCount(), 1);
        UNIT_ASSERT_VALUES_EQUAL(op3.Op2->Op->RefCount(), 3);
        UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 3);
    }
    UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 0);
}

namespace NTestIntrusiveConvertion {
    struct TA: public TSimpleRefCount<TA> {
    };
    struct TAA: public TA {
    };
    struct TB: public TSimpleRefCount<TB> {
    };

    void Func(TIntrusivePtr<TA>) {
    }

    void Func(TIntrusivePtr<TB>) {
    }

    void Func(TIntrusiveConstPtr<TA>) {
    }

    void Func(TIntrusiveConstPtr<TB>) {
    }
}

void TPointerTest::TestIntrusiveConvertion() {
    using namespace NTestIntrusiveConvertion;

    TIntrusivePtr<TAA> aa = new TAA;

    UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 1);
    TIntrusivePtr<TA> a = aa;
    UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 2);
    UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 2);
    aa.Reset();
    UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 1);

    // test that Func(TIntrusivePtr<TB>) doesn't participate in overload resolution
    Func(aa);
}

void TPointerTest::TestIntrusiveConstConvertion() {
    using namespace NTestIntrusiveConvertion;

    TIntrusiveConstPtr<TAA> aa = new TAA;

    UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 1);
    TIntrusiveConstPtr<TA> a = aa;
    UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 2);
    UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 2);
    aa.Reset();
    UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 1);

    // test that Func(TIntrusiveConstPtr<TB>) doesn't participate in overload resolution
    Func(aa);
}

void TPointerTest::TestMakeIntrusive() {
    {
        UNIT_ASSERT_VALUES_EQUAL(0, TOp::Cnt);
        auto p = MakeIntrusive<TOp>();
        UNIT_ASSERT_VALUES_EQUAL(1, p->RefCount());
        UNIT_ASSERT_VALUES_EQUAL(1, TOp::Cnt);
    }
    UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 0);
}

void TPointerTest::TestCopyOnWritePtr1() {
    using TPtr = TCowPtr<TSimpleSharedPtr<int>>;
    TPtr p1;
    UNIT_ASSERT(!p1.Shared());

    p1.Reset(new int(123));
    UNIT_ASSERT(!p1.Shared());

    {
        TPtr pTmp = p1;

        UNIT_ASSERT(p1.Shared());
        UNIT_ASSERT(pTmp.Shared());
        UNIT_ASSERT_EQUAL(p1.Get(), pTmp.Get());
    }

    UNIT_ASSERT(!p1.Shared());

    TPtr p2 = p1;
    TPtr p3;
    p3 = p2;

    UNIT_ASSERT(p2.Shared());
    UNIT_ASSERT(p3.Shared());
    UNIT_ASSERT_EQUAL(p1.Get(), p2.Get());
    UNIT_ASSERT_EQUAL(p1.Get(), p3.Get());

    *(p1.Mutable()) = 456;

    UNIT_ASSERT(!p1.Shared());
    UNIT_ASSERT(p2.Shared());
    UNIT_ASSERT(p3.Shared());
    UNIT_ASSERT_EQUAL(*p1, 456);
    UNIT_ASSERT_EQUAL(*p2, 123);
    UNIT_ASSERT_EQUAL(*p3, 123);
    UNIT_ASSERT_UNEQUAL(p1.Get(), p2.Get());
    UNIT_ASSERT_EQUAL(p2.Get(), p3.Get());

    p2.Mutable();

    UNIT_ASSERT(!p2.Shared());
    UNIT_ASSERT(!p3.Shared());
    UNIT_ASSERT_EQUAL(*p2, 123);
    UNIT_ASSERT_EQUAL(*p3, 123);
    UNIT_ASSERT_UNEQUAL(p2.Get(), p3.Get());
}

struct X: public TSimpleRefCount<X> {
    inline X(int v = 0)
        : V(v)
    {
    }

    int V;
};

void TPointerTest::TestCopyOnWritePtr2() {
    using TPtr = TCowPtr<TIntrusivePtr<X>>;
    TPtr p1;
    UNIT_ASSERT(!p1.Shared());

    p1.Reset(new X(123));
    UNIT_ASSERT(!p1.Shared());

    {
        TPtr pTmp = p1;

        UNIT_ASSERT(p1.Shared());
        UNIT_ASSERT(pTmp.Shared());
        UNIT_ASSERT_EQUAL(p1.Get(), pTmp.Get());
    }

    UNIT_ASSERT(!p1.Shared());

    TPtr p2 = p1;
    TPtr p3;
    p3 = p2;

    UNIT_ASSERT(p2.Shared());
    UNIT_ASSERT(p3.Shared());
    UNIT_ASSERT_EQUAL(p1.Get(), p2.Get());
    UNIT_ASSERT_EQUAL(p1.Get(), p3.Get());

    p1.Mutable()->V = 456;

    UNIT_ASSERT(!p1.Shared());
    UNIT_ASSERT(p2.Shared());
    UNIT_ASSERT(p3.Shared());
    UNIT_ASSERT_EQUAL(p1->V, 456);
    UNIT_ASSERT_EQUAL(p2->V, 123);
    UNIT_ASSERT_EQUAL(p3->V, 123);
    UNIT_ASSERT_UNEQUAL(p1.Get(), p2.Get());
    UNIT_ASSERT_EQUAL(p2.Get(), p3.Get());

    p2.Mutable();

    UNIT_ASSERT(!p2.Shared());
    UNIT_ASSERT(!p3.Shared());
    UNIT_ASSERT_EQUAL(p2->V, 123);
    UNIT_ASSERT_EQUAL(p3->V, 123);
    UNIT_ASSERT_UNEQUAL(p2.Get(), p3.Get());
}

namespace {
    template <class TFrom, class TTo>
    struct TImplicitlyCastable {
        struct RTYes {
            char t[2];
        };

        using RTNo = char;

        static RTYes Func(TTo);
        static RTNo Func(...);
        static TFrom Get();

        /*
         * Result == (TFrom could be converted to TTo implicitly)
         */
        enum {
            Result = (sizeof(Func(Get())) != sizeof(RTNo))
        };
    };

    struct TImplicitlyCastableToBool {
        inline operator bool() const {
            return true;
        }
    };

}

void TPointerTest::TestOperatorBool() {
    using TVec = TVector<ui32>;

    // to be sure TImplicitlyCastable works as expected
    UNIT_ASSERT((TImplicitlyCastable<int, bool>::Result));
    UNIT_ASSERT((TImplicitlyCastable<double, int>::Result));
    UNIT_ASSERT((TImplicitlyCastable<int*, void*>::Result));
    UNIT_ASSERT(!(TImplicitlyCastable<void*, int*>::Result));
    UNIT_ASSERT((TImplicitlyCastable<TImplicitlyCastableToBool, bool>::Result));
    UNIT_ASSERT((TImplicitlyCastable<TImplicitlyCastableToBool, int>::Result));
    UNIT_ASSERT((TImplicitlyCastable<TImplicitlyCastableToBool, ui64>::Result));
    UNIT_ASSERT(!(TImplicitlyCastable<TImplicitlyCastableToBool, void*>::Result));

    // pointers
    UNIT_ASSERT(!(TImplicitlyCastable<TSimpleSharedPtr<TVec>, int>::Result));
    UNIT_ASSERT(!(TImplicitlyCastable<TAutoPtr<ui64>, ui64>::Result));
    UNIT_ASSERT(!(TImplicitlyCastable<THolder<TVec>, bool>::Result)); // even this

    {
        // mostly a compilability test

        THolder<TVec> a;
        UNIT_ASSERT(!a);
        UNIT_ASSERT(!bool(a));
        if (a) {
            UNIT_ASSERT(false);
        }
        if (!a) {
            UNIT_ASSERT(true);
        }

        a.Reset(new TVec);
        UNIT_ASSERT(a);
        UNIT_ASSERT(bool(a));
        if (a) {
            UNIT_ASSERT(true);
        }
        if (!a) {
            UNIT_ASSERT(false);
        }

        THolder<TVec> b(new TVec);
        UNIT_ASSERT(a.Get() != b.Get());
        UNIT_ASSERT(a != b);
        if (a == b) {
            UNIT_ASSERT(false);
        }
        if (a != b) {
            UNIT_ASSERT(true);
        }
        if (!(a && b)) {
            UNIT_ASSERT(false);
        }
        if (a && b) {
            UNIT_ASSERT(true);
        }

        // int i = a;          // does not compile
        // bool c = (a < b);   // does not compile
    }
}

void TPointerTest::TestMakeShared() {
    {
        TSimpleSharedPtr<int> ptr = MakeSimpleShared<int>(5);
        UNIT_ASSERT_VALUES_EQUAL(*ptr, 5);
    }
    {
        struct TRec {
            int X, Y;
            TRec()
                : X(1)
                , Y(2)
            {
            }
        };
        auto ptr = MakeAtomicShared<TRec>();
        UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
        UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
    }
    {
        struct TRec {
            int X, Y;
        };
        TAtomicSharedPtr<TRec> ptr = MakeAtomicShared<TRec>(1, 2);
        UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
        UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
    }
    {
        class TRec {
        private:
            int X_, Y_;

        public:
            TRec(int x, int y)
                : X_(x)
                , Y_(y)
            {
            }

            int GetX() const {
                return X_;
            }
            int GetY() const {
                return Y_;
            }
        };
        TSimpleSharedPtr<TRec> ptr = MakeSimpleShared<TRec>(1, 2);
        UNIT_ASSERT_VALUES_EQUAL(ptr->GetX(), 1);
        UNIT_ASSERT_VALUES_EQUAL(ptr->GetY(), 2);
    }
    {
        enum EObjectState {
            OS_NOT_CREATED,
            OS_CREATED,
            OS_DESTROYED,
        };

        struct TObject {
            EObjectState& State;

            TObject(EObjectState& state)
                : State(state)
            {
                State = OS_CREATED;
            }

            ~TObject() {
                State = OS_DESTROYED;
            }
        };

        auto throwsException = []() {
            throw yexception();
            return 5;
        };

        auto testFunction = [](TSimpleSharedPtr<TObject>, int) {
        };

        EObjectState state = OS_NOT_CREATED;
        try {
            testFunction(MakeSimpleShared<TObject>(state), throwsException());
        } catch (yexception&) {
        }

        UNIT_ASSERT(state == OS_NOT_CREATED || state == OS_DESTROYED);
    }
}

template <class TPtr>
void TestPtrComparison(const TPtr& ptr) {
    UNIT_ASSERT(ptr == ptr);
    UNIT_ASSERT(!(ptr != ptr));
    UNIT_ASSERT(ptr == ptr.Get());
    UNIT_ASSERT(!(ptr != ptr.Get()));
}

void TPointerTest::TestComparison() {
    THolder<A> ptr1(new A);
    TAutoPtr<A> ptr2;
    TSimpleSharedPtr<int> ptr3(new int(6));
    TIntrusivePtr<A> ptr4;
    TIntrusiveConstPtr<A> ptr5 = ptr4;

    UNIT_ASSERT(ptr1 != nullptr);
    UNIT_ASSERT(ptr2 == nullptr);
    UNIT_ASSERT(ptr3 != nullptr);
    UNIT_ASSERT(ptr4 == nullptr);
    UNIT_ASSERT(ptr5 == nullptr);

    TestPtrComparison(ptr1);
    TestPtrComparison(ptr2);
    TestPtrComparison(ptr3);
    TestPtrComparison(ptr4);
    TestPtrComparison(ptr5);
}

template <class T, class TRefCountedPtr>
void TPointerTest::TestRefCountedPtrsInHashSetImpl() {
    THashSet<TRefCountedPtr> hashSet;
    TRefCountedPtr p1(new T());
    UNIT_ASSERT(!IsIn(hashSet, p1));
    UNIT_ASSERT(hashSet.insert(p1).second);
    UNIT_ASSERT(IsIn(hashSet, p1));
    UNIT_ASSERT_VALUES_EQUAL(hashSet.size(), 1);
    UNIT_ASSERT(!hashSet.insert(p1).second);

    TRefCountedPtr p2(new T());
    UNIT_ASSERT(!IsIn(hashSet, p2));
    UNIT_ASSERT(hashSet.insert(p2).second);
    UNIT_ASSERT(IsIn(hashSet, p2));
    UNIT_ASSERT_VALUES_EQUAL(hashSet.size(), 2);
}

struct TCustomIntrusivePtrOps: TDefaultIntrusivePtrOps<A> {
};

struct TCustomDeleter: TDelete {
};

struct TCustomCounter: TSimpleCounter {
    using TSimpleCounterTemplate::TSimpleCounterTemplate;
};

void TPointerTest::TestRefCountedPtrsInHashSet() {
    // test common case
    TestRefCountedPtrsInHashSetImpl<TString, TSimpleSharedPtr<TString>>();
    TestRefCountedPtrsInHashSetImpl<TString, TAtomicSharedPtr<TString>>();
    TestRefCountedPtrsInHashSetImpl<A, TIntrusivePtr<A>>();
    TestRefCountedPtrsInHashSetImpl<A, TIntrusiveConstPtr<A>>();

    // test with custom ops
    TestRefCountedPtrsInHashSetImpl<TString, TSharedPtr<TString, TCustomCounter, TCustomDeleter>>();
    TestRefCountedPtrsInHashSetImpl<A, TIntrusivePtr<A, TCustomIntrusivePtrOps>>();
    TestRefCountedPtrsInHashSetImpl<A, TIntrusiveConstPtr<A, TCustomIntrusivePtrOps>>();
}

class TRefCountedWithStatistics: public TNonCopyable {
public:
    struct TExternalCounter {
        std::atomic<size_t> Counter{0};
        std::atomic<size_t> Increments{0};
    };

    TRefCountedWithStatistics(TExternalCounter& cnt)
        : ExternalCounter_(cnt)
    {
        // Reset counters
        ExternalCounter_.Counter.store(0);
        ExternalCounter_.Increments.store(0);
    }

    void Ref() noexcept {
        ++ExternalCounter_.Counter;
        ++ExternalCounter_.Increments;
    }

    void UnRef() noexcept {
        if (--ExternalCounter_.Counter == 0) {
            TDelete::Destroy(this);
        }
    }

    void DecRef() noexcept {
        Y_ABORT_UNLESS(--ExternalCounter_.Counter != 0);
    }

private:
    TExternalCounter& ExternalCounter_;
};

void TPointerTest::TestIntrusiveConstConstruction() {
    {
        TRefCountedWithStatistics::TExternalCounter cnt;
        UNIT_ASSERT_VALUES_EQUAL(cnt.Counter.load(), 0);
        UNIT_ASSERT_VALUES_EQUAL(cnt.Increments.load(), 0);
        TIntrusivePtr<TRefCountedWithStatistics> i{MakeIntrusive<TRefCountedWithStatistics>(cnt)};
        UNIT_ASSERT_VALUES_EQUAL(cnt.Counter.load(), 1);
        UNIT_ASSERT_VALUES_EQUAL(cnt.Increments.load(), 1);
        i.Reset();
        UNIT_ASSERT_VALUES_EQUAL(cnt.Counter.load(), 0);
        UNIT_ASSERT_VALUES_EQUAL(cnt.Increments.load(), 1);
    }
    {
        TRefCountedWithStatistics::TExternalCounter cnt;
        UNIT_ASSERT_VALUES_EQUAL(cnt.Counter.load(), 0);
        UNIT_ASSERT_VALUES_EQUAL(cnt.Increments.load(), 0);
        TIntrusiveConstPtr<TRefCountedWithStatistics> c{MakeIntrusive<TRefCountedWithStatistics>(cnt)};
        UNIT_ASSERT_VALUES_EQUAL(cnt.Counter.load(), 1);
        UNIT_ASSERT_VALUES_EQUAL(cnt.Increments.load(), 1);
        c.Reset();
        UNIT_ASSERT_VALUES_EQUAL(cnt.Counter.load(), 0);
        UNIT_ASSERT_VALUES_EQUAL(cnt.Increments.load(), 1);
    }
}

class TVirtualProbe: public NTesting::TProbe {
public:
    using NTesting::TProbe::TProbe;

    virtual ~TVirtualProbe() = default;
};

class TDerivedProbe: public TVirtualProbe {
public:
    using TVirtualProbe::TVirtualProbe;
};

class TDerivedProbeSibling: public TVirtualProbe {
public:
    using TVirtualProbe::TVirtualProbe;
};

void TPointerTest::TestSharedPtrDowncast() {
    {
        NTesting::TProbeState probeState = {};

        {
            TSimpleSharedPtr<TVirtualProbe> base = MakeSimpleShared<TDerivedProbe>(&probeState);
            UNIT_ASSERT_VALUES_EQUAL(probeState.Constructors, 1);

            {
                auto derived = base.As<TDerivedProbe>();
                UNIT_ASSERT_VALUES_EQUAL(probeState.Constructors, 1);

                UNIT_ASSERT_VALUES_EQUAL(base.Get(), derived.Get());
                UNIT_ASSERT_VALUES_EQUAL(base.ReferenceCounter(), derived.ReferenceCounter());

                UNIT_ASSERT_VALUES_EQUAL(base.RefCount(), 2l);
                UNIT_ASSERT_VALUES_EQUAL(derived.RefCount(), 2l);
            }

            UNIT_ASSERT_VALUES_EQUAL(probeState.Destructors, 0);
        }

        UNIT_ASSERT_VALUES_EQUAL(probeState.Destructors, 1);
    }
    {
        NTesting::TProbeState probeState = {};

        {
            TSimpleSharedPtr<TVirtualProbe> base = MakeSimpleShared<TDerivedProbe>(&probeState);
            UNIT_ASSERT_VALUES_EQUAL(probeState.Constructors, 1);

            auto derived = std::move(base).As<TDerivedProbe>();
            UNIT_ASSERT_VALUES_EQUAL(probeState.Constructors, 1);
            UNIT_ASSERT_VALUES_EQUAL(probeState.CopyConstructors, 0);
            UNIT_ASSERT_VALUES_EQUAL(probeState.Destructors, 0);
        }

        UNIT_ASSERT_VALUES_EQUAL(probeState.Destructors, 1);
    }
    {
        NTesting::TProbeState probeState = {};

        {
            TSimpleSharedPtr<TVirtualProbe> base = MakeSimpleShared<TDerivedProbe>(&probeState);
            UNIT_ASSERT_VALUES_EQUAL(probeState.Constructors, 1);

            {
                auto derivedSibling = base.As<TDerivedProbeSibling>();
                UNIT_ASSERT_VALUES_EQUAL(probeState.Constructors, 1);

                UNIT_ASSERT_VALUES_EQUAL(derivedSibling.Get(), nullptr);
                UNIT_ASSERT_VALUES_UNEQUAL(base.ReferenceCounter(), derivedSibling.ReferenceCounter());

                UNIT_ASSERT_VALUES_EQUAL(base.RefCount(), 1l);
                UNIT_ASSERT_VALUES_EQUAL(derivedSibling.RefCount(), 0l);
            }

            UNIT_ASSERT_VALUES_EQUAL(probeState.Destructors, 0);
        }

        UNIT_ASSERT_VALUES_EQUAL(probeState.Destructors, 1);
    }
    {
        NTesting::TProbeState probeState = {};

        {
            TSimpleSharedPtr<TVirtualProbe> base = MakeSimpleShared<TDerivedProbe>(&probeState);
            UNIT_ASSERT_VALUES_EQUAL(probeState.Constructors, 1);

            auto derived = std::move(base).As<TDerivedProbeSibling>();
            UNIT_ASSERT_VALUES_EQUAL(derived.Get(), nullptr);
            UNIT_ASSERT_VALUES_EQUAL(probeState.Constructors, 1);
            UNIT_ASSERT_VALUES_EQUAL(probeState.CopyConstructors, 0);
            UNIT_ASSERT_VALUES_EQUAL(probeState.Destructors, 0);
        }

        UNIT_ASSERT_VALUES_EQUAL(probeState.Destructors, 1);
    }
}