//! Macros to make it easier to work with rate-limited logs.

/// Declare an instance of a `Loggable` that uses a given counter
/// type and logs a fixed string.
///
/// # Example
///
/// ```
/// use tor_log_ratelim::{declare_loggable, ProblemCount};
///
/// declare_loggable!{
///     pub struct MyLoggable(ProblemCount)
///        { :WARN "Unable to connect to filesystem" }
///     else
///        { :INFO "No filesystem problems encountered"}
/// }
/// ```
///
/// Note that you can omit `:WARN` and `INFO` in the example above; those
/// tracing levels are the default.
//
// TODO: We may want the ability to add other fields to the Loggable and then
// use them in our messages.
#[macro_export]
macro_rules! declare_loggable {
    {
        $v:vis struct $id:ident($c:ty)
        { $(:$lvl_bad:ident)? $msg_bad:expr }
        else
        { $(:$lvl_ok:ident)? $msg_ok:expr}
    } =>
    {
      $v struct $id($crate::CountedEvent<$c>);
      impl $crate::Loggable for $id {
        fn flush(&mut self, summarizing: Option<std::time::Duration>) -> $crate::Activity {
            use $crate::{Activity, Counter, x::tracing};
            let activity = self.0.activity();
            // NOTE: It would be better to have a centralized implementation for this
            // kind of Loggable, but I think that would break some aspects of
            // `tracing` by making the message look like it was originating from
            // another crate.  Maybe we can do better?
            if activity == Activity::Active {
                tracing::event!(
                    $crate::declare_loggable!(@level $(:$lvl_bad)? WARN),
                    "{}", self.0.display(summarizing));
            } else {
                tracing::event!(
                    $crate::declare_loggable!(@level $(:$lvl_ok)? INFO),
                    "{}", self.0.display(summarizing));
            }
            self.0.reset();
            activity
        }
      }
      #[allow(dead_code)]
      // TODO : Add documentation.
      impl $id {
        $v fn new() -> Self {
            Self($crate::CountedEvent::new($msg_bad, $msg_ok))
        }
        $v fn bad(&mut self) { use $crate::Counter; self.0.bad(); }
        $v fn ok(&mut self) { use $crate::Counter; self.0.ok(); }
      }
    };

    // helpers to unpack optional `:LEVEL`.
    { @level $dflt:ident } => { $crate::x::tracing::Level::$dflt };
    { @level :$provided:ident $dflt:ident } => { $crate::x::tracing::Level::$provided };
}

/// Log a given problem message with rate limits; log another message when the
/// first message has not occurred for a while.
///
/// # Example
///
/// ```
/// use tor_log_ratelim::log_ratelim;
///
/// log_ratelim! {
///    { :WARN "Kilroy was here!" }
///    else
///    { :INFO "Haven't seen Kilroy in a while" }
/// }
/// ```
///
/// Note that you can omit `:WARN` and `INFO` in the example above; those
/// tracing levels are the default.
#[macro_export]
macro_rules! log_ratelim {
    {
      { $(:$lvl_bad:ident)? $msg_bad:expr }
      else
      { $(:$lvl_ok:ident)? $msg_ok:expr }
    } => {
        $crate::declare_loggable!{
            struct Lg($crate::Count) { $(:$lvl_bad)? $msg_bad } else { $(:$lvl_ok)? $msg_ok }
        }
       static LG: $crate::x::Lazy<std::sync::Arc<$crate::RateLim<Lg>>> =
       $crate::x::Lazy::new(|| $crate::RateLim::new(Lg::new()));

       LG.event(|c| c.bad());
    }
}

/// Log a given message conditionally; if we don't log it, treat it as a problem
/// that did not occur.
///
/// # Example
///
/// ```
/// use tor_log_ratelim::log_cond_ratelim;
///
/// # let kilroy_seen = true;
/// log_cond_ratelim! {
///    kilroy_seen =>
///      { :WARN "Kilroy was here!" }
///    else
///      { :INFO "Haven't seen Kilroy in a while" }
/// }
/// ```
///
/// Note that you can omit `:WARN` and `INFO` in the example above; those
/// tracing levels are the default.
#[macro_export]
macro_rules! log_cond_ratelim {
    {
      $cond:expr =>
        { $(: $lvl_bad:ident)? $msg_bad:expr }
      else
        { $(: $lvl_ok:ident )? $msg_ok:expr }
    } => {
        $crate::declare_loggable!{
            struct Lg($crate::ProblemCount) { $(:$lvl_bad)? $msg_bad } else { $(:$lvl_ok)? $msg_ok }
        }
       static LG: $crate::x::Lazy<std::sync::Arc<$crate::RateLim<Lg>>> =
          $crate::x::Lazy::new(|| $crate::RateLim::new(Lg::new()));

       if $cond {
         LG.event(|c| c.bad());
       } else {
         LG.nonevent(|c| c.ok());
       }
    }
}
