#include "deque.h"

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

#include <utility>
#include "yexception.h"

class TDequeTest: public TTestBase {
    UNIT_TEST_SUITE(TDequeTest);
    UNIT_TEST(TestConstructorsAndAssignments);
    UNIT_TEST(TestDeque1);
    UNIT_TEST(TestAt);
    UNIT_TEST(TestInsert);
    UNIT_TEST(TestErase);
    UNIT_TEST(TestAutoRef);
    UNIT_TEST_SUITE_END();

protected:
    void TestConstructorsAndAssignments();
    void TestDeque1();
    void TestInsert();
    void TestErase();
    void TestAt();
    void TestAutoRef();
};

UNIT_TEST_SUITE_REGISTRATION(TDequeTest);

void TDequeTest::TestConstructorsAndAssignments() {
    using container = TDeque<int>;

    container c1;
    c1.push_back(100);
    c1.push_back(200);

    container c2(c1);

    UNIT_ASSERT_VALUES_EQUAL(2, c1.size());
    UNIT_ASSERT_VALUES_EQUAL(2, c2.size());
    UNIT_ASSERT_VALUES_EQUAL(100, c1.at(0));
    UNIT_ASSERT_VALUES_EQUAL(200, c2.at(1));

    container c3(std::move(c1));

    UNIT_ASSERT_VALUES_EQUAL(0, c1.size());
    UNIT_ASSERT_VALUES_EQUAL(2, c3.size());
    UNIT_ASSERT_VALUES_EQUAL(100, c3.at(0));

    c2.push_back(300);
    c3 = c2;

    UNIT_ASSERT_VALUES_EQUAL(3, c2.size());
    UNIT_ASSERT_VALUES_EQUAL(3, c3.size());
    UNIT_ASSERT_VALUES_EQUAL(300, c3.at(2));

    c2.push_back(400);
    c3 = std::move(c2);

    UNIT_ASSERT_VALUES_EQUAL(0, c2.size());
    UNIT_ASSERT_VALUES_EQUAL(4, c3.size());
    UNIT_ASSERT_VALUES_EQUAL(400, c3.at(3));

    int array[] = {2, 3, 4};
    container c4 = {2, 3, 4};
    UNIT_ASSERT_VALUES_EQUAL(c4, container(std::begin(array), std::end(array)));
}

void TDequeTest::TestDeque1() {
    TDeque<int> d;
    UNIT_ASSERT(!d);

    d.push_back(4);
    d.push_back(9);
    d.push_back(16);
    d.push_front(1);

    UNIT_ASSERT(d);

    UNIT_ASSERT(d[0] == 1);
    UNIT_ASSERT(d[1] == 4);
    UNIT_ASSERT(d[2] == 9);
    UNIT_ASSERT(d[3] == 16);

    d.pop_front();
    d[2] = 25;

    UNIT_ASSERT(d[0] == 4);
    UNIT_ASSERT(d[1] == 9);
    UNIT_ASSERT(d[2] == 25);

    //Some compile time tests:
    TDeque<int>::iterator dit = d.begin();
    TDeque<int>::const_iterator cdit(d.begin());

    UNIT_ASSERT((dit - cdit) == 0);
    UNIT_ASSERT((cdit - dit) == 0);
    UNIT_ASSERT((dit - dit) == 0);
    UNIT_ASSERT((cdit - cdit) == 0);
    UNIT_ASSERT(!((dit < cdit) || (dit > cdit) || (dit != cdit) || !(dit <= cdit) || !(dit >= cdit)));
}

void TDequeTest::TestInsert() {
    TDeque<int> d;
    d.push_back(0);
    d.push_back(1);
    d.push_back(2);

    UNIT_ASSERT(d.size() == 3);

    TDeque<int>::iterator dit;

    //Insertion before begin:
    dit = d.insert(d.begin(), 3);
    UNIT_ASSERT(dit != d.end());
    UNIT_ASSERT(*dit == 3);
    UNIT_ASSERT(d.size() == 4);
    UNIT_ASSERT(d[0] == 3);

    //Insertion after begin:
    dit = d.insert(d.begin() + 1, 4);
    UNIT_ASSERT(dit != d.end());
    UNIT_ASSERT(*dit == 4);
    UNIT_ASSERT(d.size() == 5);
    UNIT_ASSERT(d[1] == 4);

    //Insertion at end:
    dit = d.insert(d.end(), 5);
    UNIT_ASSERT(dit != d.end());
    UNIT_ASSERT(*dit == 5);
    UNIT_ASSERT(d.size() == 6);
    UNIT_ASSERT(d[5] == 5);

    //Insertion before last element:
    dit = d.insert(d.end() - 1, 6);
    UNIT_ASSERT(dit != d.end());
    UNIT_ASSERT(*dit == 6);
    UNIT_ASSERT(d.size() == 7);
    UNIT_ASSERT(d[5] == 6);

    //Insertion of several elements before begin
    d.insert(d.begin(), 2, 7);
    UNIT_ASSERT(d.size() == 9);
    UNIT_ASSERT(d[0] == 7);
    UNIT_ASSERT(d[1] == 7);

    //Insertion of several elements after begin
    //There is more elements to insert than elements before insertion position
    d.insert(d.begin() + 1, 2, 8);
    UNIT_ASSERT(d.size() == 11);
    UNIT_ASSERT(d[1] == 8);
    UNIT_ASSERT(d[2] == 8);

    //There is less elements to insert than elements before insertion position
    d.insert(d.begin() + 3, 2, 9);
    UNIT_ASSERT(d.size() == 13);
    UNIT_ASSERT(d[3] == 9);
    UNIT_ASSERT(d[4] == 9);

    //Insertion of several elements at end:
    d.insert(d.end(), 2, 10);
    UNIT_ASSERT(d.size() == 15);
    UNIT_ASSERT(d[14] == 10);
    UNIT_ASSERT(d[13] == 10);

    //Insertion of several elements before last:
    //There is more elements to insert than elements after insertion position
    d.insert(d.end() - 1, 2, 11);
    UNIT_ASSERT(d.size() == 17);
    UNIT_ASSERT(d[15] == 11);
    UNIT_ASSERT(d[14] == 11);

    //There is less elements to insert than elements after insertion position
    d.insert(d.end() - 3, 2, 12);
    UNIT_ASSERT(d.size() == 19);
    UNIT_ASSERT(d[15] == 12);
    UNIT_ASSERT(d[14] == 12);
}

void TDequeTest::TestAt() {
    TDeque<int> d;
    TDeque<int> const& cd = d;

    d.push_back(10);
    UNIT_ASSERT(d.at(0) == 10);
    d.at(0) = 20;
    UNIT_ASSERT(cd.at(0) == 20);
    UNIT_ASSERT_EXCEPTION(d.at(1) = 20, std::out_of_range);
}

void TDequeTest::TestAutoRef() {
    int i;
    TDeque<int> ref;
    for (i = 0; i < 5; ++i) {
        ref.push_back(i);
    }

    TDeque<TDeque<int>> d_d_int(1, ref);
    d_d_int.push_back(d_d_int[0]);
    d_d_int.push_back(ref);
    d_d_int.push_back(d_d_int[0]);
    d_d_int.push_back(d_d_int[0]);
    d_d_int.push_back(ref);

    for (i = 0; i < 5; ++i) {
        UNIT_ASSERT(d_d_int[i] == ref);
    }
}

void TDequeTest::TestErase() {
    TDeque<int> dint;
    dint.push_back(3);
    dint.push_front(2);
    dint.push_back(4);
    dint.push_front(1);
    dint.push_back(5);
    dint.push_front(0);
    dint.push_back(6);

    TDeque<int>::iterator it(dint.begin() + 1);
    UNIT_ASSERT(*it == 1);

    dint.erase(dint.begin());
    UNIT_ASSERT(*it == 1);

    it = dint.end() - 2;
    UNIT_ASSERT(*it == 5);

    dint.erase(dint.end() - 1);
    UNIT_ASSERT(*it == 5);

    dint.push_back(6);
    dint.push_front(0);

    it = dint.begin() + 2;
    UNIT_ASSERT(*it == 2);

    dint.erase(dint.begin(), dint.begin() + 2);
    UNIT_ASSERT(*it == 2);

    it = dint.end() - 3;
    UNIT_ASSERT(*it == 4);

    dint.erase(dint.end() - 2, dint.end());
    UNIT_ASSERT(*it == 4);
}