/*
    libmaus2
    Copyright (C) 2020 German Tischler

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <algorithm>
#include <libmaus2/avl/AVLSet.hpp>
#include <libmaus2/avl/AtomicAVLSet.hpp>
#include <libmaus2/avl/AtomicAVLMap.hpp>
#include <libmaus2/avl/AtomicAVLPtrValueMap.hpp>
#include <libmaus2/avl/AtomicAVLKeyPtrSet.hpp>
#include <cassert>
#include <cstdlib>
#include <set>

void testatomicavlkeyptrset()
{
	typedef libmaus2::avl::AtomicAVLKeyPtrSet<unsigned int> tree_type;
	tree_type AVL;

	AVL.insert(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(1)));
	AVL.check(std::cerr);
	AVL.print(std::cerr);
	AVL.insert(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(3)));
	AVL.check(std::cerr);
	AVL.print(std::cerr);
	AVL.insert(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(2)));
	AVL.check(std::cerr);
	AVL.print(std::cerr);

	AVL.erase(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(2)));
	AVL.check(std::cerr);
	AVL.print(std::cerr);

	AVL.clear();
	AVL.check(std::cerr);
	AVL.print(std::cerr);

	uint64_t const n = 10000;

	std::vector<uint64_t> VT;

	for ( uint64_t i = 0; i < n; ++i )
	// for ( uint64_t i = 0; i < 16; ++i )
	{
		uint64_t const v = ::std::rand();
		AVL.insert(
			libmaus2::util::shared_ptr<unsigned int>(new unsigned int(v))
		);
		VT.push_back(v);

		#if defined(AVL_TREE_DEBUG)
		AVL.check(std::cerr);
		#endif
	}

	std::set<uint64_t> ST(VT.begin(),VT.end());
	std::vector<uint64_t> VU(ST.begin(),ST.end());

	for ( uint64_t i = 0; i < VU.size(); ++i )
	{
		tree_type::const_iterator const it = AVL.find(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(VU[i])));
		assert ( it != AVL.end() && *(it->load()) == VU[i] );
	}

	#if 0
	std::cerr << std::string(80,'-') << std::endl;
	AVL.print(std::cerr);
	#endif

	AVL.check(std::cerr);

	std::vector<uint64_t> V;
	for ( typename tree_type::const_iterator it = AVL.begin(); it != AVL.end(); ++it )
	{
		uint64_t const v = *(it->load());
		std::cerr << v << ";";

		V.push_back(v);
	}
	std::cerr << std::endl;

	// check whether extract sequence is sorted
	for ( uint64_t i = 1; i < V.size(); ++i )
		assert ( V[i-1] < V[i] );

	assert ( V.size() == ST.size() );

	std::vector<uint64_t> VV;
	for ( auto it = AVL.begin(); it != AVL.end(); ++it )
		VV.push_back(*(it->load()));
	assert ( VV == VU );

	std::sort(VT.begin(),VT.end());
	VT.resize(std::unique(VT.begin(),VT.end())-VT.begin());
	assert ( VT == VV );

	// test reverse iterator
	std::vector<uint64_t> R;
	for ( typename tree_type::const_reverse_iterator it = AVL.rbegin(); it != AVL.rend(); ++it )
	{
		uint64_t const v = *(it->load());
		R.push_back(v);
	}

	assert ( R.size() == V.size() );
	for ( uint64_t i = 0; i < R.size(); ++i )
		assert (
			R[i]
			==
			V[V.size()-i-1]
		);

	// test lower bound
	for ( uint64_t i = 0; i < V.size(); ++i )
	{
		// std::cerr << "searching for " << V[i] << std::endl;

		if ( i == 0 )
		{
			if ( V[i] > 0 )
			{
				tree_type::const_iterator it = AVL.lower_bound(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(V[i]-1)));
				assert (
					it != AVL.end()
					&&
					*(it->load()) == V[i]
				);
			}
			else
			{
				typename tree_type::const_iterator it = AVL.lower_bound(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(V[i])));
				assert (
					it != AVL.end()
					&&
					*(it->load()) == V[i]
				);
			}
		}
		else
		{
			if ( V[i] > V[i-1]+1 )
			{
				assert ( V[i] );

				typename tree_type::const_iterator it = AVL.lower_bound(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(V[i]-1)));
				assert (
					it != AVL.end()
					&&
					*(it->load()) == V[i]
				);
			}
			else
			{
				typename tree_type::const_iterator it = AVL.lower_bound(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(V[i])));
				assert (
					it != AVL.end()
					&&
					*(it->load()) == V[i]
				);
			}
		}

		typename tree_type::const_iterator it = AVL.lower_bound(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(V[i])));
		assert ( it != AVL.end() && *(it->load()) == V[i] );
	}

	// test operator[]
	for ( uint64_t i = 0; i < V.size(); ++i )
	{
		typename tree_type::const_iterator it = AVL[i];
		assert ( it != AVL.end() && *(it->load()) == V[i] );
	}

	uint64_t erased = 0;

	std::vector<bool> Verased(V.size(),false);

	while ( erased < V.size() )
	{
		uint64_t const i = ::std::rand() % V.size();

		if ( ! Verased[i] )
		{
			bool const ok = AVL.erase(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(V[i])));

			assert ( ok );

			#if defined(AVL_TREE_DEBUG)
			AVL.check(std::cerr);
			#endif

			Verased[i] = true;
			erased += 1;
		}
	}

	for ( uint64_t i = 0; i < n; ++i )
		AVL.insert(libmaus2::util::shared_ptr<unsigned int>(new unsigned int(VT[i])));

	AVL.clear();

	assert ( AVL.empty() );

}

void testatomicavlptrvaluemap()
{
	libmaus2::avl::AtomicAVLPtrValueMap<unsigned int,double> AVM;

	AVM.insert(1,libmaus2::util::shared_ptr<double>(new double(3.1415)));
	AVM.check(std::cerr);
	AVM.print(std::cerr);
	AVM.insert(0,libmaus2::util::shared_ptr<double>(new double(1.1415)));
	AVM.check(std::cerr);
	AVM.print(std::cerr);
	AVM.insert(5,libmaus2::util::shared_ptr<double>(new double(-4.131)));
	AVM.print(std::cerr);
	AVM.check(std::cerr);

	AVM.erase(1);
	AVM.insert(5,libmaus2::util::shared_ptr<double>(new double(7.234)));
	std::cerr << std::string(80,'-') << std::endl;
	AVM.print(std::cerr);
	AVM.check(std::cerr);

	for ( auto it = AVM.cbegin(); it != AVM.cend(); ++it )
		std::cerr << it->first << "," << *(it->second.load()) << std::endl;

	AVM.clear();

	std::size_t n = 64;
	for ( std::size_t i = 0; i < n; ++i )
	{
		AVM.insert(n-i-1,libmaus2::util::shared_ptr<double>(new double(i)));
		AVM.check(std::cerr);

		for ( std::size_t j = 0; j <= i; ++j )
		{
			auto const it = AVM.find(n-j-1);
			assert ( it != AVM.end() );
			assert ( *(it->second.load()) == j );
		}

		for ( std::size_t j = i+1; j < n; ++j )
		{
			auto const it = AVM.find(n-j-1);
			assert ( it == AVM.end() );
		}
	}

	for ( auto it = AVM.cbegin(); it != AVM.cend(); ++it )
		std::cerr << it->first << "," << *(it->second.load()) << std::endl;
	for ( auto it = AVM.rbegin(); it != AVM.rend(); ++it )
		std::cerr << it->first << "," << *(it->second.load()) << std::endl;

	for ( std::size_t i = 0; i < n; ++i )
	{
		AVM.erase(i);

		for ( std::size_t j = 0; j <= i; ++j )
		{
			auto const it = AVM.find(j);
			assert ( it == AVM.end() );
		}
		for ( std::size_t j = i+1; j < n; ++j )
		{
			auto const it = AVM.find(j);
			assert ( it != AVM.end() );
			assert ( *(it->second.load()) == n-j-1 );
		}
	}

}

void testatomicavlmap()
{
	libmaus2::avl::AtomicAVLMap<unsigned int,double> AVM;

	AVM.insert(1,3.1415);
	AVM.check(std::cerr);
	AVM.print(std::cerr);
	AVM.insert(0,1.1415);
	AVM.check(std::cerr);
	AVM.print(std::cerr);
	AVM.insert(5,-4.131);
	AVM.print(std::cerr);
	AVM.check(std::cerr);

	AVM.erase(1);
	AVM.insert(5,7.234);
	AVM[0] = 2.222;
	AVM.find(0)->second = 3.333;
	std::cerr << std::string(80,'-') << std::endl;
	AVM.print(std::cerr);
	AVM.check(std::cerr);

	for ( auto it = AVM.cbegin(); it != AVM.cend(); ++it )
		std::cerr << it->first << "," << it->second << std::endl;

	AVM.clear();

	std::size_t n = 64;
	for ( std::size_t i = 0; i < n; ++i )
	{
		AVM.insert(n-i-1,i);
		AVM.check(std::cerr);

		for ( std::size_t j = 0; j <= i; ++j )
		{
			auto const it = AVM.find(n-j-1);
			assert ( it != AVM.end() );
			assert ( it->second == j );
		}

		for ( std::size_t j = i+1; j < n; ++j )
		{
			auto const it = AVM.find(n-j-1);
			assert ( it == AVM.end() );
		}
	}

	for ( auto it = AVM.cbegin(); it != AVM.cend(); ++it )
		std::cerr << it->first << "," << it->second << std::endl;
	for ( auto it = AVM.rbegin(); it != AVM.rend(); ++it )
		std::cerr << it->first << "," << it->second << std::endl;

	for ( std::size_t i = 0; i < n; ++i )
	{
		AVM.erase(i);

		for ( std::size_t j = 0; j <= i; ++j )
		{
			auto const it = AVM.find(j);
			assert ( it == AVM.end() );
		}
		for ( std::size_t j = i+1; j < n; ++j )
		{
			auto const it = AVM.find(j);
			assert ( it != AVM.end() );
			assert ( it->second == n-j-1 );
		}
	}

}

template<typename tree_type>
static void runtest()
{
	tree_type AVL;

	uint64_t const n = 10000;

	std::vector<uint64_t> VT;

	for ( uint64_t i = 0; i < n; ++i )
	// for ( uint64_t i = 0; i < 16; ++i )
	{
		uint64_t const v = ::std::rand();
		AVL.insert(v);
		VT.push_back(v);

		#if defined(AVL_TREE_DEBUG)
		AVL.check(std::cerr);
		#endif
	}

	std::set<uint64_t> ST(VT.begin(),VT.end());
	std::vector<uint64_t> VU(ST.begin(),ST.end());

	for ( uint64_t i = 0; i < VU.size(); ++i )
	{
		typename tree_type::const_iterator const it = AVL.find(VU[i]);
		assert ( it != AVL.end() && *it == VU[i] );
	}

	#if 0
	std::cerr << std::string(80,'-') << std::endl;
	AVL.print(std::cerr);
	#endif

	AVL.check(std::cerr);

	std::vector<uint64_t> V;
	for ( typename tree_type::const_iterator it = AVL.begin(); it != AVL.end(); ++it )
	{
		uint64_t const v = *it;
		std::cerr << *it << ";";

		V.push_back(v);
	}
	std::cerr << std::endl;

	// check whether extract sequence is sorted
	for ( uint64_t i = 1; i < V.size(); ++i )
		assert ( V[i-1] < V[i] );

	assert ( V.size() == ST.size() );

	std::vector<uint64_t> const VV(AVL.begin(),AVL.end());
	assert ( VV == VU );

	std::sort(VT.begin(),VT.end());
	VT.resize(std::unique(VT.begin(),VT.end())-VT.begin());
	assert ( VT == VV );

	// test reverse iterator
	std::vector<uint64_t> R;
	for ( typename tree_type::const_reverse_iterator it = AVL.rbegin(); it != AVL.rend(); ++it )
	{
		uint64_t const v = *it;
		R.push_back(v);
	}

	assert ( R.size() == V.size() );
	for ( uint64_t i = 0; i < R.size(); ++i )
		assert (
			R[i]
			==
			V[V.size()-i-1]
		);

	// test lower bound
	for ( uint64_t i = 0; i < V.size(); ++i )
	{
		// std::cerr << "searching for " << V[i] << std::endl;

		if ( i == 0 )
		{
			if ( V[i] > 0 )
			{
				typename tree_type::const_iterator it = AVL.lower_bound(V[i]-1);
				assert (
					it != AVL.end()
					&&
					*it == V[i]
				);
			}
			else
			{
				typename tree_type::const_iterator it = AVL.lower_bound(V[i]);
				assert (
					it != AVL.end()
					&&
					*it == V[i]
				);
			}
		}
		else
		{
			if ( V[i] > V[i-1]+1 )
			{
				assert ( V[i] );

				typename tree_type::const_iterator it = AVL.lower_bound(V[i]-1);
				assert (
					it != AVL.end()
					&&
					*it == V[i]
				);
			}
			else
			{
				typename tree_type::const_iterator it = AVL.lower_bound(V[i]);
				assert (
					it != AVL.end()
					&&
					*it == V[i]
				);
			}
		}

		typename tree_type::const_iterator it = AVL.lower_bound(V[i]);
		assert ( it != AVL.end() && *it == V[i] );
	}

	// test operator[]
	for ( uint64_t i = 0; i < V.size(); ++i )
	{
		typename tree_type::const_iterator it = AVL[i];
		assert ( it != AVL.end() && *it == V[i] );
	}

	uint64_t erased = 0;

	std::vector<bool> Verased(V.size(),false);

	while ( erased < V.size() )
	{
		uint64_t const i = ::std::rand() % V.size();

		if ( ! Verased[i] )
		{
			bool const ok = AVL.erase(V[i]);

			assert ( ok );

			#if defined(AVL_TREE_DEBUG)
			AVL.check(std::cerr);
			#endif

			Verased[i] = true;
			erased += 1;
		}
	}

	for ( uint64_t i = 0; i < n; ++i )
		AVL.insert(VT[i]);

	AVL.clear();

	assert ( AVL.empty() );
}

int main()
{
	try
	{
		::std::srand(10);

		runtest< libmaus2::avl::AVLSet<uint64_t> >();
		runtest< libmaus2::avl::AtomicAVLSet<uint64_t> >();
		testatomicavlmap();
		testatomicavlptrvaluemap();
		testatomicavlkeyptrset();

		std::cerr << "[V] testavlset succesful" << std::endl;

		return 0;
	}
	catch(std::exception const & ex)
	{
		std::cerr << ex.what() << std::endl;
		return EXIT_FAILURE;
	}
}
