/* +------------------------------------------------------------------------+
   |                     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          |
   +------------------------------------------------------------------------+ */

#include "nav-precomp.h"  // Precomp header
//
#include <mrpt/nav/reactive/CMultiObjMotionOpt_Scalarization.h>

using namespace mrpt::nav;

IMPLEMENTS_MRPT_OBJECT(
	CMultiObjMotionOpt_Scalarization, CMultiObjectiveMotionOptimizerBase,
	mrpt::nav)

CMultiObjMotionOpt_Scalarization::CMultiObjMotionOpt_Scalarization()
	: CMultiObjectiveMotionOptimizerBase(parameters)
{
}

void CMultiObjMotionOpt_Scalarization::clear()
{
	CMultiObjectiveMotionOptimizerBase::clear();
	m_expr_scalar_formula = mrpt::expr::CRuntimeCompiledExpression();
	m_expr_scalar_vars.clear();
}

void CMultiObjMotionOpt_Scalarization::loadConfigFile(
	const mrpt::config::CConfigFileBase& c)
{
	parameters
		.CMultiObjectiveMotionOptimizerBase::TParamsBase::loadFromConfigFile(
			c, "CMultiObjectiveMotionOptimizerBase");  // call base-member with
	// special argument
	parameters.loadFromConfigFile(c, "CMultiObjMotionOpt_Scalarization");
}

void CMultiObjMotionOpt_Scalarization::saveConfigFile(
	mrpt::config::CConfigFileBase& c) const
{
	parameters
		.CMultiObjectiveMotionOptimizerBase::TParamsBase::saveToConfigFile(
			c, "CMultiObjectiveMotionOptimizerBase");  // call base-member with
	// special argument
	parameters.saveToConfigFile(c, "CMultiObjMotionOpt_Scalarization");
}

int CMultiObjMotionOpt_Scalarization::impl_decide(
	const std::vector<mrpt::nav::TCandidateMovementPTG>& movs,
	TResultInfo& extra_info)
{
	std::vector<double>& final_evaluation = extra_info.final_evaluation;
	final_evaluation.clear();

	if (extra_info.score_values.empty()) return -1;	 // No valid candidate

	// compile expression upon first use:
	if (m_expr_scalar_vars.empty())
	{
		// vars:
		for (const auto& c : extra_info.score_values)
		{
			for (const auto& score : c)
			{
				// double & var =
				m_expr_scalar_vars[score.first];  // create or reuse placeholder
			}
		}

		// formula:
		m_expr_scalar_formula = mrpt::expr::CRuntimeCompiledExpression();
		// Compile user-given expressions:
		try
		{
			m_expr_scalar_formula.compile(
				parameters.scalar_score_formula, m_expr_scalar_vars);
		}
		catch (std::exception&)
		{
			m_expr_scalar_vars.clear();
			throw;	// rethrow
		}
	}

	// Evaluate the formula for all candidates:
	const size_t N = extra_info.score_values.size();
	final_evaluation.assign(N, .0);
	int best_idx = -1;
	double best_val = .0;
	for (size_t i = 0; i < N; i++)
	{
		if (extra_info.score_values[i].empty())
			continue;  // this candidate is non-eligible.

		// Update variables & evaluate formula:
		for (const auto& score : extra_info.score_values[i])
		{
			const auto& it = m_expr_scalar_vars.find(
				score.first);  // this should never *create* a new entry...
			if (it == m_expr_scalar_vars.cend())
			{
				THROW_EXCEPTION_FMT(
					"Error: found unexpected (unregistered) score named `%s`.",
					score.first.c_str());
			}
			double& var = it->second;
			var = score.second;
		}
		const double val = m_expr_scalar_formula.eval();
		extra_info.final_evaluation[i] = val;

		if (val > 0 && (best_idx == -1 || val > best_val))
		{
			best_idx = i;
			best_val = val;
		}
	}

	return best_idx;
}

CMultiObjMotionOpt_Scalarization::TParams::TParams() = default;
void CMultiObjMotionOpt_Scalarization::TParams::loadFromConfigFile(
	const mrpt::config::CConfigFileBase& c, const std::string& s)
{
	// TParamsBase members are already dealt elsewhere.
	MRPT_LOAD_CONFIG_VAR_REQUIRED_CS(scalar_score_formula, string);
}

void CMultiObjMotionOpt_Scalarization::TParams::saveToConfigFile(
	mrpt::config::CConfigFileBase& c, const std::string& s) const
{
	// TParamsBase members are already dealt elsewhere.
	MRPT_SAVE_CONFIG_VAR_COMMENT(
		scalar_score_formula,
		"A formula that takes all/a subset of scores and generates a scalar "
		"global score.");
}
