/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */
#pragma once

#include <mrpt/config/CLoadableOptions.h>
#include <mrpt/system/COutputLogger.h>
#include <mrpt/typemeta/TEnumType.h>

namespace mrpt
{
namespace obs
{
class CSensoryFrame;
class CActionCollection;
}  // namespace obs

/** The namespace for Bayesian filtering algorithm: different particle filters
 * and Kalman filter algorithms. \ingroup mrpt_bayes_grp
 */
namespace bayes
{
class CParticleFilterCapable;

/** This class acts as a common interface to the different interfaces (see
 *CParticleFilter::TParticleFilterAlgorithm) any bayes::CParticleFilterCapable
 *class can implement: it is the invoker of particle filter algorithms.
 *   The particle filter is executed on a probability density function (PDF)
 *described by a CParticleFilterCapable object, passed in the constructor or
 *alternatively through the CParticleFilter::executeOn method.<br>
 *
 * For a complete example and further details, see the <a
 *href="http://www.mrpt.org/Particle_Filter_Tutorial" >Particle Filter
 *tutorial</a>.
 *
 *   The basic SIR algorithm (pfStandardProposal) consists of:
 *		- Execute a prediction with the given "action".
 *		- Update the weights of the particles using the likelihood of the
 *"observation".
 *		- Normalize weights.
 *		- Perform resampling if the ESS is below the threshold options.BETA.
 *
 * \ingroup mrpt_bayes_grp
 * \sa mrpt::poses::CPoseParticlesPDF
 */
class CParticleFilter : public mrpt::system::COutputLogger
{
   public:
	/** Defines different types of particle filter algorithms.
	 *  The defined SIR implementations are:
	 *		- pfStandardProposal: Standard proposal distribution + weights
	 *according to likelihood function.
	 *		- pfAuxiliaryPFStandard: An auxiliary PF using the standard proposal
	 *distribution.
	 *		- pfOptimalProposal: Use the optimal proposal distribution (where
	 *available!, usually this will perform approximations)
	 *		- pfAuxiliaryPFOptimal: Use the optimal proposal and a auxiliary
	 *particle filter (see <a
	 *href="http://www.mrpt.org/Paper:An_Optimal_Filtering_Algorithm_for_Non-Parametric_Observation_Models_in_Robot_Localization_(ICRA_2008)"
	 *>paper</a>).
	 *
	 * See the theoretical discussion in <a
	 *href="http://www.mrpt.org/Resampling_Schemes" >resampling schemes</a>.
	 */
	enum TParticleFilterAlgorithm
	{
		pfStandardProposal = 0,
		pfAuxiliaryPFStandard,
		pfOptimalProposal,
		pfAuxiliaryPFOptimal
	};

	/** Defines the different resampling algorithms.
	 *  The implemented resampling methods are:
	 *		- prMultinomial (Default): Uses standard select with replacement
	 *(draws
	 *M random uniform numbers)
	 *		- prResidual: The residual or "remainder" method.
	 *		- prStratified: The stratified resampling, where a uniform sample is
	 *drawn for each of M subdivisions of the range (0,1].
	 *		- prSystematic: A single uniform sample is drawn in the range
	 *(0,1/M].
	 *
	 * See the theoretical discussion in <a
	 *href="http://www.mrpt.org/Resampling_Schemes" >resampling schemes</a>.
	 */
	enum TParticleResamplingAlgorithm
	{
		prMultinomial = 0,
		prResidual,
		prStratified,
		prSystematic
	};

	/** The configuration of a particle filter.
	 */
	struct TParticleFilterOptions : public mrpt::config::CLoadableOptions
	{
	   public:
		// See base docs:
		void loadFromConfigFile(
			const mrpt::config::CConfigFileBase& source,
			const std::string& section) override;
		void saveToConfigFile(
			mrpt::config::CConfigFileBase& target,
			const std::string& section) const override;

		/** A flag that indicates whether the CParticleFilterCapable object
		 * should perform adative sample size (default=false). */
		bool adaptiveSampleSize{false};
		/** The resampling of particles will be performed when ESS (in range
		 * [0,1]) < BETA (default is 0.5) */
		double BETA{0.5};
		/** The initial number of particles in the filter (it can change only if
		 * adaptiveSampleSize=true) (default=1) */
		unsigned int sampleSize{1};

		/** In the algorithm "CParticleFilter::pfAuxiliaryPFOptimal" (and in
		 * "CParticleFilter::pfAuxiliaryPFStandard" only if
		 * pfAuxFilterStandard_FirstStageWeightsMonteCarlo = true) the number of
		 * samples for searching the maximum likelihood value and also to
		 * estimate the "first stage weights" (see papers!) (default=100)
		 */
		unsigned int pfAuxFilterOptimal_MaximumSearchSamples{100};
		/** An optional step to "smooth" dramatic changes in the observation
		 * model to affect the variance of the particle weights, eg
		 * weight*=likelihood^powFactor (default=1 = no effects). */
		double powFactor{1};
		/** The PF algorithm to use (default=pfStandardProposal) See
		 * TParticleFilterAlgorithm for the possibilities. */
		TParticleFilterAlgorithm PF_algorithm{pfStandardProposal};
		/** The resampling algorithm to use (default=prMultinomial). */
		TParticleResamplingAlgorithm resamplingMethod{prMultinomial};

		/** Only for PF_algorithm=pfAuxiliaryPFOptimal: If a given particle has
		 * a max_likelihood (from the a-priori estimate) below the maximum from
		 * all the samples - max_loglikelihood_dyn_range, then the particle is
		 * directly discarded.
		 *  This is done to assure that the rejection sampling doesn't get
		 * stuck in an infinite loop trying to get an acceptable sample.
		 *  Default = 15 (in logarithmic likelihood)
		 */
		double max_loglikelihood_dyn_range{15};

		/** Only for PF_algorithm==pfAuxiliaryPFStandard:
		 * If false, the APF will predict the first stage weights just at the
		 * mean of the prior of the next time step.
		 * If true, these weights will be estimated as described in the papers
		 * for the "pfAuxiliaryPFOptimal" method, i.e. through a monte carlo
		 * simulation.
		 *  In that case, "pfAuxFilterOptimal_MaximumSearchSamples" is the
		 * number of MC samples used.
		 */
		bool pfAuxFilterStandard_FirstStageWeightsMonteCarlo{false};

		/** (Default=false) In the algorithm
		 * "CParticleFilter::pfAuxiliaryPFOptimal", if set to true, do not
		 * perform rejection sampling, but just the most-likely (ML) particle
		 * found in the preliminary weight-determination stage. */
		bool pfAuxFilterOptimal_MLE{false};
	};

	/** Statistics for being returned from the "execute" method. */
	struct TParticleFilterStats
	{
		double ESS_beforeResample{0};
		double weightsVariance_beforeResample{0};
	};

	/** Default constructor.
	 *   After creating the PF object, set the options in
	 * CParticleFilter::m_options, then execute steps through
	 * CParticleFilter::executeOn.
	 */
	CParticleFilter();

	~CParticleFilter() override = default;
	/** Executes a complete prediction + update step of the selected particle
	 * filtering algorithm.
	 *    The member CParticleFilter::m_options must be set before calling this
	 * to settle the algorithm parameters.
	 *
	 * \param obj           The object representing the probability distribution
	 * function (PDF) which apply the particle filter algorithm to.
	 * \param action		A pointer to an action in the form of a
	 * CActionCollection,
	 * or nullptr if there is no action.
	 * \param observation	A pointer to observations in the form of a
	 * CSensoryFrame, or nullptr if there is no observation.
	 * \param stats An output structure for gathering statistics of the particle
	 * filter execution, or set to nullptr if you do not need it (see
	 * CParticleFilter::TParticleFilterStats).
	 *
	 * \sa CParticleFilterCapable, executeOn
	 */
	void executeOn(
		CParticleFilterCapable& obj, const mrpt::obs::CActionCollection* action,
		const mrpt::obs::CSensoryFrame* observation,
		TParticleFilterStats* stats = nullptr) const;

	/** The options to be used in the PF, must be set before executing any step
	 * of the particle filter.
	 */
	CParticleFilter::TParticleFilterOptions m_options;

};	// End of class def.

}  // namespace bayes
}  // namespace mrpt

MRPT_ENUM_TYPE_BEGIN(mrpt::bayes::CParticleFilter::TParticleFilterAlgorithm)
MRPT_FILL_ENUM_MEMBER(mrpt::bayes::CParticleFilter, pfStandardProposal);
MRPT_FILL_ENUM_MEMBER(mrpt::bayes::CParticleFilter, pfAuxiliaryPFOptimal);
MRPT_FILL_ENUM_MEMBER(mrpt::bayes::CParticleFilter, pfAuxiliaryPFStandard);
MRPT_FILL_ENUM_MEMBER(mrpt::bayes::CParticleFilter, pfOptimalProposal);
MRPT_ENUM_TYPE_END()

MRPT_ENUM_TYPE_BEGIN(mrpt::bayes::CParticleFilter::TParticleResamplingAlgorithm)
MRPT_FILL_ENUM_MEMBER(mrpt::bayes::CParticleFilter, prMultinomial);
MRPT_FILL_ENUM_MEMBER(mrpt::bayes::CParticleFilter, prResidual);
MRPT_FILL_ENUM_MEMBER(mrpt::bayes::CParticleFilter, prStratified);
MRPT_FILL_ENUM_MEMBER(mrpt::bayes::CParticleFilter, prSystematic);
MRPT_ENUM_TYPE_END()
