//! Compares the create/destroy overhead of `events` with other similar libraries.
//!
//! * `OnceEvent` (in different threading and binding modes)
//! * `oneshot::channel()`
//! * `futures::channel::oneshot::channel()`
//!
//! In each benchmark, a channel/event is created (as well as a sender/receiver bound, where
//! applicable) and then immediately destroyed.

#![allow(
    missing_docs,
    reason = "No need for API documentation in benchmark code"
)]

use std::mem::MaybeUninit;
use std::pin::pin;
use std::rc::Rc;
use std::sync::Arc;

use alloc_tracker::{Allocator, Session};
use criterion::{Criterion, criterion_group, criterion_main};
use events::{LocalOnceEvent, LocalOnceEventPool, OnceEvent, OnceEventPool};
use many_cpus::ProcessorSet;
use par_bench::{ResourceUsageExt, Run, ThreadPool};

#[global_allocator]
static ALLOCATOR: Allocator<std::alloc::System> = Allocator::system();

criterion_group!(benches, entrypoint);
criterion_main!(benches);

type Payload = u128;

fn entrypoint(c: &mut Criterion) {
    let allocs = Session::new();
    let mut one_thread = ThreadPool::new(ProcessorSet::single());

    let mut group = c.benchmark_group("events_overhead_comparison");

    Run::new()
        .measure_resource_usage("local_once_event_ref", |measure| measure.allocs(&allocs))
        .iter(|_| {
            let event = LocalOnceEvent::<Payload>::new();
            drop(event.bind_by_ref());
        })
        .execute_criterion_on(&mut one_thread, &mut group, "local_once_event_ref");

    Run::new()
        .measure_resource_usage("local_once_event_ref_unchecked", |measure| {
            measure.allocs(&allocs)
        })
        .iter(|_| {
            let event = LocalOnceEvent::<Payload>::new();
            // SAFETY: We know this is the first and only binding of this event
            drop(unsafe { event.bind_by_ref_unchecked() });
        })
        .execute_criterion_on(
            &mut one_thread,
            &mut group,
            "local_once_event_ref_unchecked",
        );

    Run::new()
        .measure_resource_usage("local_once_event_rc", |measure| measure.allocs(&allocs))
        .iter(|_| {
            let event = Rc::new(LocalOnceEvent::<Payload>::new());
            drop(event.bind_by_rc());
        })
        .execute_criterion_on(&mut one_thread, &mut group, "local_once_event_rc");

    Run::new()
        .measure_resource_usage("local_once_event_rc_unchecked", |measure| {
            measure.allocs(&allocs)
        })
        .iter(|_| {
            let event = Rc::new(LocalOnceEvent::<Payload>::new());
            // SAFETY: We know this is the first and only binding of this event
            drop(unsafe { event.bind_by_rc_unchecked() });
        })
        .execute_criterion_on(&mut one_thread, &mut group, "local_once_event_rc_unchecked");

    Run::new()
        .measure_resource_usage("local_once_event_ptr", |measure| measure.allocs(&allocs))
        .iter(|_| {
            let event = pin!(LocalOnceEvent::<Payload>::new());
            // SAFETY: We are immediately dropping the sender/receiver, so `event` outlives them.
            unsafe {
                drop(event.as_ref().bind_by_ptr());
            }
        })
        .execute_criterion_on(&mut one_thread, &mut group, "local_once_event_ptr");

    Run::new()
        .measure_resource_usage("local_once_event_ptr_unchecked", |measure| {
            measure.allocs(&allocs)
        })
        .iter(|_| {
            let event = pin!(LocalOnceEvent::<Payload>::new());
            // SAFETY: We are immediately dropping the sender/receiver, so `event` outlives them.
            unsafe {
                drop(event.as_ref().bind_by_ptr_unchecked());
            }
        })
        .execute_criterion_on(
            &mut one_thread,
            &mut group,
            "local_once_event_ptr_unchecked",
        );

    Run::new()
        .measure_resource_usage("local_once_event_managed", |measure| {
            measure.allocs(&allocs)
        })
        .iter(|_| {
            let (sender, receiver) = LocalOnceEvent::<Payload>::new_managed();
            drop(sender);
            drop(receiver);
        })
        .execute_criterion_on(&mut one_thread, &mut group, "local_once_event_managed");

    Run::new()
        .measure_resource_usage("local_once_event_in_place_ptr", |measure| {
            measure.allocs(&allocs)
        })
        .iter(|_| {
            let mut event_storage = pin!(MaybeUninit::uninit());

            // SAFETY: We keep the event alive until sender/receiver are done and then we have
            // to properly drop the event. Sender/receiver go away immediately, we drop below.
            drop(unsafe { LocalOnceEvent::<Payload>::new_in_place_by_ptr(event_storage.as_mut()) });

            // SAFETY: We are just removing the Pin wrapper so we can drop it because the
            // MaybeUninit type is not designed for pinned usage, so does not understand Pin.
            let event_storage_ref = unsafe { event_storage.get_unchecked_mut() };

            // SAFETY: We initialized it above and have dropped both sender and receiver now.
            unsafe {
                event_storage_ref.assume_init_drop();
            }
        })
        .execute_criterion_on(&mut one_thread, &mut group, "local_once_event_in_place_ptr");

    Run::new()
        .measure_resource_usage("once_event_ref", |measure| measure.allocs(&allocs))
        .iter(|_| {
            let event = OnceEvent::<Payload>::new();
            drop(event.bind_by_ref());
        })
        .execute_criterion_on(&mut one_thread, &mut group, "once_event_ref");

    Run::new()
        .measure_resource_usage("once_event_ref_unchecked", |measure| {
            measure.allocs(&allocs)
        })
        .iter(|_| {
            let event = OnceEvent::<Payload>::new();
            // SAFETY: We know this is the first and only binding of this event
            drop(unsafe { event.bind_by_ref_unchecked() });
        })
        .execute_criterion_on(&mut one_thread, &mut group, "once_event_ref_unchecked");

    Run::new()
        .measure_resource_usage("once_event_arc", |measure| measure.allocs(&allocs))
        .iter(|_| {
            let event = Arc::new(OnceEvent::<Payload>::new());
            drop(event.bind_by_arc());
        })
        .execute_criterion_on(&mut one_thread, &mut group, "once_event_arc");

    Run::new()
        .measure_resource_usage("once_event_arc_unchecked", |measure| {
            measure.allocs(&allocs)
        })
        .iter(|_| {
            let event = Arc::new(OnceEvent::<Payload>::new());
            // SAFETY: We know this is the first and only binding of this event
            drop(unsafe { event.bind_by_arc_unchecked() });
        })
        .execute_criterion_on(&mut one_thread, &mut group, "once_event_arc_unchecked");

    Run::new()
        .measure_resource_usage("once_event_ptr", |measure| measure.allocs(&allocs))
        .iter(|_| {
            let event = pin!(OnceEvent::<Payload>::new());
            // SAFETY: We are immediately dropping the sender/receiver, so `event` outlives them.
            unsafe {
                drop(event.as_ref().bind_by_ptr());
            }
        })
        .execute_criterion_on(&mut one_thread, &mut group, "once_event_ptr");

    Run::new()
        .measure_resource_usage("once_event_ptr_unchecked", |measure| {
            measure.allocs(&allocs)
        })
        .iter(|_| {
            let event = pin!(OnceEvent::<Payload>::new());
            // SAFETY: We are immediately dropping the sender/receiver, so `event` outlives them.
            unsafe {
                drop(event.as_ref().bind_by_ptr_unchecked());
            }
        })
        .execute_criterion_on(&mut one_thread, &mut group, "once_event_ptr_unchecked");

    Run::new()
        .measure_resource_usage("once_event_managed", |measure| measure.allocs(&allocs))
        .iter(|_| {
            let (sender, receiver) = OnceEvent::<Payload>::new_managed();
            drop(sender);
            drop(receiver);
        })
        .execute_criterion_on(&mut one_thread, &mut group, "once_event_managed");

    Run::new()
        .measure_resource_usage("once_event_in_place_ptr", |measure| measure.allocs(&allocs))
        .iter(|_| {
            let mut event_storage = pin!(MaybeUninit::uninit());

            // SAFETY: We keep the event alive until sender/receiver are done and then we have
            // to properly drop the event. Sender/receiver go away immediately, we drop below.
            drop(unsafe { OnceEvent::<Payload>::new_in_place_by_ptr(event_storage.as_mut()) });

            // SAFETY: We are just removing the Pin wrapper so we can drop it because the
            // MaybeUninit type is not designed for pinned usage, so does not understand Pin.
            let event_storage_ref = unsafe { event_storage.get_unchecked_mut() };

            // SAFETY: We initialized it above and have dropped both sender and receiver now.
            unsafe {
                event_storage_ref.assume_init_drop();
            }
        })
        .execute_criterion_on(&mut one_thread, &mut group, "once_event_in_place_ptr");

    Run::new()
        .prepare_thread(|_| LocalOnceEventPool::<Payload>::new())
        .measure_resource_usage("pooled_local_once_event_ref", |measure| {
            measure.allocs(&allocs)
        })
        .iter(|args| {
            drop(args.thread_state().bind_by_ref());
        })
        .execute_criterion_on(&mut one_thread, &mut group, "pooled_local_once_event_ref");

    Run::new()
        .prepare_thread(|_| Rc::new(LocalOnceEventPool::<Payload>::new()))
        .measure_resource_usage("pooled_local_once_event_rc", |measure| {
            measure.allocs(&allocs)
        })
        .iter(|args| {
            drop(args.thread_state().bind_by_rc());
        })
        .execute_criterion_on(&mut one_thread, &mut group, "pooled_local_once_event_rc");

    Run::new()
        .prepare_thread(|_| Box::pin(LocalOnceEventPool::<Payload>::new()))
        .measure_resource_usage("pooled_local_once_event_ptr", |measure| {
            measure.allocs(&allocs)
        })
        .iter(|args| {
            // SAFETY: We are immediately dropping the sender/receiver, so the pool outlives them.
            // The pool is also pinned, as required.
            unsafe {
                drop(args.thread_state().as_ref().bind_by_ptr());
            }
        })
        .execute_criterion_on(&mut one_thread, &mut group, "pooled_local_once_event_ptr");

    Run::new()
        .prepare_thread(|_| OnceEventPool::<Payload>::new())
        .measure_resource_usage("pooled_once_event_ref", |measure| measure.allocs(&allocs))
        .iter(|args| {
            drop(args.thread_state().bind_by_ref());
        })
        .execute_criterion_on(&mut one_thread, &mut group, "pooled_once_event_ref");

    Run::new()
        .prepare_thread(|_| Arc::new(OnceEventPool::<Payload>::new()))
        .measure_resource_usage("pooled_once_event_arc", |measure| measure.allocs(&allocs))
        .iter(|args| {
            drop(args.thread_state().bind_by_arc());
        })
        .execute_criterion_on(&mut one_thread, &mut group, "pooled_once_event_arc");

    Run::new()
        .prepare_thread(|_| Box::pin(OnceEventPool::<Payload>::new()))
        .measure_resource_usage("pooled_once_event_ptr", |measure| measure.allocs(&allocs))
        .iter(|args| {
            // SAFETY: We are immediately dropping the sender/receiver, so the pool outlives them.
            // The pool is also pinned, as required.
            unsafe {
                drop(args.thread_state().as_ref().bind_by_ptr());
            }
        })
        .execute_criterion_on(&mut one_thread, &mut group, "pooled_once_event_ptr");

    Run::new()
        .measure_resource_usage("oneshot_channel", |measure| measure.allocs(&allocs))
        .iter(|_| {
            let (sender, receiver) = oneshot::channel::<Payload>();
            drop(sender);
            drop(receiver);
        })
        .execute_criterion_on(&mut one_thread, &mut group, "oneshot_channel");

    #[expect(clippy::absolute_paths, reason = "being explicit")]
    Run::new()
        .measure_resource_usage("futures_oneshot_channel", |measure| measure.allocs(&allocs))
        .iter(|_| {
            let (sender, receiver) = futures::channel::oneshot::channel::<Payload>();
            drop(sender);
            drop(receiver);
        })
        .execute_criterion_on(&mut one_thread, &mut group, "futures_oneshot_channel");

    group.finish();

    allocs.print_to_stdout();
}
