#include "test.h"
#include <panda/refcnt.h>
#include <panda/function.h>
#include <panda/intrusive_chain.h>

using std::shared_ptr;

TEST_PREFIX("intrusive chain: ", "[intrusive_chain]");

struct MyPtr : IntrusiveChainNode<MyPtr*> {};

struct MyIPtr : Refcnt, IntrusiveChainNode<iptr<MyIPtr>> {};

struct MySPtr : IntrusiveChainNode<shared_ptr<MySPtr>> {};

struct MyCustom : IntrusiveChainNode<MyCustom*> {
    MyCustom () : p(), n() {}

    MyCustom* p;
    MyCustom* n;
};

MyCustom* intrusive_chain_next (MyCustom* n) { return n->n; }
MyCustom* intrusive_chain_prev (MyCustom* n) { return n->p; }

void intrusive_chain_next (MyCustom* n, MyCustom* v) { n->n = v; }
void intrusive_chain_prev (MyCustom* n, MyCustom* v) { n->p = v; }

template <class T>
struct Test {
    using IC = IntrusiveChain<T>;
    using CIt = typename IC::const_iterator;

    static void check_content (CIt begin, CIt end) {
        CHECK(begin == end);
    }

    template <typename... Args>
    static void check_content (CIt begin, CIt end, const T& v, Args&&... rest) {
        REQUIRE(begin != end);
        CHECK(*begin == v);
        ++begin;
        check_content(begin, end, rest...);
    }

    template <typename... Args>
    static void check_content (const IC& list, Args&&... rest) {
        CHECK(list.size() == sizeof...(Args));
        check_content(list.begin(), list.end(), rest...);
    }

    //checks that elements has been properly removed from chain: next/prev properties must be reset
    static void check_removed (const T& v) {
        CHECK(!intrusive_chain_next(v));
        CHECK(!intrusive_chain_prev(v));
    }

    static void test (function<T()> ctor) {
        T v[10];
        for (int i = 0; i < 10; ++i) v[i] = ctor();

        SECTION("ctor: empty") {
            IC list;
            check_content(list);
            CHECK(list.empty());
        }

        SECTION("push_back") {
            IC list;
            list.push_back(v[0]);
            check_content(list, v[0]);
            list.push_back(v[1]);
            check_content(list, v[0], v[1]);
        }

        SECTION("push_front") {
            IC list;
            list.push_front(v[0]);
            check_content(list, v[0]);
            list.push_front(v[1]);
            check_content(list, v[1], v[0]);
        }

        SECTION("ctor: initializer list") {
            IC list({v[0], v[1], v[2]});
            check_content(list, v[0], v[1], v[2]);
        }

        SECTION("pop_back") {
            IC list({v[0], v[1], v[2]});
            CHECK(list.pop_back());
            check_content(list, v[0], v[1]);
            check_removed(v[2]);
            CHECK(list.pop_back());
            check_content(list, v[0]);
            check_removed(v[1]);
            CHECK(!list.pop_back());
            check_content(list);
            check_removed(v[0]);
        }

        SECTION("pop_front") {
            IC list({v[0], v[1], v[2]});
            CHECK(list.pop_front());
            check_content(list, v[1], v[2]);
            check_removed(v[0]);
            CHECK(list.pop_front());
            check_content(list, v[2]);
            check_removed(v[1]);
            CHECK(!list.pop_front());
            check_content(list);
            check_removed(v[2]);
        }

        SECTION("insert") {
            IC list;
            auto it = list.insert(list.begin(), v[0]);
            check_content(list, v[0]);
            CHECK(*it == v[0]);

            it = list.insert(list.begin(), v[1]);
            check_content(list, v[1], v[0]);
            CHECK(*it == v[1]);

            it = list.insert(list.end(), v[2]);
            check_content(list, v[1], v[0], v[2]);
            CHECK(*it == v[2]);

            --it;
            it = list.insert(it, v[3]);
            check_content(list, v[1], v[3], v[0], v[2]);
            CHECK(*it == v[3]);

            list.insert(T(), v[4]);
            check_content(list, v[1], v[3], v[0], v[2], v[4]);

            list.insert(v[1], v[5]);
            check_content(list, v[5], v[1], v[3], v[0], v[2], v[4]);

            list.insert(v[3], v[6]);
            check_content(list, v[5], v[1], v[6], v[3], v[0], v[2], v[4]);
        }

        SECTION("erase") {
            IC list({v[0], v[1], v[2], v[3]});

            SECTION("at end") {
                auto it = list.erase(list.end());
                check_content(list, v[0], v[1], v[2], v[3]);
                CHECK(it == list.end());
                list.erase(T());
                check_content(list, v[0], v[1], v[2], v[3]);
            }

            SECTION("from head") {
                auto it = list.erase(list.begin());
                check_content(list, v[1], v[2], v[3]);
                CHECK(*it == v[1]);
                check_removed(v[0]);

                it = list.erase(list.begin());
                check_content(list, v[2], v[3]);
                CHECK(*it == v[2]);
                check_removed(v[1]);

                list.erase(v[2]);
                check_content(list, v[3]);
                check_removed(v[2]);

                list.erase(v[3]);
                check_content(list);
                check_removed(v[3]);
            }

            SECTION("from tail") {
                auto pos = list.end(); --pos;
                auto it = list.erase(pos);
                check_content(list, v[0], v[1], v[2]);
                CHECK(it == list.end());
                check_removed(v[3]);

                pos = list.end(); --pos;
                it = list.erase(pos);
                check_content(list, v[0], v[1]);
                CHECK(it == list.end());
                check_removed(v[2]);

                list.erase(v[1]);
                check_content(list, v[0]);
                check_removed(v[1]);

                list.erase(v[0]);
                check_content(list);
                check_removed(v[0]);
            }

            SECTION("from the middle") {
                auto pos = list.begin(); ++pos; ++pos;
                auto it = list.erase(pos);
                check_content(list, v[0], v[1], v[3]);
                CHECK(*it == v[3]);
                check_removed(v[2]);

                pos = list.begin(); ++pos;
                it = list.erase(pos);
                check_content(list, v[0], v[3]);
                CHECK(*it == v[3]);
                check_removed(v[1]);

                list.push_back(v[4]);
                list.push_back(v[5]);

                list.erase(v[3]);
                check_content(list, v[0], v[4], v[5]);
                check_removed(v[3]);

                list.erase(v[4]);
                check_content(list, v[0], v[5]);
                check_removed(v[4]);
            }
            SECTION("element not in the list") {
                auto it = list.erase(v[4]);
                check_content(list, v[0], v[1], v[2], v[3]);
                CHECK(it == list.end());
            }
        }

        SECTION("clear") {
            IC list({v[0], v[1], v[2]});
            list.clear();
            check_content(list);
            CHECK(list.empty());
            check_removed(v[0]);
            check_removed(v[1]);
            check_removed(v[2]);
        }

        SECTION("front/back") {
            IC list;
            CHECK(list.front() == T());
            CHECK(list.back() == T());
            CHECK(list.size() == 0);

            list.push_back(v[0]);
            CHECK(list.front() == v[0]);
            CHECK(list.back() == v[0]);
            CHECK(list.size() == 1);

            list.push_back(v[1]);
            CHECK(list.front() == v[0]);
            CHECK(list.back() == v[1]);
            CHECK(list.size() == 2);

            list.push_back(v[2]);
            CHECK(list.front() == v[0]);
            CHECK(list.back() == v[2]);
            CHECK(list.size() == 3);
        }

    }
};

TEST("pointer type") {
    std::vector<MyPtr*> objs;
    Test<MyPtr*>::test([&]() -> MyPtr* {
        MyPtr* obj = new MyPtr();
        objs.push_back(obj);
        return obj;
    });
    for (auto o : objs) {
        delete o;
    }
}

TEST("iptr type") {
    Test<iptr<MyIPtr>>::test([&]() -> iptr<MyIPtr> {
        return new MyIPtr();
    });
}

TEST("shared_ptr type") {
    Test<shared_ptr<MySPtr>>::test([&]() -> shared_ptr<MySPtr> {
        return shared_ptr<MySPtr>(new MySPtr());
    });
}

TEST("custom type") {
    std::vector<MyCustom*> objs;
    Test<MyCustom*>::test([&]() -> MyCustom* {
        MyCustom* obj = new MyCustom();
        objs.push_back(obj);
        return obj;
    });
    for (auto o : objs) {
        delete o;
    }
}
