// Standalone tests for reasampler::instrument::engine::master_gain — no VST3, no REAPER, no framework. Same
// fast assert loop as the sibling pure tests. Assert the FB1 post-mixer gain taper HARD:
//
//  * -inf bottom — norm 0 maps to -infinity dB and TRUE ZERO linear (silence, not an epsilon);
//    linear <= 0 maps back to norm 0.
//  * endpoints — norm 1 = +24 dB = masterGainMaxLinear() (~15.849); the finite floor at -60 dB.
//  * unity — 0 dB round-trips exactly through norm<->dB<->linear (the knob's unity detent
//    position 60/84 of the travel).
//  * monotonicity — more norm never means less gain.
//  * label — "-inf" at the bottom, signed one-decimal dB elsewhere.

#include "../src/core/instrument/engine/master_gain.h"

#include <cmath>
#include <cstdio>
#include <cstring>

using namespace reasampler;
using namespace reasampler::instrument::engine;

static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
    std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)

static bool near(double a, double b, double eps = 1e-9) { return std::fabs(a - b) <= eps; }

static void testBottomIsTrueSilence() {
    CHECK(std::isinf(masterGainDbFromNorm(0.0)) && masterGainDbFromNorm(0.0) < 0.0);
    CHECK(masterGainLinearFromNorm(0.0) == 0.0);  // exactly zero — the -inf contract
    CHECK(masterGainNormFromLinear(0.0) == 0.0);
    CHECK(masterGainNormFromLinear(-1.0) == 0.0);
    CHECK(masterGainNormFromDb(-1e9) == 0.0);
}

static void testEndpoints() {
    CHECK(near(masterGainDbFromNorm(1.0), kMasterGainMaxDb));
    CHECK(near(masterGainLinearFromNorm(1.0), masterGainMaxLinear()));
    CHECK(near(masterGainMaxLinear(), std::pow(10.0, 24.0 / 20.0)));
    CHECK(near(masterGainNormFromDb(kMasterGainMaxDb), 1.0));
    CHECK(near(masterGainNormFromLinear(masterGainMaxLinear()), 1.0));
    // Out-of-range norms clamp.
    CHECK(near(masterGainDbFromNorm(2.0), kMasterGainMaxDb));
    CHECK(masterGainLinearFromNorm(-0.5) == 0.0);
}

static void testUnityRoundTrip() {
    // 0 dB sits at norm -minDb/(maxDb-minDb) = 60/84 of the travel.
    const double unityNorm = masterGainNormFromDb(0.0);
    CHECK(near(unityNorm, 60.0 / 84.0, 1e-12));
    CHECK(near(masterGainDbFromNorm(unityNorm), 0.0, 1e-9));
    CHECK(near(masterGainLinearFromNorm(unityNorm), 1.0, 1e-9));
    CHECK(near(masterGainNormFromLinear(1.0), unityNorm, 1e-9));
}

static void testNormLinearRoundTripAcrossTravel() {
    for (int i = 1; i <= 20; ++i) {
        const double norm = i / 20.0;
        const double lin = masterGainLinearFromNorm(norm);
        CHECK(lin > 0.0);
        CHECK(near(masterGainNormFromLinear(lin), norm, 1e-9));
    }
}

static void testMonotonic() {
    double prev = -1.0;
    for (int i = 0; i <= 100; ++i) {
        const double lin = masterGainLinearFromNorm(i / 100.0);
        CHECK(lin > prev);  // strictly increasing over the whole travel (0 at the bottom)
        prev = lin;
    }
}

static void testNonFiniteLinearClamps() {
    CHECK(masterGainNormFromLinear(std::nan("")) == 0.0);
    CHECK(near(masterGainNormFromLinear(1e9), 1.0));  // above the cap clamps to 1
}

static void testBelowFloorCollapsesToBottom() {
    // Any linear gain at or below the kMasterGainMinDb floor collapses to norm 0 (the -inf
    // bottom detent). The floor IS the bottom of the finite sweep, so -61 dB, -80 dB, and
    // a vanishingly small positive linear all map to the same place as true zero.
    const double floorLinear = std::pow(10.0, kMasterGainMinDb / 20.0);  // 10^(-60/20) = 0.001
    CHECK(masterGainNormFromLinear(floorLinear) == 0.0);   // exactly at the floor -> bottom
    CHECK(masterGainNormFromLinear(floorLinear * 0.5) == 0.0);  // below the floor -> bottom
    CHECK(masterGainNormFromLinear(1e-9) == 0.0);               // tiny positive -> bottom
    // The dB path agrees: anything at or below kMasterGainMinDb maps to norm 0.
    CHECK(masterGainNormFromDb(kMasterGainMinDb) == 0.0);
    CHECK(masterGainNormFromDb(kMasterGainMinDb - 20.0) == 0.0);  // -80 dB -> bottom
    // Just ABOVE the floor (by epsilon) returns a non-zero norm.
    CHECK(masterGainNormFromDb(kMasterGainMinDb + 1e-9) > 0.0);
}

int main() {
    testBottomIsTrueSilence();
    testEndpoints();
    testUnityRoundTrip();
    testNormLinearRoundTripAcrossTravel();
    testMonotonic();
    testNonFiniteLinearClamps();
    testBelowFloorCollapsesToBottom();
    if (g_fail) {
        std::printf("%d FAILURE(S)\n", g_fail);
        return 1;
    }
    std::printf("master_gain tests passed\n");
    return 0;
}
