#include "ojl/bimap.hpp"
#include <string>
#include <cstdint>
#include <vector>
#include <map>
#include <random>

#if defined(TESTS_CATCH2_USE_OLD_HEADER)
#    include <catch2/catch.hpp>
#else
#    include <catch2/catch_all.hpp>
#endif

using namespace ojl;

TEST_CASE("Bimap basic type instances", "[bimap, instantiation]")
{
    bimap<int, int> bm_ii;
    bimap<int, double> bm_id;
    bimap<double, float> bm_df;
    bimap<std::string, float> bm_sf;
    bimap<size_t, std::string> bm_us;
}

bimap<int, std::string>
test_bimap_int_string_1()
{
    bimap<int, std::string> bm;
    bm.insert(1,"1");
    bm.insert(1,"12");
    bm.insert(1,"13");

    bm.insert(2,"2");
    bm.insert(2,"12");
    bm.insert(2,"23");


    bm.insert(3,"13");
    bm.insert(3,"23");

    bm.l_insert(4);
    bm.r_insert("100");
    bm.r_insert("100");
    return bm;
}


bimap<int, int>
test_bimap_int_int_1()
{
    bimap<int, int> bm;
    bm.insert(1,10);
    bm.insert(1,12);
    bm.insert(1,13);

    bm.insert(2,20);
    bm.insert(2,12);
    bm.insert(2,23);


    bm.insert(3,13);
    bm.insert(3,23);

    bm.l_insert(4);
    return bm;
}

template <typename L, typename R>
void
test_counts(bimap<L,R> bm, std::map<L, size_t> l_counts, std::map<R, size_t> r_counts)
{
    for (const auto &[l,count] : l_counts){
        CHECK(bm.l_count(l) == count);
    }
    for (const auto &[r, count] : r_counts){
        CHECK(bm.r_count(r) == count);
    }
}

template <typename L, typename R>
void
ordered_bimap_mill(std::set<L> left_elems, std::set<R> right_elems)
{
    bimap<L,R> bm;
    std::map<L, size_t> l_counts;
    std::map<R, size_t> r_counts;
    //Insert all relations
    for (const auto &l : left_elems){
    	for (const auto &r : right_elems){
	    bm.insert(l,r);
	    l_counts[l]++;
	    r_counts[r]++;
    	    test_counts(bm, l_counts, r_counts);
	}
    }

    // Check that every relation is counted for every element
    for (const auto &l : left_elems){
        CHECK(bm.l_count(l) == right_elems.size());
    }
    for (const auto &r : right_elems){
        CHECK(bm.r_count(r) == left_elems.size());
    }

    test_counts(bm, l_counts, r_counts);
    
    auto all_relations = bm.all_relations_vec();
    for (const auto &rel : all_relations){
	//Erase one relation at a time, 
	bm.erase(rel);
	l_counts[rel.first]--;
	r_counts[rel.second]--;
	test_counts(bm, l_counts, r_counts);
    }
}

template <typename L, typename R>
void
random_order_bimap_mill(std::set<L> left_elems, std::set<R> right_elems, size_t seed = 0)
{
    std::mt19937 rng{seed};
    bimap<L,R> bm;
    std::map<L, size_t> l_counts;
    std::map<R, size_t> r_counts;
    //Insert all relations
    for (const auto &l : left_elems){
    	for (const auto &r : right_elems){
	    bm.insert(l,r);
	    l_counts[l]++;
	    r_counts[r]++;
    	    test_counts(bm, l_counts, r_counts);
	}
    }

    // Check that every relation is counted for every element
    for (const auto &l : left_elems){
        CHECK(bm.l_count(l) == right_elems.size());
    }
    for (const auto &r : right_elems){
        CHECK(bm.r_count(r) == left_elems.size());
    }

    test_counts(bm, l_counts, r_counts);
    
    auto all_relations = bm.all_relations_vec();
    for (size_t s = all_relations.size(); s > 0; s--){
	size_t i = rng() % s;
	auto rel = all_relations[i];
	bm.erase(rel);
	l_counts[rel.first]--;
	r_counts[rel.second]--;
	test_counts(bm, l_counts, r_counts);
	all_relations.erase(all_relations.begin()+i);

    }
}

TEST_CASE("Bimap insert", "[bimap, insert]")
{
    bimap<int, int> bm = test_bimap_int_int_1();
    bimap<int, std::string> bm2 = test_bimap_int_string_1();
}

// To test: bimap erase

// TODO: write a general test of consistency
// A set of conditions that must hold

TEST_CASE("Bimap count", "[bimap, count]")
{
    {
	bimap<int, int> bm = test_bimap_int_int_1();
	using left = bimap<int, int>::left;
	using right = bimap<int, int>::right;

	CHECK(bm.count(left{1}) == 3);
	CHECK(bm.count(left{2}) == 3);
	CHECK(bm.count(left{3}) == 2);
	CHECK(bm.count(left{4}) == 0);
	CHECK(bm.count(left{5}) == 0);

	CHECK(bm.count(right{10}) == 1);
	CHECK(bm.count(right{12}) == 2); 
	CHECK(bm.count(right{13}) == 2);
	CHECK(bm.count(right{20}) == 1); 
	CHECK(bm.count(right{23}) == 2); 
    }
    {
    	bimap<int, std::string> bm = test_bimap_int_string_1();
	using left = bimap<int, std::string>::left;
	using right = bimap<int, std::string>::right;

	CHECK(bm.count(left{1}) == 3);
	CHECK(bm.count(left{2}) == 3);
	CHECK(bm.count(left{3}) == 2);
	CHECK(bm.count(left{4}) == 0);
	CHECK(bm.count(left{5}) == 0);

	CHECK(bm.count(right{"1"}) == 1);
	CHECK(bm.count(right{"12"}) == 2); 
	CHECK(bm.count(right{"13"}) == 2);
	CHECK(bm.count(right{"2"}) == 1); 
	CHECK(bm.count(right{"23"}) == 2); 
	CHECK(bm.count(right{"100"}) == 0); 

    }
}

TEST_CASE("Bimap left map view vectors", "[bimap, mapped_vector, left_view]")
{
    bimap<int, int> bm = test_bimap_int_int_1();

    using left = bimap<int, int>::left;
    using right = bimap<int, int>::right;
    auto v1 = bm.mapped_vector(left{1});

    CHECK(v1.size() == 3);
    CHECK(v1[0] == 10);
    CHECK(v1[1] == 12);
    CHECK(v1[2] == 13);

    auto v2 = bm.mapped_vector(left{2});

    CHECK(v2.size() == 3);
    CHECK(v2[0] == 12);
    CHECK(v2[1] == 20);
    CHECK(v2[2] == 23);

    auto v3 = bm.mapped_vector(left{3});

    CHECK(v3.size() == 2);
    CHECK(v3[0] == 13);
    CHECK(v3[1] == 23);

    auto v4 = bm.mapped_vector(left{4});

    CHECK(v4.size() == 0);


    auto v5 = bm.mapped_vector(left{5});

    CHECK(v5.size() == 0);

    /*
    bimap<int, std::string> bm = test_bimap_int_int();

    auto v1 = bm.mapped_vector(bimap_left_key(1));

    CHECK(v1.size() == 3);
    CHECK(v1[0] == 10);
    CHECK(v1[1] == 12);
    CHECK(v1[2] == 13);

    auto v2 = bm.mapped_vector(bimap_left_key(2));

    CHECK(v2.size() == 3);
    CHECK(v2[0] == 12);
    CHECK(v2[1] == 20);
    CHECK(v2[2] == 23);

    auto v3 = bm.mapped_vector(left{3});

    CHECK(v3.size() == 2);
    CHECK(v3[0] == 13);
    CHECK(v3[1] == 23);

    auto v4 = bm.mapped_vector(left{4});

    CHECK(v4.size() == 0);


    auto v5 = bm.mapped_vector(left{5});

    CHECK(v5.size() == 0);
    */
}

TEST_CASE("Bimap right map view vectors", "[bimap, mapped_vector, right_view]")
{
    bimap<int, int> bm = test_bimap_int_int_1();

    using left = bimap<int, int>::left;
    using right = bimap<int, int>::right;

    auto v10 = bm.mapped_vector(right{10});

    CHECK(v10.size() == 1);
    CHECK(v10[0] == 1);

    auto v12 = bm.mapped_vector(right{12});

    CHECK(v12.size() == 2);
    CHECK(v12[0] == 1);
    CHECK(v12[1] == 2);

    auto v13 = bm.mapped_vector(right{13});
    CHECK(v13.size() == 2);
    CHECK(v13[0] == 1);
    CHECK(v13[1] == 3);

    auto v20 = bm.mapped_vector(right{20});

    CHECK(v20.size() == 1);
    CHECK(v20[0] == 2);

    auto v23 = bm.mapped_vector(right{23});

    CHECK(v23.size() == 2);
    CHECK(v23[0] == 2);
    CHECK(v23[1] == 3);

    auto v00 = bm.mapped_vector(right{0});

    CHECK(v00.size() == 0);

}

//TODO: provide a generator for types that aren't convertible from size_t
template <typename L, typename R>
void
ordered_mill_linear_values(size_t n_left, size_t n_right)
{
    std::set<L> lefts;
    std::set<R> rights;

    for (size_t i = 0; i < n_left; i++){
        lefts.insert(L(i));
    }

    for (size_t i = 0; i < n_right; i++){
        rights.insert(R(i));
    }

    ordered_bimap_mill(lefts, rights);
}

template <typename L, typename R>
void
ordered_mill_random_values(size_t n_left, size_t n_right, size_t seed = 0)
{
    std::mt19937 rng{seed};
    std::set<L> lefts;
    std::set<R> rights;

    for (size_t i = 0; i < n_left; i++){
	size_t set_size = lefts.size();
	while (set_size == lefts.size()){
            lefts.insert(L(rng()));
	}
    }

    for (size_t i = 0; i < n_right; i++){
        size_t set_size = rights.size();
	while (set_size == rights.size()){
            rights.insert(R(rng()));
	}
    }

    ordered_bimap_mill(lefts, rights);
}

template <typename L, typename R>
void
random_order_mill_linear_values(size_t n_left, size_t n_right)
{
    std::set<L> lefts;
    std::set<R> rights;

    for (size_t i = 0; i < n_left; i++){
        lefts.insert(L(i));
    }

    for (size_t i = 0; i < n_right; i++){
        rights.insert(R(i));
    }

    random_order_bimap_mill(lefts, rights);
}

template <typename L, typename R>
void
random_order_mill_random_values(size_t n_left, size_t n_right, size_t seed = 0)
{
    std::mt19937 rng{seed};
    std::set<L> lefts;
    std::set<R> rights;

    for (size_t i = 0; i < n_left; i++){
	size_t set_size = lefts.size();
	while (set_size == lefts.size()){
            lefts.insert(L(rng()));
	}
    }

    for (size_t i = 0; i < n_right; i++){
        size_t set_size = rights.size();
	while (set_size == rights.size()){
            rights.insert(R(rng()));
	}
    }

    random_order_bimap_mill(lefts, rights);
}

size_t n_vals = 50;
TEST_CASE("Bimap<int, int> ordered mill of linear vals", "[bimap, insert, erase, count]")
{
    ordered_mill_linear_values<int, int>(n_vals,n_vals);
}

TEST_CASE("Bimap<int, int> ordered mill of random vals", "[bimap, insert, erase, count]")
{
    ordered_mill_random_values<int, int>(n_vals,n_vals, 313);
}

TEST_CASE("Bimap<int, int> random order mill of linear vals", "[bimap, insert, erase, count]")
{
    random_order_mill_linear_values<int, int>(n_vals,n_vals);
}

TEST_CASE("Bimap<int, int> random order mill of random vals", "[bimap, insert, erase, count]")
{
    random_order_mill_random_values<int, int>(n_vals,n_vals, 313);
}


enum class EnumA {};
enum class EnumB {};

TEST_CASE("Bimap<EnumA, EnumB> ordered mill of linear vals", "[bimap, insert, erase, count]")
{
    ordered_mill_linear_values<EnumA, EnumB>(n_vals,n_vals);
}

TEST_CASE("Bimap<EnumA, EnumB> ordered mill of random vals", "[bimap, insert, erase, count]")
{
    ordered_mill_random_values<EnumA, EnumB>(n_vals,n_vals, 313);
}

TEST_CASE("Bimap<EnumA, EnumB> random order mill of linear vals", "[bimap, insert, erase, count]")
{
    random_order_mill_linear_values<EnumA, EnumB>(n_vals,n_vals);
}

TEST_CASE("Bimap<EnumA, EnumB> random order mill of random vals", "[bimap, insert, erase, count]")
{
    random_order_mill_random_values<EnumA, EnumB>(n_vals,n_vals, 313);
}


//Strings
//TODO: need size_t -> string converters for this
/*
TEST_CASE("Bimap<int, std::string> ordered mill of linear vals", "[bimap, insert, erase, count]")
{
    ordered_mill_linear_values<int, int>(n_vals,n_vals);
}

TEST_CASE("Bimap<int, int> ordered mill of random vals", "[bimap, insert, erase, count]")
{
    ordered_mill_random_values<int, int>(n_vals,n_vals, 313);
}

TEST_CASE("Bimap<int, int> random order mill of linear vals", "[bimap, insert, erase, count]")
{
    random_order_mill_linear_values<int, int>(n_vals,n_vals);
}

TEST_CASE("Bimap<int, int> random order mill of random vals", "[bimap, insert, erase, count]")
{
    random_order_mill_random_values<int, int>(n_vals,n_vals, 313);
}
*/

// Min/max elements
TEST_CASE("Bimap min/max elements", "[bimap, min_element, max_element, min_mapped_element, max_mapped_element]")
{
    CHECK(!"Not implemented");
}

// Complements
TEST_CASE("Bimap complement API", "[bimap, complement]")
{
    {
        auto bm = test_bimap_int_int_1();
        auto complement = bm.complement();
        auto complement_complement = complement.complement();
        CHECK(bm == complement_complement);
    }

    {
        auto bm = test_bimap_int_string_1();
        auto complement = bm.complement();
        auto complement_complement = complement.complement();
        CHECK(bm == complement_complement);
    }
}