#include <pkg/common/ParallelEngine.hpp>

#ifdef YADE_OPENMP
#include <omp.h>
#endif

namespace yade { // Cannot have #include directive inside.

YADE_PLUGIN((ParallelEngine));

//! ParallelEngine's pseudo-ctor (factory), taking nested lists of slave engines (might be moved to real ctor perhaps)
shared_ptr<ParallelEngine> ParallelEngine_ctor_list(const boost::python::list& slaves)
{
	shared_ptr<ParallelEngine> instance(new ParallelEngine);
	instance->slaves_set(slaves);
	return instance;
}

void ParallelEngine::action()
{
	// openMP warns if the iteration variable is unsigned...
	const int  size               = (int)slaves.size();
	const bool TimingInfo_enabled = TimingInfo::enabled;
#ifdef YADE_OPENMP
	//nested parallel regions are disabled by default on some platforms, we enable them since some of the subengine may be also parallel

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
// silence the warning that omp_set_nested(1) is deprecated
	omp_set_nested(1);
#pragma GCC diagnostic pop

#pragma omp parallel for num_threads(ompThreads)

#endif
	for (int i = 0; i < size; i++) {
		TimingInfo::delta last = TimingInfo::getNow();
		// run every slave group sequentially
		FOREACH(const shared_ptr<Engine>& e, slaves[i])
		{
			e->scene = scene;
			if (!e->dead && e->isActivated()) {
				e->action();
				if (TimingInfo_enabled) {
					TimingInfo::delta now = TimingInfo::getNow();
					e->timingInfo.nsec += now - last;
					e->timingInfo.nExec += 1;
					last = now;
				}
			}
		}
	}
}

void ParallelEngine::slaves_set(const boost::python::list& slaves2)
{
	int len = boost::python::len(slaves2);
	slaves.clear();
	for (int i = 0; i < len; i++) {
		boost::python::extract<std::vector<shared_ptr<Engine>>> serialGroup(slaves2[i]);
		if (serialGroup.check()) {
			slaves.push_back(serialGroup());
			continue;
		}
		boost::python::extract<shared_ptr<Engine>> serialAlone(slaves2[i]);
		if (serialAlone.check()) {
			vector<shared_ptr<Engine>> aloneWrap;
			aloneWrap.push_back(serialAlone());
			slaves.push_back(aloneWrap);
			continue;
		}
		PyErr_SetString(
		        PyExc_TypeError, "List elements must be either\n (a) sequences of engines to be executed one after another\n(b) alone engines.");
		boost::python::throw_error_already_set();
	}
}

boost::python::list ParallelEngine::slaves_get()
{
	boost::python::list ret;
	FOREACH(vector<shared_ptr<Engine>> & grp, slaves)
	{
		if (grp.size() == 1) ret.append(boost::python::object(grp[0]));
		else
			ret.append(boost::python::object(grp));
	}
	return ret;
}

} // namespace yade
