#include "catch2/catch_test_macros.hpp"

#include "vtr_small_vector.h"
#include <vector>

namespace vtr {

//Must be declared in namespace for argument dependent lookup to work with clang
template<class T>
bool operator==(const std::vector<T>& lhs, const vtr::small_vector<T>& rhs) {
    if (lhs.size() != rhs.size()) return false;

    for (size_t i = 0; i < lhs.size(); ++i) {
        if (lhs[i] != rhs[i]) return false;
    }
    return true;
}

} // namespace vtr

TEST_CASE("Basic", "[vtr_small_vector]") {
    std::vector<int> ref;
    vtr::small_vector<int> vec;

    //Create the vectors the same way
    int i;
    for (i = 0; i < 100; i++) {
        ref.push_back(i);
        vec.push_back(i);

        REQUIRE(ref == vec);
    }

    //Check forward iteration
    auto vec_itr = vec.begin();
    for (auto ref_itr = ref.begin(); ref_itr != ref.end(); ++ref_itr, ++vec_itr) {
        REQUIRE(*ref_itr == *vec_itr);

        int dist = std::distance(ref.begin(), ref_itr);
        REQUIRE(ref[dist] == vec[dist]);
    }

    //Check backward iteration
    auto vec_rev_itr = vec.rbegin();
    for (auto ref_itr = ref.rbegin(); ref_itr != ref.rend(); ++ref_itr, ++vec_rev_itr) {
        REQUIRE(*ref_itr == *vec_rev_itr);
    }

    //Check front/back
    REQUIRE(ref.front() == vec.front());
    REQUIRE(ref.back() == vec.back());

    //Push/Emplace/Pop back
    ref.push_back(i);
    vec.push_back(i);
    REQUIRE(ref == vec);
    ++i;

    ref.emplace_back(i);
    vec.emplace_back(i);
    REQUIRE(ref == vec);
    ++i;

    ref.pop_back();
    vec.pop_back();
    REQUIRE(ref == vec);

    //Test the short (internal storage) transition
    size_t inplace_cap = vtr::small_vector<int>::INPLACE_CAPACITY;
    REQUIRE(inplace_cap > 1);

    //From long to short
    ref.resize(inplace_cap + 1);
    vec.resize(inplace_cap + 1);
    REQUIRE(ref == vec);
    REQUIRE(vec.size() > inplace_cap);

    ref.pop_back();
    vec.pop_back();
    REQUIRE(ref == vec);
    REQUIRE(vec.size() == inplace_cap);

    ref.pop_back();
    vec.pop_back();
    REQUIRE(ref == vec);
    REQUIRE(vec.size() < inplace_cap);

    //From short to long
    ref.push_back(i);
    vec.push_back(i);
    REQUIRE(ref == vec);
    REQUIRE(vec.size() == inplace_cap);
    ++i;

    ref.push_back(i);
    vec.push_back(i);
    REQUIRE(ref == vec);
    REQUIRE(vec.size() > inplace_cap);
    ++i;

#if 0
    //Emplace at position
    auto ref_itr = ref.begin() + ref.size() / 2;
    ref.emplace(ref_itr, i);
    vec_itr = vec.begin() + vec.size() / 2;
    vec.emplace(vec_itr, i);
    i++;
    REQUIRE(ref == vec);
#endif

    //Insert single at position
    auto ref_itr = ref.begin() + ref.size() / 2;
    ref.insert(ref_itr, i);
    vec_itr = vec.begin() + vec.size() / 2;
    vec.insert(vec_itr, i);
    i++;
    REQUIRE(ref == vec);

    //Insert K at position
    int k = 5;
    ref_itr = ref.begin() + ref.size() / 2;
    ref.insert(ref_itr, k, i);
    vec_itr = vec.begin() + vec.size() / 2;
    vec.insert(vec_itr, k, i);
    i++;
    REQUIRE(ref == vec);

    //Range insert
    std::vector<int> range_values = {5, 4, 3, 2, 1};
    ref_itr = ref.begin() + ref.size() / 2;
    ref.insert(ref_itr, range_values.begin(), range_values.end());
#if 0
    vec_itr = vec.begin() + vec.size() / 2;
    vec.insert(vec_itr, range_values.begin(), range_values.end());
    REQUIRE(ref == vec);
#endif

    //Clear
    ref.clear();
    vec.clear();
    REQUIRE(ref == vec);

    //Add after clear
    ref.push_back(i);
    vec.push_back(i);
    REQUIRE(ref == vec);
    ++i;
}
