#pragma once

#include <core/TimeStepper.hpp>

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

class Integrator;

typedef std::vector<Real> stateVector; // Currently, we are unable to use Eigen library within odeint

/*Observer used to update the state of the scene*/
class observer {
	Integrator* integrator;

public:
	observer(Integrator* _in)
	        : integrator(_in)
	{
	}
	void operator()(const stateVector& /* x */, Real /* t */) const;
};

//[ ode_wrapper
template <class Obj, class Mem> class ode_wrapper {
	Obj m_obj;
	Mem m_mem;

public:
	ode_wrapper(Obj obj, Mem mem)
	        : m_obj(obj)
	        , m_mem(mem)
	{
	}

	template <class State, class Deriv, class Time> void operator()(const State& x, Deriv& dxdt, Time t) { (m_obj.*m_mem)(x, dxdt, t); }
};

template <class Obj, class Mem> ode_wrapper<Obj, Mem> make_ode_wrapper(Obj obj, Mem mem) { return ode_wrapper<Obj, Mem>(obj, mem); }
//]


class Integrator : public TimeStepper {
public:
	stateVector accumstateofthescene; //pos+vel

	stateVector accumstatedotofthescene; //only the accelerations

	stateVector resetstate; //last state before integration attempt

	Real timeresetvalue;

	inline void evaluateQuaternions(const stateVector&); //evaluate quaternions after integration

	typedef vector<vector<shared_ptr<Engine>>> slaveContainer;

#ifdef YADE_OPENMP
	vector<Real> threadMaxVelocitySq;
#endif

	void action() override;

	virtual void system(const stateVector&, stateVector&, Real); //System function to calculate the derivatives of states

	bool isActivated() override { return true; }
	// py access
	boost::python::list slaves_get();

	stateVector& getSceneStateDot();

	bool saveCurrentState(Scene const* ourscene); //Before any integration attempt state of the scene should be saved.

	bool resetLastState(void); //Before any integration attempt state of the scene should be saved.

	void slaves_set(const boost::python::list& slaves);

	stateVector& getCurrentStates(void);

	bool setCurrentStates(stateVector);

	Real updatingDispFactor; //(experimental) Displacement factor used to trigger bound update: the bound is updated only if updatingDispFactor*disp>sweepDist when >0, else all bounds are updated.

	void saveMaximaDisplacement(const shared_ptr<Body>& b);

#ifdef YADE_OPENMP
	void ensureSync();
	bool syncEnsured;
#endif


	// clang-format off
		YADE_CLASS_BASE_DOC_ATTRS_CTOR_PY(Integrator,TimeStepper,"Integration Engine Interface.",
		((slaveContainer,slaves,,,"[will be overridden]"))
		((Real,integrationsteps,,,"all integrationsteps count as all succesfull substeps"))
		((Real,maxVelocitySq,NaN,,"store square of max. velocity, for informative purposes; computed again at every step. |yupdate|"))
		,
		/*ctor*/
		#ifdef YADE_OPENMP
			threadMaxVelocitySq.resize(omp_get_max_threads()); syncEnsured=false;
		#endif
		,
		/*py*/

		.add_property("slaves",&Integrator::slaves_get,&Integrator::slaves_set,"List of lists of Engines to calculate the force acting on the particles;  to obtain the derivatives of the states, engines inside will be run sequentially.");
	);
	// clang-format on
};
REGISTER_SERIALIZABLE(Integrator);

} // namespace yade
