// Standalone tests for reasampler::instrument::engine::meter_accumulate — no VST3, no REAPER,
// no framework. Assert:
//
//  * the window semantics — max for a peak, min for the limiter gain, and a consume that both
//    reports the window and reinstalls the identity element that starts the next one.
//  * the INTERLEAVE the CAS exists for: a consume landing inside a fold must not swallow that
//    block. Driven by an accumulator that performs the consume from inside its first CAS, so
//    the ordering is pinned rather than raced for.

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

#include <atomic>
#include <cstdio>

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)

// Stands in for std::atomic<float> with ONE scripted interference: the first compare-exchange
// runs the UI's consume (identity reinstalled, the window taken) and reports failure exactly as
// the real CAS does — expected updated to what the consume left. Everything after is ordinary,
// which is what makes the retry count assertable: a strong CAS fails only under interference.
struct ConsumingAccumulator {
    float value;
    float identity;
    float consumed = -1.f;   // what the injected consume took
    int casCount = 0;

    float load(std::memory_order) const { return value; }

    bool compare_exchange_strong(float& expected, float desired, std::memory_order,
                                 std::memory_order) {
        if (casCount++ == 0) {
            consumed = value;
            value = identity;
            expected = value;
            return false;
        }
        value = desired;
        return true;
    }
};

static void testPeakWindowKeepsTheLoudestBlock() {
    std::atomic<float> acc{kMeterPeakIdentity};
    foldPeak(acc, 0.25f);
    foldPeak(acc, 0.90f);
    foldPeak(acc, 0.40f);  // quieter than the window's max: must not lower it
    CHECK(acc.load() == 0.90f);
    CHECK(consumePeak(acc) == 0.90f);
    // Consumed means a NEW window, not a carried-over one.
    CHECK(acc.load() == kMeterPeakIdentity);
    foldPeak(acc, 0.10f);
    CHECK(consumePeak(acc) == 0.10f);
}

static void testGainWindowKeepsTheDeepestReduction() {
    std::atomic<float> acc{kMeterGainIdentity};
    foldMinGain(acc, 0.80f);
    foldMinGain(acc, 0.55f);
    foldMinGain(acc, 0.95f);  // shallower: must not raise the window
    CHECK(acc.load() == 0.55f);
    CHECK(consumeMinGain(acc) == 0.55f);
    // 1.0, not 0.0 — an untouched gain window means "no reduction", and a 0 identity would
    // report a total mute on every idle frame.
    CHECK(acc.load() == kMeterGainIdentity);
}

static void testBlocksAtOrBelowTheWindowLeaveItAlone() {
    std::atomic<float> acc{kMeterPeakIdentity};
    foldPeak(acc, 0.50f);
    foldPeak(acc, 0.50f);
    CHECK(acc.load() == 0.50f);
    // The post-condition every fold owes, whichever way the comparison went.
    foldPeak(acc, 0.20f);
    CHECK(acc.load() >= 0.20f);
}

static void testConsumeInsideAFoldStillLandsTheBlockInTheNewWindow() {
    // The window holds a LOUDER peak than the block being folded — the exact case a
    // load-compare-store fold skips, so the block would be lost when the consume lands
    // between that load and the store it decided not to make.
    ConsumingAccumulator acc{0.90f, kMeterPeakIdentity};
    foldPeak(acc, 0.40f);
    CHECK(acc.consumed == 0.90f);  // the UI got the window it was owed
    CHECK(acc.value == 0.40f);     // and the block reached the NEW window rather than vanishing
    CHECK(acc.casCount == 2);      // one interfering consume, exactly one retry

    // Same for the gain window: a block reducing LESS than the window's minimum is the one a
    // skipping fold drops, and losing it reports "no reduction" over a block that had some.
    ConsumingAccumulator gain{0.60f, kMeterGainIdentity};
    foldMinGain(gain, 0.90f);
    CHECK(gain.consumed == 0.60f);
    CHECK(gain.value == 0.90f);
    CHECK(gain.casCount == 2);
}

int main() {
    testPeakWindowKeepsTheLoudestBlock();
    testGainWindowKeepsTheDeepestReduction();
    testBlocksAtOrBelowTheWindowLeaveItAlone();
    testConsumeInsideAFoldStillLandsTheBlockInTheNewWindow();
    if (g_fail == 0) std::printf("meter_accumulate tests passed\n");
    return g_fail == 0 ? 0 : 1;
}
