use criterion::{criterion_group, criterion_main, Criterion};

#[cfg(feature = "test_utils")]
mod run {
    use std::hint::black_box;
    use std::sync::Arc;

    use super::*;
    use bench_utils::TextAction;
    use loro_common::LoroValue;
    use loro_internal::{cursor::PosType, loro::ExportMode, LoroDoc};

    pub fn b4(c: &mut Criterion) {
        let actions = bench_utils::get_automerge_actions();
        let mut b = c.benchmark_group("refactored direct_apply");
        b.sample_size(10);
        b.bench_function("B4", |b| {
            b.iter(|| {
                let loro = LoroDoc::default();
                let text = loro.get_text("text");
                let mut txn = loro.txn().unwrap();

                for TextAction { pos, ins, del } in actions.iter() {
                    text.delete_with_txn(&mut txn, *pos, *del, PosType::Unicode)
                        .unwrap();
                    text.insert_with_txn(&mut txn, *pos, ins, PosType::Unicode)
                        .unwrap();
                }
            })
        });

        b.bench_function("B4 with 100K actors history", |b| {
            let store = LoroDoc::default();
            for i in 0..100_000 {
                store.set_peer_id(i).unwrap();
                let list = store.get_list("list");
                let value: LoroValue = i.to_string().into();
                let mut txn = store.txn().unwrap();
                list.insert_with_txn(&mut txn, 0, value).unwrap();
                txn.commit().unwrap();
            }

            let update = store.export(ExportMode::Snapshot).unwrap();
            drop(store);
            b.iter_batched(
                || {
                    let loro = LoroDoc::default();
                    loro.import(&update).unwrap();
                    loro
                },
                |loro| {
                    let text = loro.get_text("text");
                    let mut txn = loro.txn().unwrap();

                    for TextAction { pos, ins, del } in actions.iter() {
                        text.delete_with_txn(&mut txn, *pos, *del, PosType::Unicode)
                            .unwrap();
                        text.insert_with_txn(&mut txn, *pos, ins, PosType::Unicode)
                            .unwrap();
                    }
                },
                criterion::BatchSize::SmallInput,
            )
        });

        b.bench_function("B4 Obs", |b| {
            b.iter(|| {
                let loro = LoroDoc::default();
                let text = loro.get_text("text");
                let _g = loro.subscribe_root(Arc::new(move |event| {
                    black_box(event);
                }));
                let mut txn = loro.txn().unwrap();
                for TextAction { pos, ins, del } in actions.iter() {
                    text.delete_with_txn(&mut txn, *pos, *del, PosType::Unicode)
                        .unwrap();
                    text.insert_with_txn(&mut txn, *pos, ins, PosType::Unicode)
                        .unwrap();
                }
            })
        });

        b.bench_function("B4 encode snapshot", |b| {
            let loro = LoroDoc::default();
            let text = loro.get_text("text");

            let mut n = 0;
            let mut txn = loro.txn().unwrap();
            for TextAction { pos, ins, del } in actions.iter() {
                if n == 10 {
                    n = 0;
                    drop(txn);
                    txn = loro.txn().unwrap();
                }
                n += 1;
                text.delete_with_txn(&mut txn, *pos, *del, PosType::Unicode)
                    .unwrap();
                text.insert_with_txn(&mut txn, *pos, ins, PosType::Unicode)
                    .unwrap();
            }
            txn.commit().unwrap();

            b.iter(|| {
                loro.export(ExportMode::Snapshot).unwrap();
            });
        });

        b.bench_function("B4 encode updates", |b| {
            let loro = LoroDoc::default();
            let text = loro.get_text("text");

            let mut n = 0;
            let mut txn = loro.txn().unwrap();
            for TextAction { pos, ins, del } in actions.iter() {
                if n == 10 {
                    n = 0;
                    drop(txn);
                    txn = loro.txn().unwrap();
                }
                n += 1;
                text.delete_with_txn(&mut txn, *pos, *del, PosType::Unicode)
                    .unwrap();
                text.insert_with_txn(&mut txn, *pos, ins, PosType::Unicode)
                    .unwrap();
            }
            txn.commit().unwrap();

            b.iter(|| {
                loro.export(ExportMode::all_updates()).unwrap();
            });
        });

        b.bench_function("B4 decode snapshot", |b| {
            let loro = LoroDoc::default();
            let text = loro.get_text("text");
            let mut n = 0;
            let mut txn = loro.txn().unwrap();
            for TextAction { pos, ins, del } in actions.iter() {
                if n == 10 {
                    n = 0;
                    drop(txn);
                    txn = loro.txn().unwrap();
                }
                n += 1;
                text.delete_with_txn(&mut txn, *pos, *del, PosType::Unicode)
                    .unwrap();
                text.insert_with_txn(&mut txn, *pos, ins, PosType::Unicode)
                    .unwrap();
            }
            txn.commit().unwrap();

            let data = loro.export(ExportMode::Snapshot).unwrap();
            b.iter(|| {
                let l = LoroDoc::new();
                l.import(&data).unwrap();
            });
        });

        b.bench_function("B4 import updates", |b| {
            let loro = LoroDoc::default();
            let text = loro.get_text("text");

            let mut n = 0;
            let mut txn = loro.txn().unwrap();
            for TextAction { pos, ins, del } in actions.iter() {
                if n == 10 {
                    n = 0;
                    drop(txn);
                    txn = loro.txn().unwrap();
                }
                n += 1;
                text.delete_with_txn(&mut txn, *pos, *del, PosType::Unicode)
                    .unwrap();
                text.insert_with_txn(&mut txn, *pos, ins, PosType::Unicode)
                    .unwrap();
            }
            txn.commit().unwrap();

            let data = loro.export(ExportMode::all_updates()).unwrap();
            b.iter(|| {
                let l = LoroDoc::new();
                l.import(&data).unwrap();
            });
        });

        // b.bench_function("B4 utf16", |b| {
        //     b.iter(|| {
        //         let loro = LoroDoc::new();
        //         let text = loro.get_text("text");
        //         let mut txn = loro.txn().unwrap();

        //         for TextAction { pos, ins, del } in actions.iter() {
        //             text.delete_utf16(&mut txn, *pos, *del).unwrap();
        //             text.insert_utf16(&mut txn, *pos, ins).unwrap();
        //         }
        //     })
        // });

        b.bench_function("B4_Per100_Txn", |b| {
            b.iter(|| {
                let loro = LoroDoc::default();
                let text = loro.get_text("text");
                let mut n = 0;
                let mut txn = loro.txn().unwrap();
                for TextAction { pos, ins, del } in actions.iter() {
                    if n == 100 {
                        n = 0;
                        drop(txn);
                        txn = loro.txn().unwrap();
                    }
                    n += 1;
                    text.delete_with_txn(&mut txn, *pos, *del, PosType::Unicode)
                        .unwrap();
                    text.insert_with_txn(&mut txn, *pos, ins, PosType::Unicode)
                        .unwrap();
                }
            })
        });

        b.bench_function("B4 One Op One Txn", |b| {
            b.iter(|| {
                let loro = LoroDoc::default();
                let text = loro.get_text("text");
                {
                    for TextAction { pos, ins, del } in actions.iter() {
                        let mut txn = loro.txn().unwrap();
                        text.delete_with_txn(&mut txn, *pos, *del, PosType::Unicode)
                            .unwrap();
                        text.insert_with_txn(&mut txn, *pos, ins, PosType::Unicode)
                            .unwrap();
                        txn.commit().unwrap();
                    }
                }
            })
        });

        b.bench_function("B4 One Op One Txn Obs", |b| {
            b.iter(|| {
                let loro = LoroDoc::default();
                let text = loro.get_text("text");
                let _g = loro.subscribe_root(Arc::new(move |event| {
                    black_box(event);
                }));
                {
                    for TextAction { pos, ins, del } in actions.iter() {
                        let mut txn = loro.txn().unwrap();
                        text.delete_with_txn(&mut txn, *pos, *del, PosType::Unicode)
                            .unwrap();
                        text.insert_with_txn(&mut txn, *pos, ins, PosType::Unicode)
                            .unwrap();
                        txn.commit().unwrap();
                    }
                }
            })
        });

        b.bench_function("B4DirectSync", |b| {
            b.iter(|| {
                let loro = LoroDoc::default();
                let loro_b = LoroDoc::default();
                let text = loro.get_text("text");
                for TextAction { pos, ins, del } in actions.iter() {
                    {
                        let mut txn = loro.txn().unwrap();
                        text.delete_with_txn(&mut txn, *pos, *del, PosType::Unicode)
                            .unwrap();
                        text.insert_with_txn(&mut txn, *pos, ins, PosType::Unicode)
                            .unwrap();
                    }

                    loro_b
                        .import(
                            &loro
                                .export(ExportMode::updates(&loro_b.oplog_vv()))
                                .unwrap(),
                        )
                        .unwrap();
                }
            })
        });

        drop(b);
        let mut b = c.benchmark_group("refactored-sync");
        b.bench_function("B4Parallel", |b| {
            b.iter(|| {
                let loro = LoroDoc::default();
                let loro_b = LoroDoc::default();
                let text = loro.get_text("text");
                let text2 = loro_b.get_text("text");
                let mut i = 0;
                for TextAction { pos, ins, del } in actions.iter() {
                    let pos = *pos;
                    let del = *del;
                    i += 1;
                    if i > 1000 {
                        break;
                    }

                    {
                        let mut txn = loro.txn().unwrap();
                        text.delete_with_txn(&mut txn, pos, del, PosType::Unicode)
                            .unwrap();
                        text.insert_with_txn(&mut txn, pos, ins, PosType::Unicode)
                            .unwrap();
                    }

                    {
                        let mut txn = loro_b.txn().unwrap();
                        text2
                            .delete_with_txn(&mut txn, pos, del, PosType::Unicode)
                            .unwrap();
                        text2
                            .insert_with_txn(&mut txn, pos, ins, PosType::Unicode)
                            .unwrap();
                    }
                    loro_b
                        .import(
                            &loro
                                .export(ExportMode::updates(&loro_b.oplog_vv()))
                                .unwrap(),
                        )
                        .unwrap();
                    loro.import(
                        &loro_b
                            .export(ExportMode::updates(&loro.oplog_vv()))
                            .unwrap(),
                    )
                    .unwrap();
                }
            })
        });

        let b = b.sample_size(10);
        b.bench_function("DecodeUpdates B4Parallel", |b| {
            let loro = LoroDoc::default();
            let loro_b = LoroDoc::default();
            let text = loro.get_text("text");
            let text2 = loro_b.get_text("text");
            for TextAction { pos, ins, del } in actions.iter() {
                let pos = *pos;
                let del = *del;
                {
                    let mut txn = loro.txn().unwrap();
                    text.delete_with_txn(&mut txn, pos, del, PosType::Unicode)
                        .unwrap();
                    text.insert_with_txn(&mut txn, pos, ins, PosType::Unicode)
                        .unwrap();
                }

                {
                    let mut txn = loro_b.txn().unwrap();
                    text2
                        .delete_with_txn(&mut txn, pos, del, PosType::Unicode)
                        .unwrap();
                    text2
                        .insert_with_txn(&mut txn, pos, ins, PosType::Unicode)
                        .unwrap();
                }
                loro_b
                    .import(
                        &loro
                            .export(ExportMode::updates(&loro_b.oplog_vv()))
                            .unwrap(),
                    )
                    .unwrap();
                loro.import(
                    &loro_b
                        .export(ExportMode::updates(&loro.oplog_vv()))
                        .unwrap(),
                )
                .unwrap();
            }
            let data = loro.export(ExportMode::all_updates()).unwrap();
            b.iter(|| {
                let loro = LoroDoc::default();
                loro.import(&data).unwrap();
            })
        });
        b.bench_function("DecodeSnapshot B4Parallel", |b| {
            let loro = LoroDoc::default();
            let loro_b = LoroDoc::default();
            let text = loro.get_text("text");
            let text2 = loro_b.get_text("text");
            for TextAction { pos, ins, del } in actions.iter() {
                let pos = *pos;
                let del = *del;
                {
                    let mut txn = loro.txn().unwrap();
                    text.delete_with_txn(&mut txn, pos, del, PosType::Unicode)
                        .unwrap();
                    text.insert_with_txn(&mut txn, pos, ins, PosType::Unicode)
                        .unwrap();
                }

                {
                    let mut txn = loro_b.txn().unwrap();
                    text2
                        .delete_with_txn(&mut txn, pos, del, PosType::Unicode)
                        .unwrap();
                    text2
                        .insert_with_txn(&mut txn, pos, ins, PosType::Unicode)
                        .unwrap();
                }
                loro_b
                    .import(
                        &loro
                            .export(ExportMode::updates(&loro_b.oplog_vv()))
                            .unwrap(),
                    )
                    .unwrap();
                loro.import(
                    &loro_b
                        .export(ExportMode::updates(&loro.oplog_vv()))
                        .unwrap(),
                )
                .unwrap();
            }
            let data = loro.export(ExportMode::Snapshot).unwrap();
            b.iter(|| {
                let loro = LoroDoc::default();
                loro.import(&data).unwrap();
            })
        });
    }
}
pub fn dumb(_c: &mut Criterion) {}

#[cfg(feature = "test_utils")]
criterion_group!(benches, run::b4);
#[cfg(not(feature = "test_utils"))]
criterion_group!(benches, dumb);
criterion_main!(benches);
