/*
 * A simple unit-test for combined_queue_lock_t.
 */

#include <so_5/disp/mpsc_queue_traits/pub.hpp>

#include <iostream>
#include <thread>
#include <chrono>
#include <vector>

#include <test/3rd_party/various_helpers/time_limited_execution.hpp>

using namespace so_5::disp::mpsc_queue_traits;

void
test_thread(
	lock_factory_t factory,
	unsigned int milliseconds_to_wait )
{
	lock_unique_ptr_t lock_a{ factory() };
	lock_unique_ptr_t lock_b{ factory() };

	unique_lock_t guard_b{ *lock_b };

	std::thread child{ [&lock_a, &lock_b] {
			unique_lock_t a{ *lock_a };
			{
				// Informs parent that child has started.
				lock_guard_t b{ *lock_b };
				b.notify_one();
			}
			// Try to wait on lock_a.
			a.wait_for_notify();

			{
				// Informs parent that child has started.
				lock_guard_t b{ *lock_b };
				b.notify_one();
			}
		} };

	guard_b.wait_for_notify();
	
	// Acquire lock_a and make child thread to wait for some time.
	{
		lock_guard_t guard_a{ *lock_a };
		std::this_thread::sleep_for(
				std::chrono::milliseconds{ milliseconds_to_wait } );
		guard_a.notify_one();
	}

	// Wait for notify again.
	guard_b.wait_for_notify();

	child.join();
}

void
do_pair_thread_test_case(
	lock_factory_t factory,
	unsigned int milliseconds_to_wait )
{
	std::thread parent{ test_thread, factory, milliseconds_to_wait };
	parent.join();
}

void
single_pair_test_case( lock_factory_t factory )
{
	run_with_time_limit( [factory] {
			std::cout << "single pair: " << std::flush;
			do_pair_thread_test_case( factory, 250u );
			std::cout << "OK" << std::endl;
		},
		20,
		"single_pair_test_case" );

}

void
serie_of_pair_tests_with_equal_intervals( lock_factory_t factory )
{
	std::cout << "serie of pair tests with equal interval: " << std::flush;

	for( unsigned int i = 0; i != 50; ++i )
	{
		run_with_time_limit( [factory] {
			do_pair_thread_test_case( factory, 25u );
		},
		20,
		"serie_of_pair_tests_with_equal_intervals" );
	}

	std::cout << "OK" << std::endl;

}

void
serie_of_pair_tests_with_different_intervals( lock_factory_t factory )
{
	std::cout << "serie of pair tests with different intervals: " << std::flush;

	for( unsigned int i = 1; i != 50; ++i )
	{
		run_with_time_limit( [factory, i] {
			do_pair_thread_test_case( factory, i*5u );
		},
		20,
		"serie_of_pair_tests_with_different_intervals" );
	}

	std::cout << "OK" << std::endl;

}

void
bunch_of_threads( lock_factory_t factory )
{
	std::cout << "bunch of threads: " << std::flush;

	std::vector< std::thread > threads;
	threads.reserve( 50 );

	run_with_time_limit( [&] {
			for( std::size_t i = 0; i != 50; ++i )
				threads.push_back( std::thread{ test_thread, factory, 250u } );

			for( auto & t : threads )
				t.join();
		},
		100,
		"bunch_of_threads" );

	std::cout << "OK" << std::endl;
}

int
main()
{
	try
	{
		struct case_info_t
		{
			std::string m_name;
			lock_factory_t m_factory;
		};

		std::vector< case_info_t > cases;
		cases.push_back( case_info_t{ "combined_lock(default)", combined_lock_factory() } );
		cases.push_back( case_info_t{ "combined_lock(1us)",
				combined_lock_factory( std::chrono::microseconds(1) ) } );
		cases.push_back( case_info_t{ "simple_lock", simple_lock_factory() } );

		for( const auto & c : cases )
		{
			std::cout << "CHECKING CASE: " << c.m_name << "..." << std::endl;

			single_pair_test_case( c.m_factory );
			serie_of_pair_tests_with_equal_intervals( c.m_factory );
			serie_of_pair_tests_with_different_intervals( c.m_factory );
			bunch_of_threads( c.m_factory );

			std::cout << "--- DONE ---" << std::endl;
		}

		return 0;
	}
	catch( const std::exception & x )
	{
		std::cerr << "Exception: " << x.what() << std::endl;
	}

	return 2;
}

