pub mod support;

/// Measure given expression usage time
///
/// If expression is early return, Time will not be measured.
/// It occur when we use `?` in expression.
/// Some kind of that expression can be measured by moving `?` out of expression.
///
/// For example
///
/// This is async method that return `Result`
/// ```rustc
/// async fn get() -> Result<(), ()> {}
/// ```
/// Change this
///     `measure_time!("TAG", get().await?)`
/// To this
///     `measure_time!("TAG", get().await)?`
/// Will measure time correctly.
///
/// NOTE:
///     - Use `;` as a unit separator cause rustfmt at call site not working properly.
///     - `$tag` can be anything that implement `std::fmt::Display`
#[macro_export]
macro_rules! measure_time {
    (@init_start) => {
        if ::log::log_enabled!(::log::Level::Debug) {
            Some(::std::time::Instant::now())
        } else {
            None
        }
    };
    // Auto unit implementation
    (@as_auto $tag:expr, $expr:expr) => {{
        let start = measure_time!(@init_start);
        let value = $expr;
        if let Some(start) = start {
            ::log::debug!(
                "{} in {}",
                $tag,
                $crate::_macros::support::DisplayWithAutoUnit::from(start),
            );
        }
        value
    }};
    // Custom unit implementation
    (@as_unit $unit:literal, $as_unit:ident, $tag:expr, $expr:expr) => {{
        let start = measure_time!(@init_start);
        let value = $expr;
        if let Some(start) = start {
            ::log::debug!(
                "{tag} in {usage} {unit}",
                tag = $tag,
                usage = start.elapsed().$as_unit(),
                unit = $unit,
            );
        }
        value
    }};

    // We usually use this variant
    ($tag:expr, $expr:expr) => { measure_time!(@as_auto $tag, $expr) };
    // Use following variants when custom unit is desire
    (MILLIS, $tag:expr, $expr:expr) => { measure_time!(@as_unit "ms", as_millis, $tag, $expr) };
    (MICROS, $tag:expr, $expr:expr) => { measure_time!(@as_unit "µs", as_micros, $tag, $expr) };
    (NANOS,  $tag:expr, $expr:expr) => { measure_time!(@as_unit "ns", as_nanos,  $tag, $expr) };
    (SECS,   $tag:expr, $expr:expr) => { measure_time!(@as_unit "s",  as_secs,   $tag, $expr) };
}
