use core::fmt;
use core::fmt::Display;
use core::fmt::Write as _;

use super::Error;
use super::Formatter;
use super::Result;
use super::QUOTE;

#[derive(Debug)]
pub struct QuotedDisplay<T>(T);

impl<T> Display for QuotedDisplay<&T>
where
    T: Quote + ?Sized,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_char(QUOTE)?;

        self.0.escape(Formatter::new(f)).map_err(|x| x.0)?;

        f.write_char(QUOTE)
    }
}

/// The trait used to quote strings.
pub trait Quote {
    /// Escapes a string using the format described in the [the module-level
    /// documentation][format], without the surrounding quotes.
    ///
    /// This method is only used to provide new implementations of this trait.
    ///
    /// # Errors
    ///
    /// Similar to [`Display::fmt`], this method should fail if and only if the
    /// formatter returns an error. Since quoting is an infallible operation,
    /// these failures will only result from inability to write to the
    /// underlying stream.
    ///
    /// # Examples
    ///
    /// ```
    /// use uniquote::Quote;
    ///
    /// struct Strings<'a>(&'a str, &'a str);
    ///
    /// impl Quote for Strings<'_> {
    ///     fn escape(&self, f: &mut uniquote::Formatter<'_>) -> uniquote::Result {
    ///         self.0.escape(f)?;
    ///         ','.escape(f)?;
    ///         self.1.escape(f)
    ///     }
    /// }
    ///
    /// assert_eq!(r#""foo,bar""#, Strings("foo", "bar").quote().to_string());
    /// ```
    ///
    /// [format]: super#format
    fn escape(&self, f: &mut Formatter<'_>) -> Result;

    /// Quotes a string using the format described in the [the module-level
    /// documentation][format].
    ///
    /// The returned struct will implement [`Display`]. It can be output using
    /// a formatting macro or converted to a string by calling
    /// [`ToString::to_string`].
    ///
    /// # Examples
    ///
    /// ```
    /// use std::env;
    /// # use std::io;
    ///
    /// use uniquote::Quote;
    ///
    /// # #[cfg(feature = "std")]
    /// println!("{}", env::current_exe()?.quote());
    /// #
    /// # Ok::<_, io::Error>(())
    /// ```
    ///
    /// [format]: super#format
    #[inline]
    #[must_use]
    fn quote(&self) -> QuotedDisplay<&Self> {
        QuotedDisplay(self)
    }
}

macro_rules! r#impl {
    ( $($type:ty),+ ) => {
        $(
            impl Quote for $type {
                #[inline]
                fn escape(&self, f: &mut Formatter<'_>) -> $crate::Result {
                    use super::escape::Escape;

                    Escape::escape(self, &mut f.0).map_err(Error)
                }
            }
        )+
    };
}
r#impl!(char, str, [u8]);

impl<const N: usize> Quote for [u8; N] {
    #[inline]
    fn escape(&self, f: &mut Formatter<'_>) -> Result {
        self[..].escape(f)
    }
}

#[cfg_attr(not(feature = "std"), allow(unused_macros))]
macro_rules! impl_with_deref {
    ( $($type:ty),+ ) => {
        $(
            impl $crate::Quote for $type {
                #[inline]
                fn escape(
                    &self,
                    f: &mut $crate::Formatter<'_>
                ) -> $crate::Result {
                    (**self).escape(f)
                }
            }
        )+
    };
}

#[cfg(feature = "alloc")]
mod alloc {
    use alloc::string::String;
    use alloc::vec::Vec;

    impl_with_deref!(String, Vec<u8>);
}

#[cfg(feature = "std")]
mod std {
    use std::ffi::CStr;
    use std::ffi::CString;

    use crate::Formatter;
    use crate::Result;

    use super::Quote;

    impl Quote for CStr {
        #[inline]
        fn escape(&self, f: &mut Formatter<'_>) -> Result {
            self.to_bytes().escape(f)
        }
    }

    impl_with_deref!(CString);

    #[cfg(any(target_os = "hermit", target_os = "wasi", unix, windows))]
    mod os_str {
        use std::ffi::OsStr;
        use std::ffi::OsString;
        use std::path::Path;
        use std::path::PathBuf;

        use crate::Formatter;
        use crate::Result;

        use super::super::Quote;

        impl Quote for OsStr {
            #[inline]
            fn escape(&self, f: &mut Formatter<'_>) -> Result {
                #[cfg(windows)]
                {
                    use std::os::windows::ffi::OsStrExt;

                    f.escape_utf16(self.encode_wide())
                }
                #[cfg(not(windows))]
                {
                    #[cfg(any(target_os = "hermit", unix))]
                    use std::os::unix as os;
                    #[cfg(target_os = "wasi")]
                    use std::os::wasi as os;

                    use os::ffi::OsStrExt;

                    self.as_bytes().escape(f)
                }
            }
        }

        impl Quote for Path {
            #[inline]
            fn escape(&self, f: &mut Formatter<'_>) -> Result {
                self.as_os_str().escape(f)
            }
        }

        impl_with_deref!(OsString, PathBuf);
    }
}
