//! Types for counting events and formatting them.  Used to implement
//! instance of [`Loggable`](crate::Loggable).

// TODO: Think about whether logging exact success/failure counts is
// problematic.

use crate::Activity;
use std::borrow::Cow;
use std::fmt::Display;
use std::ops::{Deref, DerefMut};
use std::time::Duration;

/// Trait implemented by objects that count failures (and possibly successes).
pub trait Counter: Display {
    /// Return whether this counter has activity to report.
    fn activity(&self) -> Activity;
    /// Reset this counter to its initial state.
    fn reset(&mut self);
    /// Note that a failure has occurred.
    fn bad(&mut self);
    /// Note that a failure had an opportunity to occur, but didn't.
    fn ok(&mut self);
}

/// A [`Counter`] for counting a failure condition.
///
/// When possible, you should use [`ProblemCount`] instead for better output.
/// This type is for cases where it is not feasible or desirable to count how
/// many times an operation succeeded.
#[derive(Copy, Clone, Debug, Default)]
pub struct Count {
    pub n: usize,
}

impl Counter for Count {
    fn activity(&self) -> Activity {
        if self.n == 0 {
            Activity::Dormant
        } else {
            Activity::Active
        }
    }

    fn reset(&mut self) {
        self.n = 0;
    }

    fn bad(&mut self) {
        self.n += 1;
    }

    fn ok(&mut self) {}
}

impl Display for Count {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.n)
    }
}

/// A [`Counter`] for counting a failure condition along with its absence.
#[derive(Copy, Clone, Debug, Default)]
pub struct ProblemCount {
    pub n_bad: usize,
    pub n_ok: usize,
}

impl Counter for ProblemCount {
    fn activity(&self) -> Activity {
        if self.n_bad == 0 {
            Activity::Dormant
        } else {
            Activity::Active
        }
    }

    fn reset(&mut self) {
        self.n_bad = 0;
        self.n_ok = 0;
    }

    fn bad(&mut self) {
        self.n_bad += 1;
    }

    fn ok(&mut self) {
        self.n_ok += 1;
    }
}

impl Display for ProblemCount {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let total = self.n_bad + self.n_ok;
        write!(f, "{}/{}", self.n_bad, total)
    }
}

/// A wrapper around a [`Counter`] that can be used to implement
/// [`Loggable`](crate::Loggable).
///
/// See [`CountedEvent::display`] for an explanation of why we don't implement
/// `Loggable` directly.
pub struct CountedEvent<C> {
    msg_ok: Cow<'static, str>,
    msg_bad: Cow<'static, str>,
    counter: C,
}
impl<C> CountedEvent<C> {
    /// Construct a new `CountedEvent` around a provided counter.
    ///
    /// If the failure condition occurs one or more types, `msg_bad` will be
    /// logged.  If time passes after that without any failures, `msg_ok` will
    /// be logged.
    pub fn new_with_counter<OK, BAD>(msg_ok: OK, msg_bad: BAD, counter: C) -> Self
    where
        OK: Into<Cow<'static, str>>,
        BAD: Into<Cow<'static, str>>,
    {
        Self {
            msg_ok: msg_ok.into(),
            msg_bad: msg_bad.into(),
            counter,
        }
    }
}
impl<C: Default> CountedEvent<C> {
    /// Construct a new `CountedEvent` around a counter with its default value.
    ///
    /// If the failure condition occurs one or more types, `msg_bad` will be
    /// logged.  If time passes after that without any failures, `msg_ok` will
    /// be logged.
    pub fn new<OK, BAD>(msg_ok: OK, msg_bad: BAD) -> Self
    where
        OK: Into<Cow<'static, str>>,
        BAD: Into<Cow<'static, str>>,
    {
        Self::new_with_counter(msg_ok, msg_bad, Default::default())
    }
}
impl<C: Counter> CountedEvent<C> {
    /// Return an object that can be formatted to describe the current state of
    /// this `CountedEvent`: the success or failure, a description of the count,
    /// and of how much time is being summarized.
    ///
    /// This function is useful for implementing [`Loggable`](crate::Loggable).
    /// (We don't have `CountedEvent` implement `Loggable` itself, since we want
    /// the call sites for our trace macros to be in the crate where the event
    /// is actually logged.
    pub fn display(&self, summarizing: Option<Duration>) -> impl std::fmt::Display + '_ {
        EventDisplay(self, summarizing)
    }
}
impl<C> Deref for CountedEvent<C> {
    type Target = C;

    fn deref(&self) -> &Self::Target {
        &self.counter
    }
}
impl<C> DerefMut for CountedEvent<C> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.counter
    }
}

/// Helper to format a `CountedEvent` and a duration.
struct EventDisplay<'a, C>(&'a CountedEvent<C>, Option<Duration>);
impl<'a, C: Counter> Display for EventDisplay<'a, C> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use Activity::*;
        match (self.0.activity(), self.1) {
            // Rate-limiting is disabled; just log the message.
            (Active, None) => write!(f, "{}", self.0.msg_bad),
            // Rate-limiting is disabled; don't log any success.
            (Dormant, None) => Ok(()),
            (Active, Some(d)) => write!(
                f,
                "{} (Problem occurred {} times in the last {})",
                self.0.msg_bad,
                self.0.counter,
                humantime::format_duration(d)
            ),
            (Dormant, Some(d)) => write!(
                f,
                "{} (Problem occurred {} times in the last {})",
                self.0.msg_ok,
                self.0.counter,
                humantime::format_duration(d)
            ),
        }
    }
}
