

#include <iostream>
#include <string>
#include <iomanip>		// std::setw()
#include <iterator>		// std::reverse_iterator
#include <utility>		// std::make_pair
#include <sstream>		// std::stringstream
#include <vector>


// toogle between test ft and stl
//                                *************************
#include <map>
#ifdef STL
	namespace ft = std;
#else
	#include "vector.hpp"
	#include "map.hpp"
	#include "reverse_iterator.hpp"
#endif


// colors
//        *************************************************
# define GRAY "\e[0;30m"
# define RED "\e[0;31m"
# define GREEN "\e[0;32m"
# define YELLOW "\e[0;33m"
# define BLUE "\e[0;34m"
# define PURPLE "\e[0;35m"
# define CYAN "\e[0;36m"
# define WHITE "\e[0;37m"

# define B_GRAY "\e[1;30m"
# define B_RED "\e[1;31m"
# define B_GREEN "\e[1;32m"
# define B_YELLOW "\e[1;33m"
# define B_BLUE "\e[1;34m"
# define B_PURPLE "\e[1;35m"
# define B_CYAN "\e[1;36m"
# define B_WHITE "\e[1;37m"

# define RESET "\e[0m"


// defines
//                  ****************************************
# define TITLE(s)	std::cout << "\n" B_PURPLE #s RESET "\n\n";
# define VAL(n)		val<T>(n)
# define VALT(n)	val<T>(n)
# define VALU(n)	val<U>(n)
# define TOI(n)		toi<T>(n)
# define PRINT(n)	print<>(n, #n);
# define DELETE		delete_structs();


// adding each test to the list
//                              ***************************
#define TEST_V(f_name) \
	template <class T> struct s_ ## f_name : public A_test\
		{ void func(); };\
	void f_name () {\
		add_to_list("", "", NULL);\
		add_to_list(#f_name, "int", new(s_ ## f_name <int>));\
		add_to_list(#f_name, "char", new(s_ ## f_name <char>));\
		add_to_list(#f_name, "std::string", new(s_ ## f_name <std::string>));\
		add_to_list(#f_name, "mystruct*", new(s_ ## f_name <mystruct*>));\
		}\
	template <class T>\
		void s_ ## f_name <T>::func()

#define TEST_M(f_name) \
	template <class T, class U> struct s_ ## f_name : public A_test\
		{ void func(); };\
	void f_name () {\
		add_to_list("", "", NULL);\
		add_to_list(#f_name, "char, int", new(s_ ## f_name <char, int>));\
		add_to_list(#f_name, "char, char", new(s_ ## f_name <char, char>));\
		add_to_list(#f_name, "char, std::string", new(s_ ## f_name <char, std::string>));\
		add_to_list(#f_name, "char, mystruct*", new(s_ ## f_name <char, mystruct*>));\
		add_to_list(#f_name, "int, int", new(s_ ## f_name <int, int>));\
		add_to_list(#f_name, "int, char", new(s_ ## f_name <int, char>));\
		add_to_list(#f_name, "int, std::string", new(s_ ## f_name <int, std::string>));\
		add_to_list(#f_name, "int, mystruct*", new(s_ ## f_name <int, mystruct*>));\
	}\
	template <class T, class U>\
		void s_ ## f_name <T, U>::func()


// structures
//            *********************************************
struct A_test
{
	virtual ~A_test(){};
	std::string		title;
	std::string		type;
	virtual void	func() = 0;
};

struct mystruct {
public:
	mystruct(int data = 0)
		{_val = new int[2]; _val[0] = data; _val[1] = data;}
	~mystruct()
		{delete[] _val;}
	int *	get_data() const
		{return _val;}
private:
	int *	_val;
};
std::ostream &	operator<<(std::ostream & o, mystruct const * rhs) {
	if (rhs != NULL)
		o << (*rhs).get_data()[0] << "," << (*rhs).get_data()[1];
	else
		o << "NULL";
	return (o);
}


// global variables
//                  ***************************************
std::vector< std::vector<A_test*> >	test_list;
std::vector< mystruct* >			mem_list;


// functions utiles
//                  ***************************************
void add_to_list(std::string title, std::string type, A_test* test) {

	std::vector<A_test*>							test_sub_list;
	std::vector< std::vector<A_test*> >::iterator	it;

	// title != NULL for the first element
	if (test == NULL)
	{
		test_list.push_back(test_sub_list);
		return;
	}

	test->title = title;
	test->type = type;
	it = test_list.end() - 1;
	(*it).push_back(test);
}
void	delete_structs() {
	
	std::vector<mystruct*>::iterator it;
	std::vector<mystruct*>::iterator it_end = mem_list.end();

	for (it = mem_list.begin(); it != it_end; ++it)
		delete *it;
	mem_list.clear();
}


// templates print
//                 *****************************************
template <class T>
	void	print(ft::vector<T> vec, std::string name) {

	int i = 0;
	typename ft::vector<T>::iterator it;
	typename ft::vector<T>::iterator it_end = vec.end();

	std::cout << "\n" << name << ":(vector)\n";
	for (it = vec.begin(); it != it_end; ++it, i++)
		std::cout << "[" << i << "]" << *it << " ";
	std::cout << "\nsize:" << vec.size() << " capacty:" << vec.capacity() << "\n";
}
template <class T, class U>
	void	print(ft::map<T, U> mp, std::string name) {

	int i = 0;
	typename ft::map<T, U>::iterator it;
	typename ft::map<T, U>::iterator it_end = mp.end();

	std::cout << "\n" << name << ":(map)\n";
	for (it = mp.begin(); it != it_end; ++it, i++)
		std::cout << "[" << i << "]" << it->first << ":" << it->second << " ";
	std::cout << "\nsize:" << mp.size() << "\n";
}

// templates get value
//                     *************************************
// specialization in header, make it inline :
// https://stackoverflow.com/questions/63529059/c-specialized-method-templates-produce-multiple-definition-errors
template <class T>
	T val(int n) { (void)n; return (T()); }
template <>
	inline	int val(int n) { return (n); }
template <>
	inline	char val(int n) {
	
	if (n <= 126 && n >= 33)
		return n;
	return (n % 94 + 33);
}
template <>
	inline	std::string val(int n) {

	std::string			str;
	std::stringstream	stream;

	stream << n;
	stream >> str;
	stream.clear();
	return (str);
}
template <>
	inline	mystruct* val(int n) {

	mystruct *s = new mystruct(n);
	mem_list.push_back(s);
	return ( s );
}
template <class T>
	T val(std::string str) { (void)str; return (T()); }
template <>
	inline	int val(std::string str) { int i = str[0]; return (val<int>(i)); }
template <>
	inline	char val(std::string str) { int i = str[0]; return (val<char>(i)); }
template <>
	inline	std::string val(std::string str) { return (str); }
template <>
	inline	mystruct* val(std::string str) { int i = str[0]; return (val<mystruct*>(i)); }


// templates to value
//                    **************************************
template <class T>
	int toi(T t) {(void)t; return (0);
}
template <>
	inline	int toi(int i) {return (i);
}
template <>
	inline	int toi(char c) {return (c);
}
template <>
	inline	int toi(std::string str) {

	int					i;
	std::stringstream	stream;

	stream << str;
	stream >> i;
	stream.clear();
	return (i);
}
template <>
	inline	int toi(mystruct* s) {

	return ( s->get_data()[0] );
}


// tests functions
//                 *****************************************
TEST_M(tests_map_operator_assignation)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> first;
	ft::map<T,U> second;

	first[VALT('x')]=VALU(8);
	first[VALT('y')]=VALU(16);
	first[VALT('z')]=VALU(32);

	PRINT(first)
	PRINT(second)

	second=first;                // second now contains 3 ints
	first=ft::map<T,U>();  // and first is now empty

	std::cout << "Size of first: " << first.size() << '\n';
	std::cout << "Size of second: " << second.size() << '\n';

	PRINT(first)
	PRINT(second)

	DELETE
}

TEST_M(tests_map_begin)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;

	mymap[VALT('b')] = VALU(100);
	mymap[VALT('a')] = VALU(200);
	mymap[VALT('c')] = VALU(300);

	PRINT(mymap)

	DELETE
}

TEST_M(tests_map_end)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;

	mymap[VALT('b')] = VALU(100);
	mymap[VALT('a')] = VALU(200);
	mymap[VALT('c')] = VALU(300);

	PRINT(mymap)

	DELETE
}

TEST_M(tests_map_rbegin)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;

	mymap[VALT('x')] = VALU(100);
	mymap[VALT('y')] = VALU(200);
	mymap[VALT('z')] = VALU(300);

	// show content:
	typename ft::map<T,U>::reverse_iterator rit;
	for (rit=mymap.rbegin(); rit!=mymap.rend(); ++rit)
		std::cout << rit->first << " => " << rit->second << '\n';

	DELETE
}

TEST_M(tests_map_rend)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;

	mymap[VALT('x')] = VALU(100);
	mymap[VALT('y')] = VALU(200);
	mymap[VALT('z')] = VALU(300);

	// show content:
	typename ft::map<T,U>::reverse_iterator rit;
	for (rit=mymap.rbegin(); rit!=mymap.rend(); ++rit)
		std::cout << rit->first << " => " << rit->second << '\n';

	DELETE
}

TEST_M(tests_map_empty)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;

	mymap[VALT('a')]=VALU(10);
	mymap[VALT('b')]=VALU(20);
	mymap[VALT('c')]=VALU(30);

	while (!mymap.empty())
	{
		std::cout << mymap.begin()->first << " => " << mymap.begin()->second << '\n';
		mymap.erase(mymap.begin());
	}

	DELETE
}

TEST_M(tests_map_size)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;
	mymap[VALT('a')]=VALU(101);
	mymap[VALT('b')]=VALU(202);
	mymap[VALT('c')]=VALU(302);

	std::cout << "mymap.size() is " << mymap.size() << '\n';

	PRINT(mymap)

	DELETE
}

TEST_M(tests_map_max_size)
{
	// title
	TITLE(cplusplus.com reference)

	int i;
	std::map<T,U> mymap;

	if (mymap.max_size()>1000)
	{
		for (i=0; i<1000; i++) mymap[i]=VALU(0);
		std::cout << "The map contains 1000 elements.\n";
	}
	else std::cout << "The map could not hold 1000 elements.\n";

	DELETE
}

TEST_M(tests_map_operator_access)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;

	mymap[VALT('a')]=VALU("An element");
	mymap[VALT('b')]=VALU("another element");
	mymap[VALT('c')]=mymap[VAL('b')];

	std::cout << "mymap['a'] is " << mymap[VALT('a')] << '\n';
	std::cout << "mymap['b'] is " << mymap[VALT('b')] << '\n';
	std::cout << "mymap['c'] is " << mymap[VALT('c')] << '\n';
	std::cout << "mymap['d'] is " << mymap[VALT('d')] << '\n';

	std::cout << "mymap now contains " << mymap.size() << " elements.\n";

	DELETE
}

TEST_M(tests_map_insert)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;

	// first insert function version (single parameter):
	mymap.insert ( ft::pair<T,U>(VALT('a'),VALU(100)) );
	mymap.insert ( ft::pair<T,U>(VALT('z'),VALU(200)) );

	ft::pair<typename ft::map<T,U>::iterator, bool> ret;
	ret = mymap.insert ( ft::pair<T,U>(VALT('z'),VALU(500)) );
	if (ret.second==false) {
		std::cout << "element 'z' already existed";
		std::cout << " with a value of " << ret.first->second << '\n';
	}


	// second insert function version (with hint position):
	typename ft::map<T,U>::iterator it = mymap.begin();
	mymap.insert (it, ft::pair<T,U>(VALT('b'),VALU(300)));	// max efficiency inserting
	mymap.insert (it, ft::pair<T,U>(VALT('c'),VALU(400)));	// no max efficiency inserting

	// third insert function version (range insertion):
	ft::map<T,U> anothermap;
	anothermap.insert(mymap.begin(),mymap.find('c'));

	PRINT(mymap)
	PRINT(anothermap)

	DELETE
}

TEST_M(tests_map_erase)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;
	typename ft::map<T,U>::iterator it;

	// insert some values:
	mymap[VALT('a')]=VALU(10);
	mymap[VALT('b')]=VALU(20);
	mymap[VALT('c')]=VALU(30);
	mymap[VALT('d')]=VALU(40);
	mymap[VALT('e')]=VALU(50);
	mymap[VALT('f')]=VALU(60);

	it=mymap.find(VALT('b'));
	mymap.erase (it);					// erasing by iterator

	mymap.erase (VALT('c'));			// erasing by key

	it=mymap.find (VALT('e'));
	mymap.erase ( it, mymap.end() );	// erasing by range

	PRINT(mymap)

	DELETE
}

TEST_M(tests_map_swap)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> foo,bar;

	foo[VALT('x')]=VALU(100);
	foo[VALT('y')]=VALU(200);

	bar[VALT('a')]=VALU(11);
	bar[VALT('b')]=VALU(22);
	bar[VALT('c')]=VALU(33);

	foo.swap(bar);

	PRINT(foo)
	PRINT(bar)

	DELETE
}

TEST_M(tests_map_clear)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;

	mymap[VALT('x')]=VALU(100);
	mymap[VALT('y')]=VALU(200);
	mymap[VALT('z')]=VALU(300);

	PRINT(mymap)

	mymap.clear();
	mymap[VALT('a')]=VALU(1101);
	mymap[VALT('b')]=VALU(2202);

	PRINT(mymap)

	DELETE
}

TEST_M(tests_map_key_comp)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;

	typename ft::map<T,U>::key_compare mycomp = mymap.key_comp();

	mymap[VALT('a')]=VALU(100);
	mymap[VALT('b')]=VALU(200);
	mymap[VALT('c')]=VALU(300);

	std::cout << "mymap contains:\n";

	T highest = mymap.rbegin()->first;		// key value of last element

	typename ft::map<T,U>::iterator it = mymap.begin();
	do {
		std::cout << it->first << " => " << it->second << '\n';
	} while ( mycomp((*it++).first, highest) );

	std::cout << '\n';

	DELETE
}

TEST_M(tests_map_value_comp)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;

	mymap[VALT('x')]=VALU(1001);
	mymap[VALT('y')]=VALU(2002);
	mymap[VALT('z')]=VALU(3003);

	std::cout << "mymap contains:\n";

	ft::pair<T,U> highest = *mymap.rbegin();          // last element

	typename ft::map<T,U>::iterator it = mymap.begin();
	do {
		std::cout << it->first << " => " << it->second << '\n';
	} while ( mymap.value_comp()(*it++, highest) );

	DELETE
}

TEST_M(tests_map_find)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;
	typename ft::map<T,U>::iterator it;

	mymap[VALT('a')]=VALU(50);
	mymap[VALT('b')]=VALU(100);
	mymap[VALT('c')]=VALU(150);
	mymap[VALT('d')]=VALU(200);

	it = mymap.find(VALT('b'));
	if (it != mymap.end())
		mymap.erase (it);

	// print content:
	std::cout << "elements in mymap:" << '\n';
	std::cout << "a => " << mymap.find(VALT('a'))->second << '\n';
	std::cout << "c => " << mymap.find(VALT('c'))->second << '\n';
	std::cout << "d => " << mymap.find(VALT('d'))->second << '\n';

	DELETE
}

TEST_M(tests_map_count)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;
	T c;

	mymap [VALT('a')]=VALU(101);
	mymap [VALT('c')]=VALU(202);
	mymap [VALT('f')]=VALU(303);

	// to do this test with T as a 'string' or 'mystruct*' we should add overload
	for (c=VALT('a'); c<VALT('h'); c++)
	{
		std::cout << c;
		if (mymap.count(c)>0)
			std::cout << " is an element of mymap.\n";
		else 
			std::cout << " is not an element of mymap.\n";
	}

	DELETE
}

TEST_M(tests_map_lower_bound)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;
	typename ft::map<T,U>::iterator itlow,itup;

	mymap[VALT('a')]=VALU(20);
	mymap[VALT('b')]=VALU(40);
	mymap[VALT('c')]=VALU(60);
	mymap[VALT('d')]=VALU(80);
	mymap[VALT('e')]=VALU(100);

	itlow=mymap.lower_bound (VALT('b'));  // itlow points to b
	itup=mymap.upper_bound (VALT('d'));   // itup points to e (not d!)

	mymap.erase(itlow,itup);        // erases [itlow,itup)

	PRINT(mymap)

	DELETE
}

TEST_M(tests_map_upper_bound)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;
	typename ft::map<T,U>::iterator itlow,itup;

	mymap[VALT('a')]=VALU(20);
	mymap[VALT('b')]=VALU(40);
	mymap[VALT('c')]=VALU(60);
	mymap[VALT('d')]=VALU(80);
	mymap[VALT('e')]=VALU(100);

	itlow=mymap.lower_bound (VALT('b'));	// itlow points to b
	itup=mymap.upper_bound (VALT('d'));		// itup points to e (not d!)

	mymap.erase(itlow,itup);				// erases [itlow,itup)

	PRINT(mymap)

	DELETE
}

TEST_M(tests_map_equal_range)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> mymap;

	mymap[VALT('a')]=VALU(10);
	mymap[VALT('b')]=VALU(20);
	mymap[VALT('c')]=VALU(30);

	ft::pair<typename ft::map<T,U>::iterator,typename ft::map<T,U>::iterator> ret;
	ret = mymap.equal_range(VALT('b'));

	std::cout << "lower bound points to: ";
	std::cout << ret.first->first << " => " << ret.first->second << '\n';

	std::cout << "upper bound points to: ";
	std::cout << ret.second->first << " => " << ret.second->second << '\n';

	DELETE
}

TEST_M(tests_map_get_allocator)
{
	// title
	TITLE(cplusplus.com reference)

	int psize;
	ft::map<T,U> mymap;
	ft::pair<const T,U>* p;

	// allocate an array of 5 elements using mymap's allocator:
	p=mymap.get_allocator().allocate(5);

	// assign some values to array
	psize = sizeof(typename ft::map<T,U>::value_type)*5;

	std::cout << "The allocated array has a size of " << psize << " bytes.\n";

	mymap.get_allocator().deallocate(p,5);

	DELETE
}

TEST_M(tests_map_relational_operators)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T,U> alice;
	ft::map<T,U>   bob;
	ft::map<T,U>   eve;
	(void)alice;
	(void)bob;
	(void)eve;

	alice[VALT(1)]=VALU('a');
	alice[VALT(2)]=VALU('b');
	alice[VALT(3)]=VALU('c');

	bob[VALT(7)]=VALU('Z');
	bob[VALT(8)]=VALU('Y');
	bob[VALT(9)]=VALU('X');
	bob[VALT(10)]=VALU('W');

	eve[VALT(1)]=VALU('a');
	eve[VALT(2)]=VALU('b');
	eve[VALT(3)]=VALU('c');

	std::cout << std::boolalpha;

	// Compare non equal containers
	std::cout << "alice == bob returns " << (alice == bob) << '\n';
	std::cout << "alice != bob returns " << (alice != bob) << '\n';
	std::cout << "alice <  bob returns " << (alice < bob) << '\n';
	std::cout << "alice <= bob returns " << (alice <= bob) << '\n';
	std::cout << "alice >  bob returns " << (alice > bob) << '\n';
	std::cout << "alice >= bob returns " << (alice >= bob) << '\n';

	std::cout << '\n';

	// Compare equal containers
	std::cout << "alice == eve returns " << (alice == eve) << '\n';
	std::cout << "alice != eve returns " << (alice != eve) << '\n';
	std::cout << "alice <  eve returns " << (alice < eve) << '\n';
	std::cout << "alice <= eve returns " << (alice <= eve) << '\n';
	std::cout << "alice >  eve returns " << (alice > eve) << '\n';
	std::cout << "alice >= eve returns " << (alice >= eve) << '\n';

	DELETE
}


TEST_M(tests_map_swap_non_member)
{
	// title
	TITLE(cplusplus.com reference)

	ft::map<T, U> alice;
	ft::map<T, U> bob;

	alice[VALT(1)]=VALU('a');
	alice[VALT(2)]=VALU('b');
	alice[VALT(3)]=VALU('c');

	bob[VALT(7)]=VALU('Z');
	bob[VALT(8)]=VALU('Y');
	bob[VALT(9)]=VALU('X');
	bob[VALT(10)]=VALU('W');

	// Print state before swap
	PRINT(alice)
	PRINT(bob)

	std::cout << "-- SWAP\n";
	std::swap(alice, bob);

	// Print state after swap
	PRINT(alice)
	PRINT(bob)

	DELETE
}


int	main() {

	tests_map_operator_assignation();
	tests_map_begin();
	tests_map_end();
	tests_map_rbegin();
	tests_map_rend();
	tests_map_empty();
	tests_map_size();
	tests_map_max_size();
	tests_map_operator_access();
	tests_map_insert();
	tests_map_erase();
	tests_map_swap();
	tests_map_clear();
	tests_map_key_comp();
	tests_map_value_comp();
	tests_map_find();
	tests_map_count();
	tests_map_lower_bound();
	tests_map_upper_bound();
	tests_map_equal_range();
	tests_map_get_allocator();
	tests_map_relational_operators();
	tests_map_swap_non_member();


	// execute tests and print them :
	int size = test_list.size();
	int	sub_size;
	for(int i = 0; i < size; i++)
	{
		std::cout << "\n" B_YELLOW "[" << i + 1 << "/" << size << "] "
				  << test_list[i][0]->title << RESET << "\n";
		sub_size = test_list[i].size();
		for (int j = 0; j < sub_size; j++)
		{
			std::cout << "\n" << B_CYAN << "-- " << test_list[i][j]->type
					  << " --" << RESET "\n";
			test_list[i][j]->func();
			delete test_list[i][j];
		}
	}
	std::cout << "\n";

	return 0;
}

