/*
 * A test for parallel remove of stop_guards.
 */

#include <so_5/all.hpp>

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

#include <test/3rd_party/utest_helper/helper.hpp>

using namespace std;

struct shutdown_started final : public so_5::signal_t {};

class second_stop_guard_t final
	: public so_5::stop_guard_t
	, public std::enable_shared_from_this<second_stop_guard_t>
{
public :
	second_stop_guard_t()
	{}

	virtual void
	stop() noexcept override
	{
		std::this_thread::sleep_for( std::chrono::milliseconds(50) );
	}
};

const std::size_t N = 4;

class first_worker_t final : public so_5::agent_t
{
public :
	struct worker_started final : public so_5::signal_t {};

	first_worker_t( context_t ctx, so_5::mbox_t notify_mbox )
		:	so_5::agent_t( std::move(ctx) )
		,	m_notify_mbox( std::move(notify_mbox) )
	{
		so_subscribe_self()
			.event( &first_worker_t::on_worker_started );
	}

private :
	const so_5::mbox_t m_notify_mbox;
	std::size_t m_active_workers = 0;

	void
	on_worker_started( mhood_t< worker_started > )
	{
		++m_active_workers;
		if( m_active_workers >= N )
		{
			so_5::send< shutdown_started >( m_notify_mbox );
			so_environment().stop();
		}
	}
};

class second_worker_t final : public so_5::agent_t
{
public :
	second_worker_t(
		context_t ctx,
		so_5::mbox_t manager_mbox,
		so_5::mbox_t notify_mbox )
		:	so_5::agent_t( std::move(ctx) )
		,	m_manager_mbox( std::move(manager_mbox) )
	{
		so_subscribe( notify_mbox ).event( &second_worker_t::on_shutdown_started );
	}

	virtual void
	so_evt_start() override
	{
		m_guard = std::make_shared< second_stop_guard_t >();
		so_environment().setup_stop_guard( m_guard );

		so_5::send< first_worker_t::worker_started >( m_manager_mbox );
	}

private :
	const so_5::mbox_t m_manager_mbox;
	std::shared_ptr< second_stop_guard_t > m_guard;

	void
	on_shutdown_started( mhood_t<shutdown_started> )
	{
		so_environment().remove_stop_guard( m_guard );
	}
};

void make_stuff( so_5::environment_t & env )
{
	auto notify_mbox = env.create_mbox();

	env.introduce_coop(
		so_5::disp::active_obj::make_dispatcher( env ).binder(),
		[&]( so_5::coop_t & coop ) {
			auto first = coop.make_agent< first_worker_t >( notify_mbox );
			for( std::size_t i = 0; i != N; ++i )
				coop.make_agent< second_worker_t >(
						first->so_direct_mbox(),
						notify_mbox );
		} );
}

int
main()
{
	try
	{
		run_with_time_limit(
			[]() {
				so_5::launch(
					[&](so_5::environment_t & env) {
						make_stuff( env );
					},
					[](so_5::environment_params_t & params) {
						(void)params;
					} );
			},
			5 );
	}
	catch(const exception & ex)
	{
		cerr << "Error: " << ex.what() << endl;
		return 1;
	}

	return 0;
}

