auto_bench! {

use ::criterion::{Criterion, black_box};

#[cfg(not(ditto_internal))]
compile_error!("Please enable the `DITTO_INTERNAL=1` features");

use ::dittokit::{
    internal::test_helpers::{
        get_ditto,
        temp_persistence_dir,
    },
    prelude::*,
};

#[derive(Debug, ::serde::Serialize)]
struct UserDoc<'__> {
    generation: u32,

    #[serde(skip_serializing_if = "Option::is_none")]
    payload: Option<&'__ [u8]>,

    #[serde(rename = "_id")]
    id: String,
}

#[bench]
fn empty_live_query (bench_name: &'_ str, c: &'_ mut Criterion)
{
    let mut c = c.benchmark_group(bench_name);
    c.plot_config(
        ::criterion::PlotConfiguration::default()
            .summary_scale(::criterion::AxisScale::Logarithmic)
    );

    let (live_query_handler, mut wait_for_live_query_end); {
        let (tx, rx) = ::std::sync::mpsc::sync_channel(0);
        wait_for_live_query_end = move || rx.recv().expect("Closed channel");
        live_query_handler = move /* tx */ || {
            let signal_live_query_end = {
                let tx = tx.clone();
                move || tx.send(()).expect("Closed channel")
            };
            move |docs: Vec<_>, event| match event {
                | LiveQueryEvent::Initial => {
                    // dbg!(&docs[.. ::std::cmp::min(500, docs.len())]);
                },
                | LiveQueryEvent::Update {
                    old_documents, insertions, deletions, updates, moves,
                } => {
                    // signal that exactly one live-query has run.
                    signal_live_query_end();
                },
            }
        };
    }
    for &(kind, max_power_of_10, ref payload) in &[
        ("small", 4, None),
        ("average", 3, Some(vec![0; 50])),
    ]
    {
        let payload = payload.as_deref();
        let powers_of_ten =
            ::std::iter::successors(
                Some((0, 1_u32)),
                |&(_, c)| Some((c, c.checked_mul(10)?)),
            )
            .take(1 + max_power_of_10)
        ;
        // start with a fresh dittokit instance
        let ref temp_dir = temp_persistence_dir();
        let temp_dir = dbg!(temp_dir.path());
        let ref mut ditto = get_ditto(temp_dir);
        let ref mut collection = ditto.store().collection("foo");

        // iteratively query a db that gets orders of magnitudes bigger.
        let mut last_gen = 1;
        powers_of_ten.for_each(|(prev, cur)| {
            ditto.store().with_batched_write(|mut snapshot| {
                let mut collection = snapshot.collection("foo\0");
                let id = &f!("_{i}");
                let user_doc = UserDoc { generation: cur, payload, id };
                (prev .. cur).for_each(|i| {
                    let prog = 100 * i;
                    if prog % cur == 0 {
                        eprintln_f!(
                            "Inserted {:2}% of {cur} documents.",
                            prog / cur
                        );
                    }
                    collection.upsert(user_doc);
                });
                snapshot.commit_changes()
            });
            let pending_op = collection.find_all();
            let _query = pending_op.observe_local(live_query_handler());
            let _sub = pending_op.subscribe();

            let mut doc_i = (prev..cur).cycle();
            c.bench_function(f!("{kind}_{cur}"), |b| b.iter(|| {
                // Keep rewriting documents that we know are there so that the total size
                // of the db doesn't change as the benchmark progresses.
                // The live query will still fire as the document summaries will differ.
                let i = doc_i.next().unwrap();
                let id = &f!("_{i}");
                collection.upsert(UserDoc { generation: cur, payload, id });
                wait_for_live_query_end();
            }));
            last_gen = cur;
        }); // drop(_query) <=> query.stop()

        // Query 10% of the final db.
        let ref query = f!("generation != {last_gen}");
        let docs =
            collection
                .find(query)
                .exec()
                .into_iter()
                .map(|doc| doc.to_untyped_json_value())
                .collect::<Vec<_>>()
        ;
        // dbg!(&docs[.. 10]);
        // dbg!(&docs[(docs.len() - 10) ..]);
        assert_eq!(docs.len(), cast::usize(last_gen) / 10);
        let pending_op = collection.find_all();
        let _query = pending_op.observe_local(live_query_handler());
        let _sub = pending_op.subscribe();
        let ref name =
            f!("{kind}_filter_{}_out_of_{last_gen}", last_gen / 10)
        ;

        // Keep rewriting a dummy document that shows up in the live query,
        // so that the total size of the db doesn't change as the benchmark progresses.
        // The live query will still fire as the document summaries will differ.
        c.bench_function(name, |b| b.iter(|| {
            collection.upsert(UserDoc { generation: last_gen, payload, id: "_".to_string() });
            wait_for_live_query_end();
        }));
    }
    c.finish();
}

} // auto_bench!
