//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/IO/DiffUtil.cpp
//! @brief     Implements namespace DataUtil.
//!
//! @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 "Device/IO/DiffUtil.h"
#include "Base/Axis/Frame.h"
#include "Base/Axis/Scale.h"
#include "Base/Math/Numeric.h"
#include "Base/Util/Assert.h"
#include "Device/Data/Datafield.h"
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>

namespace {

//! Returns relative difference between two data sets sum(dat[i] - ref[i])/ref[i]).
double meanRelVecDiff(const std::vector<double>& dat, const std::vector<double>& ref)
{
    ASSERT(dat.size() == ref.size());
    double diff = 0;
    for (size_t i = 0; i < dat.size(); ++i) {
        if (ref[i] == 0 && dat[i] == 0)
            continue;
        if (ref[i] == 0 || dat[i] == 0) {
            std::cerr << "check manually at i=" << i << ": ref=" << ref[i] << " vs dat=" << dat[i]
                      << "\n";
            continue;
        }
        diff += Numeric::relativeDifference(dat[i], ref[i]);
    }
    diff /= dat.size();
    ASSERT(std::isfinite(diff));
    return diff;
}

bool checkVecConsistence(const std::vector<double>& dat, const std::vector<double>& ref,
                         double precision, double snr, int allowed_outliers)
{
    ASSERT(dat.size() == ref.size());
    const size_t N = dat.size();
    double max_dat = 0;
    double max_ref = 0;
    for (size_t i = 0; i < N; ++i) {
        max_dat = std::max(max_dat, dat[i]);
        max_ref = std::max(max_ref, ref[i]);
    }
    if (!max_dat) {
        std::cerr << "FAILED: Data are all zero" << std::endl;
        return false;
    }
    if (!max_ref) {
        std::cerr << "FAILED: Reference data are all zero" << std::endl;
        return false;
    }
    const double noise_level = max_ref * snr;
    double max_err = 0;
    size_t i_max = -1;
    int n_outliers = 0;
    for (size_t i = 0; i < N; ++i) {
        double rel_err = std::abs(dat[i] - ref[i]) / (precision * std::abs(ref[i]) + noise_level);
        if (rel_err > 1)
            ++n_outliers;
        if (rel_err > max_err) {
            max_err = rel_err;
            i_max = i;
        }
    }
    if (i_max == size_t(-1)) {
        std::cout << "OK: no disagreement beyond max rel err = " << max_err << std::endl;
        return true;
    } else if (allowed_outliers > 0) {
        if (n_outliers > allowed_outliers) {
            std::cerr << "FAILED: #outliers=" << n_outliers << " vs allowed #=" << allowed_outliers
                      << std::endl;
            return false;
        }
        if (n_outliers > 0) {
            std::cerr << "OK: #outliers=" << n_outliers << " vs allowed #=" << allowed_outliers
                      << std::endl;
            return true;
        }
        // else fall through
    } else if (max_err > 1) {
        std::cerr << "FAILED: Maximum disagreement at i=" << i_max << ", dat=" << dat[i_max]
                  << " vs ref=" << ref[i_max] << " => err_factor=" << max_err << " where "
                  << " precision=" << precision << ", snr=" << snr << std::endl;
        return false;
    }
    std::cout << "OK: Maximum disagreement at i=" << i_max << ", dat=" << dat[i_max]
              << " vs ref=" << ref[i_max] << " => err_factor=" << max_err << " where "
              << " precision=" << precision << ", snr=" << snr << std::endl;
    return true;
}

} // namespace


bool DiffUtil::checkRelVecDifference(const std::vector<double>& dat, const std::vector<double>& ref,
                                     double threshold)
{
    ASSERT(dat.size() == ref.size());
    if (*std::min_element(dat.begin(), dat.end()) == 0
        && *std::max_element(dat.begin(), dat.end()) == 0) {
        std::cerr << "FAILED: simulated data set is empty" << std::endl;
        return false;
    }

    try {
        const double diff = ::meanRelVecDiff(dat, ref);
        if (diff > threshold) {
            std::cerr << "FAILED: relative deviation of dat from ref is " << diff
                      << ", above given threshold " << threshold << std::endl;
            return false;
        }
        if (diff)
            std::cerr << "- OK: relative deviation of dat from ref is " << diff
                      << ", within given threshold " << threshold << std::endl;
        else
            std::cout << "- OK: dat = ref\n";
        return true;
    } catch (...) {
        return false;
    }
}

bool DiffUtil::checkRelativeDifference(const Datafield& dat, const Datafield& ref, double threshold)
{
    return checkRelVecDifference(dat.flatVector(), ref.flatVector(), threshold);
}

bool DiffUtil::checkConsistence(const Datafield& dat, const Datafield& ref, double precision,
                                double snr, int allowed_outliers)
{
    return ::checkVecConsistence(dat.flatVector(), ref.flatVector(), precision, snr,
                                 allowed_outliers);
}
