use criterion::Bencher;

/// Sequential write batch into a long-lived collection.
/// `put_one(i)` performs one put for monotonically increasing logical index `i`.
/// `sync()` runs once per iteration (flush for Bitcask, no-op for Fixed).
/// The counter grows monotonically across all Criterion iterations (warm-up +
/// measurement), so the database accumulates entries over the run; compaction is
/// never triggered between iterations.
pub fn write_seq<P, S>(b: &mut Bencher, batch: u64, mut put_one: P, mut sync: S)
where
    P: FnMut(u64),
    S: FnMut(),
{
    let mut counter = 0u64;
    b.iter(|| {
        for _ in 0..batch {
            put_one(counter);
            counter += 1;
        }
        sync();
    });
}

/// Random write batch using a precomputed key list (so RNG cost is excluded
/// from the measured loop). `put_one(k)` puts one logical key `k`.
pub fn write_rand<P, S>(b: &mut Bencher, keys: &[u64], mut put_one: P, mut sync: S)
where
    P: FnMut(u64),
    S: FnMut(),
{
    b.iter(|| {
        for &k in keys {
            put_one(k);
        }
        sync();
    });
}

/// Generic op batch: run `op()` `batch` times per iteration. Covers
/// rand-read-hot, rand-read-cold, and overwrite — the caller closure does the
/// key selection and the actual get/put.
pub fn op_batch<F>(b: &mut Bencher, batch: u64, mut op: F)
where
    F: FnMut(),
{
    b.iter(|| {
        for _ in 0..batch {
            op();
        }
    });
}

/// Sequential read batch. The runner passes a monotonically increasing index
/// (`offset + j`) that grows without bound across iterations; the CALLER must
/// wrap it against the populated range (e.g. `i % NUM_ENTRIES`) inside `read`,
/// otherwise reads run past the keyspace and degrade to misses.
pub fn read_seq<R>(b: &mut Bencher, batch: u64, mut read: R)
where
    R: FnMut(u64),
{
    let mut offset = 0u64;
    b.iter(|| {
        for j in 0..batch {
            read(offset + j);
        }
        offset += batch;
    });
}

/// Mixed read/write batch: `reads` calls to `read()` then `writes` calls to
/// `write(seq)` where `seq` increments across the whole run.
/// Note: reads and writes run sequentially within a batch (all reads, then all writes), not truly interleaved.
pub fn mix_batch<R, W>(b: &mut Bencher, reads: u64, writes: u64, mut read: R, mut write: W)
where
    R: FnMut(),
    W: FnMut(u64),
{
    let mut write_seq = 0u64;
    b.iter(|| {
        for _ in 0..reads {
            read();
        }
        for _ in 0..writes {
            write(write_seq);
            write_seq += 1;
        }
    });
}
