//! Utilities for working with [fields] and [field visitors].
//!
//! [fields]: tracing_core::field
//! [field visitors]: tracing_core::field::Visit
use core::{fmt, marker::PhantomData};
pub use tracing_core::field::Visit;
use tracing_core::{
    span::{Attributes, Record},
    Event,
};
pub mod debug;
pub mod delimited;
pub mod display;

/// Creates new [visitors].
///
/// A type implementing `MakeVisitor` represents a composable factory for types
/// implementing the [`Visit` trait][visitors]. The `MakeVisitor` trait defines
/// a single function, `make_visitor`, which takes in a `T`-typed `target` and
/// returns a type implementing `Visit` configured for that target. A target may
/// be a string, output stream, or data structure that the visitor will record
/// data to, configuration variables that determine the visitor's behavior, or
/// `()` when no input is required to produce a visitor.
///
/// [visitors]: tracing_core::field::Visit
pub trait MakeVisitor<T> {
    /// The visitor type produced by this `MakeVisitor`.
    type Visitor: Visit;

    /// Make a new visitor for the provided `target`.
    fn make_visitor(&self, target: T) -> Self::Visitor;
}

/// A [visitor] that produces output once it has visited a set of fields.
///
/// [visitor]: tracing_core::field::Visit
pub trait VisitOutput<Out>: Visit {
    /// Completes the visitor, returning any output.
    ///
    /// This is called once a full set of fields has been visited.
    fn finish(self) -> Out;

    /// Visit a set of fields, and return the output of finishing the visitor
    /// once the fields have been visited.
    fn visit<R>(mut self, fields: &R) -> Out
    where
        R: RecordFields,
        Self: Sized,
    {
        fields.record(&mut self);
        self.finish()
    }
}

/// Extension trait implemented by types which can be recorded by a [visitor].
///
/// This allows writing code that is generic over `tracing_core`'s
/// [`span::Attributes`][attr], [`span::Record`][rec], and [`Event`][event]
/// types. These types all provide inherent `record` methods that allow a
/// visitor to record their fields, but there is no common trait representing this.
///
/// With `RecordFields`, we can write code like this:
/// ```
/// use tracing_core::field::Visit;
/// # use tracing_core::field::Field;
/// use tracing_subscriber::field::RecordFields;
///
/// struct MyVisitor {
///     // ...
/// }
/// # impl MyVisitor { fn new() -> Self { Self{} } }
/// impl Visit for MyVisitor {
///     // ...
/// # fn record_debug(&mut self, _: &Field, _: &dyn std::fmt::Debug) {}
/// }
///
/// fn record_with_my_visitor<R>(r: R)
/// where
///     R: RecordFields,
/// {
///     let mut visitor = MyVisitor::new();
///     r.record(&mut visitor);
/// }
/// ```
/// [visitor]: tracing_core::field::Visit
/// [attr]: tracing_core::span::Attributes
/// [rec]: tracing_core::span::Record
/// [event]: tracing_core::event::Event
pub trait RecordFields: crate::sealed::Sealed<RecordFieldsMarker> {
    /// Record all the fields in `self` with the provided `visitor`.
    fn record(&self, visitor: &mut dyn Visit);
}

/// Extension trait implemented for all `MakeVisitor` implementations that
/// produce a visitor implementing `VisitOutput`.
pub trait MakeOutput<T, Out>
where
    Self: MakeVisitor<T> + crate::sealed::Sealed<(T, Out)>,
    Self::Visitor: VisitOutput<Out>,
{
    /// Visits all fields in `fields` with a new visitor constructed from
    /// `target`.
    fn visit_with<F>(&self, target: T, fields: &F) -> Out
    where
        F: RecordFields,
    {
        self.make_visitor(target).visit(fields)
    }
}

feature! {
    #![feature = "std"]
    use std::io;

    /// Extension trait implemented by visitors to indicate that they write to an
    /// `io::Write` instance, and allow access to that writer.
    pub trait VisitWrite: VisitOutput<Result<(), io::Error>> {
        /// Returns the writer that this visitor writes to.
        fn writer(&mut self) -> &mut dyn io::Write;
    }
}

/// Extension trait implemented by visitors to indicate that they write to a
/// `fmt::Write` instance, and allow access to that writer.
pub trait VisitFmt: VisitOutput<fmt::Result> {
    /// Returns the formatter that this visitor writes to.
    fn writer(&mut self) -> &mut dyn fmt::Write;
}

/// Extension trait providing `MakeVisitor` combinators.
pub trait MakeExt<T>
where
    Self: MakeVisitor<T> + Sized,
    Self: crate::sealed::Sealed<MakeExtMarker<T>>,
{
    /// Wraps `self` so that any `fmt::Debug` fields are recorded using the
    /// alternate formatter (`{:#?}`).
    fn debug_alt(self) -> debug::Alt<Self> {
        debug::Alt::new(self)
    }

    /// Wraps `self` so that any string fields named "message" are recorded
    /// using `fmt::Display`.
    fn display_messages(self) -> display::Messages<Self> {
        display::Messages::new(self)
    }

    /// Wraps `self` so that when fields are formatted to a writer, they are
    /// separated by the provided `delimiter`.
    fn delimited<D>(self, delimiter: D) -> delimited::Delimited<D, Self>
    where
        D: AsRef<str> + Clone,
        Self::Visitor: VisitFmt,
    {
        delimited::Delimited::new(delimiter, self)
    }
}

// === impl RecordFields ===

impl<'a> crate::sealed::Sealed<RecordFieldsMarker> for Event<'a> {}
impl<'a> RecordFields for Event<'a> {
    fn record(&self, visitor: &mut dyn Visit) {
        Event::record(self, visitor)
    }
}

impl<'a> crate::sealed::Sealed<RecordFieldsMarker> for Attributes<'a> {}
impl<'a> RecordFields for Attributes<'a> {
    fn record(&self, visitor: &mut dyn Visit) {
        Attributes::record(self, visitor)
    }
}

impl<'a> crate::sealed::Sealed<RecordFieldsMarker> for Record<'a> {}
impl<'a> RecordFields for Record<'a> {
    fn record(&self, visitor: &mut dyn Visit) {
        Record::record(self, visitor)
    }
}

impl<'a, F> crate::sealed::Sealed<RecordFieldsMarker> for &'a F where F: RecordFields {}
impl<'a, F> RecordFields for &'a F
where
    F: RecordFields,
{
    fn record(&self, visitor: &mut dyn Visit) {
        F::record(*self, visitor)
    }
}

// === blanket impls ===

impl<T, V, F> MakeVisitor<T> for F
where
    F: Fn(T) -> V,
    V: Visit,
{
    type Visitor = V;
    fn make_visitor(&self, target: T) -> Self::Visitor {
        (self)(target)
    }
}

impl<T, Out, M> crate::sealed::Sealed<(T, Out)> for M
where
    M: MakeVisitor<T>,
    M::Visitor: VisitOutput<Out>,
{
}

impl<T, Out, M> MakeOutput<T, Out> for M
where
    M: MakeVisitor<T>,
    M::Visitor: VisitOutput<Out>,
{
}

impl<T, M> crate::sealed::Sealed<MakeExtMarker<T>> for M where M: MakeVisitor<T> + Sized {}

impl<T, M> MakeExt<T> for M
where
    M: MakeVisitor<T> + Sized,
    M: crate::sealed::Sealed<MakeExtMarker<T>>,
{
}

#[derive(Debug)]
#[doc(hidden)]
pub struct MakeExtMarker<T> {
    _p: PhantomData<T>,
}

#[derive(Debug)]
#[doc(hidden)]
pub struct RecordFieldsMarker {
    _p: (),
}

#[cfg(all(test, feature = "alloc"))]
#[macro_use]
pub(in crate::field) mod test_util {
    use super::*;
    pub(in crate::field) use alloc::string::String;
    use tracing_core::{
        callsite::Callsite,
        field::{Field, Value},
        metadata::{Kind, Level, Metadata},
    };

    pub(crate) struct TestAttrs1;
    pub(crate) struct TestAttrs2;

    impl TestAttrs1 {
        pub(crate) fn with<T>(f: impl FnOnce(Attributes<'_>) -> T) -> T {
            let fieldset = TEST_META_1.fields();
            let values = &[
                (
                    &fieldset.field("question").unwrap(),
                    Some(&"life, the universe, and everything" as &dyn Value),
                ),
                (&fieldset.field("question.answer").unwrap(), None),
                (
                    &fieldset.field("tricky").unwrap(),
                    Some(&true as &dyn Value),
                ),
                (
                    &fieldset.field("can_you_do_it").unwrap(),
                    Some(&true as &dyn Value),
                ),
            ];
            let valueset = fieldset.value_set(values);
            let attrs = tracing_core::span::Attributes::new(&TEST_META_1, &valueset);
            f(attrs)
        }
    }

    impl TestAttrs2 {
        pub(crate) fn with<T>(f: impl FnOnce(Attributes<'_>) -> T) -> T {
            let fieldset = TEST_META_1.fields();
            let none = tracing_core::field::debug(&Option::<&str>::None);
            let values = &[
                (
                    &fieldset.field("question").unwrap(),
                    Some(&none as &dyn Value),
                ),
                (
                    &fieldset.field("question.answer").unwrap(),
                    Some(&42 as &dyn Value),
                ),
                (
                    &fieldset.field("tricky").unwrap(),
                    Some(&true as &dyn Value),
                ),
                (
                    &fieldset.field("can_you_do_it").unwrap(),
                    Some(&false as &dyn Value),
                ),
            ];
            let valueset = fieldset.value_set(values);
            let attrs = tracing_core::span::Attributes::new(&TEST_META_1, &valueset);
            f(attrs)
        }
    }

    struct TestCallsite1;
    static TEST_CALLSITE_1: &'static dyn Callsite = &TestCallsite1;
    static TEST_META_1: Metadata<'static> = tracing_core::metadata! {
        name: "field_test1",
        target: module_path!(),
        level: Level::INFO,
        fields: &["question", "question.answer", "tricky", "can_you_do_it"],
        callsite: TEST_CALLSITE_1,
        kind: Kind::SPAN,
    };

    impl Callsite for TestCallsite1 {
        fn set_interest(&self, _: tracing_core::subscriber::Interest) {
            unimplemented!()
        }

        fn metadata(&self) -> &Metadata<'_> {
            &TEST_META_1
        }
    }

    pub(crate) struct MakeDebug;
    pub(crate) struct DebugVisitor<'a> {
        writer: &'a mut dyn fmt::Write,
        err: fmt::Result,
    }

    impl<'a> DebugVisitor<'a> {
        pub(crate) fn new(writer: &'a mut dyn fmt::Write) -> Self {
            Self {
                writer,
                err: Ok(()),
            }
        }
    }

    impl<'a> Visit for DebugVisitor<'a> {
        fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
            write!(self.writer, "{}={:?}", field, value).unwrap();
        }
    }

    impl<'a> VisitOutput<fmt::Result> for DebugVisitor<'a> {
        fn finish(self) -> fmt::Result {
            self.err
        }
    }

    impl<'a> VisitFmt for DebugVisitor<'a> {
        fn writer(&mut self) -> &mut dyn fmt::Write {
            self.writer
        }
    }

    impl<'a> MakeVisitor<&'a mut dyn fmt::Write> for MakeDebug {
        type Visitor = DebugVisitor<'a>;
        fn make_visitor(&self, w: &'a mut dyn fmt::Write) -> DebugVisitor<'a> {
            DebugVisitor::new(w)
        }
    }
}
