//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Fit/TestEngine/MinimizerTestPlan.cpp
//! @brief     Implements class MinimizerTestPlan
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Fit/TestEngine/MinimizerTestPlan.h"
#include "Fit/Param/Parameters.h"
#include <cmath>
#include <iostream>
#include <sstream>

namespace {

//! Returns the safe relative difference, which is 2(|a-b|)/(|a|+|b|) except in special cases.
double relativeDifference(double a, double b)
{
    constexpr double eps = std::numeric_limits<double>::epsilon();
    const double avg_abs = (std::abs(a) + std::abs(b)) / 2.0;
    // return 0.0 if relative error smaller than epsilon
    if (std::abs(a - b) <= eps * avg_abs)
        return 0.0;
    return std::abs(a - b) / avg_abs;
}

} // namespace

using namespace mumufit;

MinimizerTestPlan::~MinimizerTestPlan() = default;

void MinimizerTestPlan::addParameter(const Parameter& param, double expected_value,
                                     double tolerance)
{
    m_parameter_references.push_back({param, expected_value, tolerance});
}

//! Returns fit parameters which will be used as initial one for the minimization.

Parameters MinimizerTestPlan::parameters() const
{
    Parameters result;
    for (const auto& ref : m_parameter_references)
        result.add(ref.parameter);

    return result;
}

//! Return vector of expected parameter values.

std::vector<double> MinimizerTestPlan::expectedValues() const
{
    std::vector<double> result;
    for (const ParameterReference& ref : m_parameter_references)
        result.push_back(ref.expected_value);

    return result;
}

//! Returns true if given values coincide with expected fit parameter values.

bool MinimizerTestPlan::valuesAsExpected(const std::vector<double>& values) const
{
    bool success = true;

    if (m_parameter_references.size() != values.size())
        throw std::runtime_error("FunctionTestPlan::valuesAsExpected -> Error. Sizes differ.");

    for (size_t i = 0; i < values.size(); ++i) {
        const ParameterReference& ref = m_parameter_references[i];
        double diff = relativeDifference(values[i], ref.expected_value);

        bool ok = diff <= ref.tolerance;

        std::cout << ref.parameter.name() << " found:" << values[i]
                  << " expected:" << ref.expected_value << " diff:" << diff << " "
                  << (ok ? "OK" : "FAILED") << std::endl;

        success &= ok;
    }

    return success;
}
