// Standalone tests for reasampler::insert_plan — no REAPER, no framework.
// The insert shell is DAW-bound; this covers the one genuinely pure piece: the
// InsertMedia `mode` bitmask arithmetic. The bits are load-bearing for the
// "no silent time-stretch" invariant, so the assertions here are the checkable
// proof that the forbidden stretch bit is never set and that native-length insert
// carries no tempo bits.

#include "../src/core/capture/insert_plan.h"

#include <cstdio>
#include <initializer_list>

using namespace reasampler;
using namespace reasampler::capture;

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

// The tempo bits, mirrored here so the tests assert against literal expected
// values independently of the .cpp's private constants (a test that reused the
// implementation's constants would be tautological).
constexpr int STRETCH_FIT = 4;
constexpr int MATCH_1X    = 8;
constexpr int MATCH_HALF  = 16;
constexpr int MATCH_DBL   = 32;
constexpr int NO_PITCH    = 64;

static void testDefaultIsCurrentTrackNativeLength() {
    // Defaults: current track (base 0), no conform, pitch preserved.
    InsertOptions opts;
    const int mode = computeInsertMode(opts);
    CHECK(mode == 0);                       // base 0 only, no other bits
    CHECK((mode & STRETCH_FIT) == 0);       // never stretch-to-time-sel
    CHECK((mode & MATCH_1X) == 0);          // no tempo bits at native length
    CHECK((mode & MATCH_HALF) == 0);
    CHECK((mode & MATCH_DBL) == 0);
    CHECK((mode & NO_PITCH) == 0);
}

static void testCurrentTrackBaseIsZero() {
    InsertOptions opts;
    opts.target = InsertTarget::CurrentTrack;
    const int mode = computeInsertMode(opts);
    CHECK((mode & 3) == 0);                 // base 0 = add to current track
    CHECK((mode & STRETCH_FIT) == 0);
    CHECK((mode & (MATCH_1X | MATCH_HALF | MATCH_DBL | NO_PITCH)) == 0);
}

static void testConform1xSetsOnlyMatchBit() {
    InsertOptions opts;                      // current track base 0 (default)
    opts.conform = TempoConform::Ratio1x;
    const int mode = computeInsertMode(opts);
    CHECK((mode & MATCH_1X) == MATCH_1X);    // the 1x match bit is set
    CHECK((mode & 3) == 0);                  // base target: current track (0)
    CHECK((mode & STRETCH_FIT) == 0);        // still never the stretch bit
    CHECK((mode & (MATCH_HALF | MATCH_DBL)) == 0);  // no other ratio bits
    CHECK((mode & NO_PITCH) == 0);           // pitch preserved by default
}

static void testConformHalfAndDoubleRatios() {
    InsertOptions half;
    half.conform = TempoConform::RatioHalf;
    CHECK((computeInsertMode(half) & MATCH_HALF) == MATCH_HALF);
    CHECK((computeInsertMode(half) & (MATCH_1X | MATCH_DBL)) == 0);

    InsertOptions dbl;
    dbl.conform = TempoConform::RatioDouble;
    CHECK((computeInsertMode(dbl) & MATCH_DBL) == MATCH_DBL);
    CHECK((computeInsertMode(dbl) & (MATCH_1X | MATCH_HALF)) == 0);
}

static void testPreservePitchGatesTheNoPitchBit() {
    // preservePitch=false only takes effect when a tempo match is active.
    InsertOptions conformShiftPitch;
    conformShiftPitch.conform = TempoConform::Ratio1x;
    conformShiftPitch.preservePitch = false;
    CHECK((computeInsertMode(conformShiftPitch) & NO_PITCH) == NO_PITCH);

    InsertOptions conformKeepPitch;
    conformKeepPitch.conform = TempoConform::Ratio1x;
    conformKeepPitch.preservePitch = true;
    CHECK((computeInsertMode(conformKeepPitch) & NO_PITCH) == 0);

    // preservePitch=false with NO conform must NOT set the pitch bit (nothing to
    // pitch-shift; the bit is meaningless and would be spurious).
    InsertOptions noConformNoPitch;
    noConformNoPitch.conform = TempoConform::None;
    noConformNoPitch.preservePitch = false;
    CHECK((computeInsertMode(noConformNoPitch) & NO_PITCH) == 0);
    CHECK(computeInsertMode(noConformNoPitch) == 0);  // base 0 (current track), nothing else
}

static void testDefaultActionIsCurrentTrackBase0() {
    // The default InsertOptions must compute base 0 (current track) so that both
    // insert actions (default + conform) target the user's selected track(s), not
    // a new track. This is the wired M6 insert-target change.
    InsertOptions defaultOpts;
    CHECK(defaultOpts.target == InsertTarget::CurrentTrack);
    CHECK((computeInsertMode(defaultOpts) & 3) == 0);  // base 0

    InsertOptions conformOpts;
    conformOpts.conform = TempoConform::Ratio1x;
    CHECK((computeInsertMode(conformOpts) & 3) == 0);  // conform variant also base 0
}

static void testStretchBitNeverSetAcrossAllOptions() {
    // Exhaustively enumerate every option combination and assert the forbidden
    // stretch-to-time-selection bit is absent from every computed mode. This is
    // the machine-checkable form of the "no silent time-stretch" invariant.
    const InsertTarget targets[] = {InsertTarget::NewTrack, InsertTarget::CurrentTrack};
    const TempoConform conforms[] = {TempoConform::None, TempoConform::Ratio1x,
                                     TempoConform::RatioHalf, TempoConform::RatioDouble};
    for (InsertTarget t : targets)
        for (TempoConform c : conforms)
            for (bool pp : {true, false}) {
                InsertOptions o;
                o.target = t; o.conform = c; o.preservePitch = pp;
                CHECK((computeInsertMode(o) & kStretchToTimeSelBit) == 0);
            }
}

int main() {
    testDefaultIsCurrentTrackNativeLength();
    testCurrentTrackBaseIsZero();
    testConform1xSetsOnlyMatchBit();
    testConformHalfAndDoubleRatios();
    testPreservePitchGatesTheNoPitchBit();
    testDefaultActionIsCurrentTrackBase0();
    testStretchBitNeverSetAcrossAllOptions();

    if (g_fail == 0) std::printf("insert_plan: all tests passed\n");
    else std::printf("insert_plan: %d FAILED\n", g_fail);
    return g_fail ? 1 : 0;
}
