#include "tests/RingBufferTests.hpp"

#include "data/RingBuffer.hpp"
#include "test/Test.hpp"

struct Tester final {
    static int ids;
    static int sum;
    int id;

    Tester() : id(++ids) {
        sum += id;
    }

    Tester(const Tester&) : id(++ids) {
        sum += id;
    }

    Tester(Tester&&) : id(++ids) {
        sum += id;
    }

    Tester& operator=(const Tester&) {
        return *this;
    }

    Tester& operator=(Tester&&) {
        return *this;
    }

    ~Tester() {
        sum -= id;
    }

    template<typename String>
    check_return Core::Error toString(String& s) const {
        return s.append(id);
    }
};

int Tester::ids = 0;
int Tester::sum = 0;

static void resetTester() {
    Tester::ids = 0;
    Tester::sum = 0;
}

static void testReadAndWrite() {
    Core::RingBuffer<int, 5> buffer;
    CORE_TEST_FALSE(buffer.canRemove());
    CORE_TEST_EQUAL(0, buffer.getLength());
    CORE_TEST_ERROR(buffer.add(4));
    CORE_TEST_EQUAL(1, buffer.getLength());
    CORE_TEST_TRUE(buffer.canRemove());
    CORE_TEST_EQUAL(4, buffer[0]);
    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_FALSE(buffer.canRemove());
    CORE_TEST_EQUAL(0, buffer.getLength());
}

static void testOverflow() {
    Core::RingBuffer<int, 3> buffer;
    CORE_TEST_ERROR(buffer.add(1));
    CORE_TEST_ERROR(buffer.add(2));
    CORE_TEST_ERROR(buffer.add(3));
    CORE_TEST_EQUAL(Core::Error::CAPACITY_REACHED, buffer.add(4));
    CORE_TEST_EQUAL(Core::Error::CAPACITY_REACHED, buffer.add(5));
    CORE_TEST_EQUAL(3, buffer.getLength());
    CORE_TEST_EQUAL(1, buffer[0]);
    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_EQUAL(2, buffer.getLength());
    CORE_TEST_EQUAL(2, buffer[0]);
    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_EQUAL(1, buffer.getLength());
    CORE_TEST_EQUAL(3, buffer[0]);
    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_FALSE(buffer.canRemove());
    CORE_TEST_EQUAL(0, buffer.getLength());
}

static void testRefill() {
    Core::RingBuffer<int, 3> buffer;
    CORE_TEST_ERROR(buffer.add(1));
    CORE_TEST_ERROR(buffer.add(2));
    CORE_TEST_ERROR(buffer.add(3));
    CORE_TEST_EQUAL(Core::Error::CAPACITY_REACHED, buffer.add(4));
    CORE_TEST_EQUAL(3, buffer.getLength());
    CORE_TEST_TRUE(buffer.canRemove());
    CORE_TEST_EQUAL(1, buffer[0]);
    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_EQUAL(2, buffer[0]);
    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_EQUAL(3, buffer[0]);
    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_EQUAL(0, buffer.getLength());

    CORE_TEST_FALSE(buffer.canRemove());
    CORE_TEST_ERROR(buffer.add(5));
    CORE_TEST_ERROR(buffer.add(6));
    CORE_TEST_EQUAL(2, buffer.getLength());
    CORE_TEST_TRUE(buffer.canRemove());
    CORE_TEST_EQUAL(5, buffer[0]);
    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_EQUAL(6, buffer[0]);
    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_FALSE(buffer.canRemove());
    CORE_TEST_EQUAL(0, buffer.getLength());
}

static void testClear() {
    Core::RingBuffer<int, 3> buffer;
    CORE_TEST_ERROR(buffer.add(1));
    CORE_TEST_ERROR(buffer.add(2));
    CORE_TEST_EQUAL(2, buffer.getLength());
    buffer.clear();
    CORE_TEST_FALSE(buffer.canRemove());
    CORE_TEST_EQUAL(0, buffer.getLength());
}

static void testConstructDestruct() {
    resetTester();
    Core::RingBuffer<Tester, 3> buffer;
    CORE_TEST_ERROR(buffer.add());
    CORE_TEST_EQUAL(1, Tester::sum);
    CORE_TEST_ERROR(buffer.add());
    CORE_TEST_EQUAL(3, Tester::sum);
    CORE_TEST_ERROR(buffer.add());
    CORE_TEST_EQUAL(6, Tester::sum);

    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_EQUAL(5, Tester::sum);
    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_EQUAL(3, Tester::sum);
    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_EQUAL(0, Tester::sum);
}

static void testCopyDestruct() {
    resetTester();
    {
        Core::RingBuffer<Tester, 3> buffer;
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(1, Tester::sum);
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(3, Tester::sum);
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(6, Tester::sum);
        {
            Core::RingBuffer<Tester, 3> copy = buffer;
            CORE_TEST_EQUAL(6 + 4 + 5 + 6, Tester::sum);
        }
        CORE_TEST_EQUAL(6, Tester::sum);
    }
    CORE_TEST_EQUAL(0, Tester::sum);
}

static void testCopyAssignmentDestruct() {
    resetTester();
    {
        Core::RingBuffer<Tester, 3> buffer;
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(1, Tester::sum);
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(3, Tester::sum);
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(6, Tester::sum);
        {
            Core::RingBuffer<Tester, 3> copy;
            copy = buffer;
            CORE_TEST_EQUAL(6 + 4 + 5 + 6, Tester::sum);
        }
        CORE_TEST_EQUAL(6, Tester::sum);
    }
    CORE_TEST_EQUAL(0, Tester::sum);
}

static void testMoveDestruct() {
    resetTester();
    {
        Core::RingBuffer<Tester, 3> buffer;
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(1, Tester::sum);
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(3, Tester::sum);
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(6, Tester::sum);
        {
            Core::RingBuffer<Tester, 3> move = Core::move(buffer);
            CORE_TEST_EQUAL(4 + 5 + 6, Tester::sum);
            CORE_TEST_EQUAL(0, buffer.getLength());
        }
        CORE_TEST_EQUAL(0, Tester::sum);
    }
    CORE_TEST_EQUAL(0, Tester::sum);
}

static void testMoveAssignmentDestruct() {
    resetTester();
    {
        Core::RingBuffer<Tester, 3> buffer;
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(1, Tester::sum);
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(3, Tester::sum);
        CORE_TEST_ERROR(buffer.add());
        CORE_TEST_EQUAL(6, Tester::sum);
        {
            Core::RingBuffer<Tester, 3> move;
            move = Core::move(buffer);
            CORE_TEST_EQUAL(4 + 5 + 6, Tester::sum);
            CORE_TEST_EQUAL(0, buffer.getLength());
        }
        CORE_TEST_EQUAL(0, Tester::sum);
    }
    CORE_TEST_EQUAL(0, Tester::sum);
}

static void testOverall() {
    resetTester();
    Core::RingBuffer<Tester, 3> buffer;

    CORE_TEST_ERROR(buffer.add());
    CORE_TEST_ERROR(buffer.add());
    CORE_TEST_ERROR(buffer.add());
    CORE_TEST_STRING("[1, 2, 3]", buffer);
    CORE_TEST_EQUAL(3, buffer.getLength());
    CORE_TEST_EQUAL(6, Tester::sum);

    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_STRING("[2, 3]", buffer);
    CORE_TEST_EQUAL(2, buffer.getLength());
    CORE_TEST_EQUAL(5, Tester::sum);

    CORE_TEST_ERROR(buffer.add());
    CORE_TEST_STRING("[2, 3, 4]", buffer);
    CORE_TEST_EQUAL(3, buffer.getLength());
    CORE_TEST_EQUAL(9, Tester::sum);

    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_STRING("[3, 4]", buffer);
    CORE_TEST_EQUAL(2, buffer.getLength());
    CORE_TEST_EQUAL(7, Tester::sum);

    CORE_TEST_ERROR(buffer.add());
    CORE_TEST_STRING("[3, 4, 5]", buffer);
    CORE_TEST_EQUAL(3, buffer.getLength());
    CORE_TEST_EQUAL(12, Tester::sum);

    CORE_TEST_ERROR(buffer.remove());
    CORE_TEST_STRING("[4, 5]", buffer);
    CORE_TEST_EQUAL(2, buffer.getLength());
    CORE_TEST_EQUAL(9, Tester::sum);

    buffer.clear();
    CORE_TEST_STRING("[]", buffer);
    CORE_TEST_EQUAL(0, buffer.getLength());
    CORE_TEST_EQUAL(0, Tester::sum);
}

void Core::RingBufferTests::test() {
    testReadAndWrite();
    testOverflow();
    testRefill();
    testClear();
    testConstructDestruct();
    testCopyDestruct();
    testCopyAssignmentDestruct();
    testMoveDestruct();
    testMoveAssignmentDestruct();
    testOverall();
}