#include "Tests/Unit/Numeric/MultiQTest.h"

using ::testing::internal::ParamGenerator;

namespace formfactorTest {

const auto qlist = testing::Combine(
    testing::Values(C3({1, 0, 0}), C3({0, 1, 0}), C3({0, 0, 1}), C3({1, 1, 0}), C3({1, 0, 1}),
                    C3({0, 1, 1}), C3({1, 1, 1})),
    testing::Values(C3({1, 0, 0}), C3({0, 1, 0}), C3({0, 0, 1}), C3({1, 1, 0}), C3({1, 0, 1}),
                    C3({1, 0, 1}), C3({1, 1, 1})),
    testing::Values(1e-19, 1e-17, 1e-15, 1e-12, 1e-9, 1e-6, 1e-3, .03, 1., 1e1, 1e2, 1e3, 1e4),
    testing::Values(-1e-15, +1e-14, -1e-11, 1e-7, -1e-3, .1, 1, sqrt(2), sqrt(3)),
    testing::Values(0, -4e-16, +8e-16, -5e-11, 3e-7, -2e-3, .01, .1));

int run_test_for_many_q(std::function<complex_t(C3)> fff0, std::function<complex_t(C3)> fff1,
                        double qmag_min, double qmag_max, double eps, bool real_only)
{
    auto evaluate = [&](C3 q) -> std::tuple<complex_t, complex_t, double, double, double> {
        const complex_t f0 = fff0(q);
        const complex_t f1 = fff1(q);
        const double avge = (std::abs(f0) + std::abs(f1)) / 2;
        const double abserr = real_only
                                  ? std::abs(f0) - std::abs(f1)
                                  : std::max(fabs(real(f0) - real(f1)), fabs(imag(f0) - imag(f1)));
        const double deviation = abserr / avge * std::min(1., eps * avge / 1e-16);
        return {f0, f1, avge, abserr, deviation};
    };

    ParamGenerator<std::tuple<C3, C3, double, double, double>> gen = qlist;
    double max_deviation = 0;
    C3 q_at_max;
    int failures = 0;
    for (auto it : gen) {
        const C3 q_maindir = std::get<0>(it);
        const C3 q_sidedir = std::get<1>(it);
        const double qrealmag = std::get<2>(it);
        const double qsidemag = std::get<3>(it);
        const double qimagrel = std::get<4>(it);
        if (real_only && qimagrel)
            continue;
        const complex_t qmag(qrealmag, qrealmag * qimagrel);
        if (std::abs(qmag) <= qmag_min || std::abs(qmag) >= qmag_max)
            continue;
        if (q_maindir == q_sidedir)
            continue;
        const C3 q = qmag * (q_maindir + qsidemag * q_sidedir).unit_or_throw();

        const auto [f0, f1, avge, abserr, deviation] = evaluate(q);

        if (deviation > eps) {
            ++failures;
            if (deviation > max_deviation) {
                max_deviation = deviation;
                q_at_max = q;
            }
        }
    }

    if (failures) {
        const auto [f0, f1, avge, abserr, deviation] = evaluate(q_at_max);
        std::cout << "Deviation at q = " << q_at_max << ":\n";
        std::cout << "  Re(f0) = " << std::setprecision(16) << real(f0) << ", Im(f0) = " << imag(f0)
                  << "\n";
        std::cout << "  Re(f1) = " << real(f1) << ", Im(f1) = " << imag(f1) << "\n";
        std::cout << "  abs dev = " << std::setprecision(8) << abserr
                  << ", rel dev = " << abserr / avge << ", score = " << deviation
                  << ", limit = " << eps << "\n";
    }

    return failures;
}

} // namespace formfactorTest
