//! A `MakeVisitor` wrapper that separates formatted fields with a delimiter.
use super::{MakeVisitor, VisitFmt, VisitOutput};

use std::fmt;
use tracing_core::field::{Field, Visit};

/// A `MakeVisitor` wrapper that wraps a visitor that writes formatted output so
/// that a delimiter is inserted between writing formatted field values.
#[derive(Debug, Clone)]
pub struct Delimited<D, V> {
    delimiter: D,
    inner: V,
}

/// A visitor wrapper that inserts a delimiter after the wrapped visitor formats
/// a field value.
#[derive(Debug)]
pub struct VisitDelimited<D, V> {
    delimiter: D,
    seen: bool,
    inner: V,
    err: fmt::Result,
}

// === impl Delimited ===

impl<D, V, T> MakeVisitor<T> for Delimited<D, V>
where
    D: AsRef<str> + Clone,
    V: MakeVisitor<T>,
    V::Visitor: VisitFmt,
{
    type Visitor = VisitDelimited<D, V::Visitor>;
    fn make_visitor(&self, target: T) -> Self::Visitor {
        let inner = self.inner.make_visitor(target);
        VisitDelimited::new(self.delimiter.clone(), inner)
    }
}

impl<D, V> Delimited<D, V> {
    /// Returns a new [`MakeVisitor`] implementation that wraps `inner` so that
    /// it will format each visited field separated by the provided `delimiter`.
    ///
    /// [`MakeVisitor`]: ../trait.MakeVisitor.html
    pub fn new(delimiter: D, inner: V) -> Self {
        Self { delimiter, inner }
    }
}

// === impl VisitDelimited ===

impl<D, V> VisitDelimited<D, V> {
    /// Returns a new [`Visit`] implementation that wraps `inner` so that
    /// each formatted field is separated by the provided `delimiter`.
    ///
    /// [`Visit`]: https://docs.rs/tracing-core/0.1.6/tracing_core/field/trait.Visit.html
    pub fn new(delimiter: D, inner: V) -> Self {
        Self {
            delimiter,
            inner,
            seen: false,
            err: Ok(()),
        }
    }

    fn delimit(&mut self)
    where
        V: VisitFmt,
        D: AsRef<str>,
    {
        if self.err.is_err() {
            return;
        }

        if self.seen {
            self.err = self.inner.writer().write_str(self.delimiter.as_ref());
        }

        self.seen = true;
    }
}

impl<D, V> Visit for VisitDelimited<D, V>
where
    V: VisitFmt,
    D: AsRef<str>,
{
    fn record_i64(&mut self, field: &Field, value: i64) {
        self.delimit();
        self.inner.record_i64(field, value);
    }

    fn record_u64(&mut self, field: &Field, value: u64) {
        self.delimit();
        self.inner.record_u64(field, value);
    }

    fn record_bool(&mut self, field: &Field, value: bool) {
        self.delimit();
        self.inner.record_bool(field, value);
    }

    fn record_str(&mut self, field: &Field, value: &str) {
        self.delimit();
        self.inner.record_str(field, value);
    }

    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
        self.delimit();
        self.inner.record_debug(field, value);
    }
}

impl<D, V> VisitOutput<fmt::Result> for VisitDelimited<D, V>
where
    V: VisitFmt,
    D: AsRef<str>,
{
    fn finish(self) -> fmt::Result {
        self.err?;
        self.inner.finish()
    }
}

impl<D, V> VisitFmt for VisitDelimited<D, V>
where
    V: VisitFmt,
    D: AsRef<str>,
{
    fn writer(&mut self) -> &mut dyn fmt::Write {
        self.inner.writer()
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::field::test_util::*;

    #[test]
    fn delimited_visitor() {
        let mut s = String::new();
        let visitor = DebugVisitor::new(&mut s);
        let mut visitor = VisitDelimited::new(", ", visitor);

        TestAttrs1::with(|attrs| attrs.record(&mut visitor));
        visitor.finish().unwrap();

        assert_eq!(
            s.as_str(),
            "question=\"life, the universe, and everything\", tricky=true, can_you_do_it=true"
        );
    }

    #[test]
    fn delimited_new_visitor() {
        let make = Delimited::new("; ", MakeDebug);

        TestAttrs1::with(|attrs| {
            let mut s = String::new();
            {
                let mut v = make.make_visitor(&mut s);
                attrs.record(&mut v);
            }
            assert_eq!(
                s.as_str(),
                "question=\"life, the universe, and everything\"; tricky=true; can_you_do_it=true"
            );
        });

        TestAttrs2::with(|attrs| {
            let mut s = String::new();
            {
                let mut v = make.make_visitor(&mut s);
                attrs.record(&mut v);
            }
            assert_eq!(
                s.as_str(),
                "question=None; question.answer=42; tricky=true; can_you_do_it=false"
            );
        });
    }
}
