#include "core/instrument/engine/filter/filter_params.h"

#include <cmath>

namespace reasampler::instrument::engine::filter {
namespace {

double clamp01(double v) { return v < 0.0 ? 0.0 : (v > 1.0 ? 1.0 : v); }

// log Q = A + B*n + C*n^2, solved from the three anchor points. C is nonzero precisely
// because the center anchor sqrt(2) is not the geometric mean of the endpoints (which is 1);
// were they equal the curve would degenerate to a plain log sweep and the inverse below
// would divide by zero.
struct QCurve {
    double a, b, c;
};

QCurve solveQCurve() {
    const double lo = std::log(static_cast<double>(kFilterQMin));
    const double mid = std::log(static_cast<double>(kFilterQCenter));
    const double hi = std::log(static_cast<double>(kFilterQMax));
    return {lo, 4.0 * mid - 3.0 * lo - hi, 2.0 * lo + 2.0 * hi - 4.0 * mid};
}

// Functions of compile-time constants alone, so they resolve once at static init rather than
// per call. Load-bearing rather than tidy: a modulated cutoff re-solves EVERY FRAME, and
// recomputing these logarithms of literals cost more than the solve they feed.
const double kLogCutoffMin = std::log(static_cast<double>(kFilterCutoffMinHz));
const double kLogCutoffSpan =
    std::log(static_cast<double>(kFilterCutoffMaxHz)) - kLogCutoffMin;
const QCurve kQCurve = solveQCurve();

}  // namespace

float filterCutoffHzFromNorm(float norm) {
    return static_cast<float>(std::exp(kLogCutoffMin + clamp01(norm) * kLogCutoffSpan));
}

float filterNormFromCutoffHz(float hz) {
    if (!(hz > 0.0f)) return 0.0f;
    return static_cast<float>(
        clamp01((std::log(static_cast<double>(hz)) - kLogCutoffMin) / kLogCutoffSpan));
}

float filterQFromNorm(float norm) {
    const double n = clamp01(norm);
    return static_cast<float>(std::exp(kQCurve.a + n * (kQCurve.b + kQCurve.c * n)));
}

float filterDriveDepthFromNorm(float norm) {
    const double n = clamp01(norm);
    return static_cast<float>(kFilterDriveDepthMax * n * n);
}

float filterNormFromDriveDepth(float depth) {
    if (!(depth > 0.0f)) return 0.0f;  // also catches NaN
    if (depth >= kFilterDriveDepthMax) return 1.0f;
    return static_cast<float>(
        std::sqrt(static_cast<double>(depth) / static_cast<double>(kFilterDriveDepthMax)));
}

float filterNormFromQ(float q) {
    if (!(q > kFilterQMin)) return 0.0f;
    if (q >= kFilterQMax) return 1.0f;
    // Clamping first is load-bearing, not just tidy: the parabola peaks at log Q well below
    // an arbitrarily large q, so an unclamped out-of-range value has no real root at all.
    const QCurve& k = kQCurve;
    const double d = k.b * k.b - 4.0 * k.c * (k.a - std::log(static_cast<double>(q)));
    if (!(d >= 0.0)) return 0.0f;
    // Of the two roots only this one lies on the rising branch inside [0,1]; the parabola's
    // vertex sits well above 1 for the settled anchors.
    return static_cast<float>(clamp01((-k.b + std::sqrt(d)) / (2.0 * k.c)));
}

}  // namespace reasampler::instrument::engine::filter
