// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::fs::SEPARATOR;
use crate::fs::path::Path;
use crate::{CStr16, CString16, Char16};
use core::fmt::{Display, Formatter};

/// A path buffer similar to the `PathBuf` of the standard library, but based on
/// [`CString16`] strings and [`SEPARATOR`] as separator.
///
/// `/` is replaced by [`SEPARATOR`] on the fly.
#[derive(Clone, Debug, Default, Eq, PartialOrd, Ord)]
pub struct PathBuf(CString16);

impl PathBuf {
    /// Constructor.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Constructor that replaces all occurrences of `/` with `\`.
    fn new_from_cstring16(mut string: CString16) -> Self {
        const SEARCH: Char16 = unsafe { Char16::from_u16_unchecked('/' as u16) };
        string.replace_char(SEARCH, SEPARATOR);
        Self(string)
    }

    /// Extends self with path.
    ///
    /// UNIX separators (`/`) will be replaced by [`SEPARATOR`] on the fly.
    pub fn push<P: AsRef<Path>>(&mut self, path: P) {
        const SEARCH: Char16 = unsafe { Char16::from_u16_unchecked('/' as u16) };

        // do nothing on empty path
        if path.as_ref().is_empty() {
            return;
        }

        let empty = self.0.is_empty();
        let needs_sep = *self
            .0
            .as_slice_with_nul()
            .last()
            .expect("Should have at least null character")
            != SEPARATOR;
        if !empty && needs_sep {
            self.0.push(SEPARATOR)
        }

        self.0.push_str(path.as_ref().to_cstr16());
        self.0.replace_char(SEARCH, SEPARATOR);
    }
}

impl PartialEq for PathBuf {
    fn eq(&self, other: &Self) -> bool {
        let path1: &Path = self.as_ref();
        let path2: &Path = other.as_ref();
        path1 == path2
    }
}

impl Display for PathBuf {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        Display::fmt(self.to_cstr16(), f)
    }
}

mod convenience_impls {
    use super::*;
    use core::borrow::Borrow;
    use core::ops::Deref;

    impl From<CString16> for PathBuf {
        fn from(value: CString16) -> Self {
            Self::new_from_cstring16(value)
        }
    }

    impl From<&CStr16> for PathBuf {
        fn from(value: &CStr16) -> Self {
            Self::new_from_cstring16(CString16::from(value))
        }
    }

    impl Deref for PathBuf {
        type Target = Path;

        fn deref(&self) -> &Self::Target {
            Path::new(&self.0)
        }
    }

    impl AsRef<Path> for PathBuf {
        fn as_ref(&self) -> &Path {
            // falls back to deref impl
            self
        }
    }

    impl Borrow<Path> for PathBuf {
        fn borrow(&self) -> &Path {
            // falls back to deref impl
            self
        }
    }

    impl AsRef<CStr16> for PathBuf {
        fn as_ref(&self) -> &CStr16 {
            &self.0
        }
    }

    impl Borrow<CStr16> for PathBuf {
        fn borrow(&self) -> &CStr16 {
            &self.0
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::cstr16;
    use alloc::string::ToString;

    #[test]
    fn from_cstr16() {
        let source: &CStr16 = cstr16!("\\hello\\foo\\bar");
        let _path: PathBuf = source.into();
    }

    #[test]
    fn from_cstring16() {
        let source = CString16::try_from("\\hello\\foo\\bar").unwrap();
        let _path: PathBuf = source.as_ref().into();
        let _path: PathBuf = source.clone().into();
        let _path: PathBuf = PathBuf::new_from_cstring16(source);
    }

    #[test]
    fn from_std_string() {
        let std_string = "\\hello\\foo\\bar".to_string();
        let _path = PathBuf::new_from_cstring16(CString16::try_from(std_string.as_str()).unwrap());
    }

    #[test]
    fn push() {
        let mut pathbuf = PathBuf::new();
        pathbuf.push(cstr16!("first"));
        pathbuf.push(cstr16!("second"));
        pathbuf.push(cstr16!("third"));
        assert_eq!(pathbuf.to_cstr16(), cstr16!("first\\second\\third"));

        let mut pathbuf = PathBuf::new();
        pathbuf.push(cstr16!("\\first"));
        pathbuf.push(cstr16!("second"));
        assert_eq!(pathbuf.to_cstr16(), cstr16!("\\first\\second"));

        // empty pushes should be ignored and have no effect
        let empty_cstring16 = CString16::try_from("").unwrap();
        let mut pathbuf = PathBuf::new();
        pathbuf.push(cstr16!("first"));
        pathbuf.push(empty_cstring16.as_ref());
        pathbuf.push(empty_cstring16.as_ref());
        pathbuf.push(empty_cstring16.as_ref());
        pathbuf.push(cstr16!("second"));
        assert_eq!(pathbuf.to_cstr16(), cstr16!("first\\second"));
    }

    #[test]
    fn partial_eq() {
        let mut pathbuf1 = PathBuf::new();
        pathbuf1.push(cstr16!("first"));
        pathbuf1.push(cstr16!("second"));
        pathbuf1.push(cstr16!("third"));

        assert_eq!(pathbuf1, pathbuf1);

        let mut pathbuf2 = PathBuf::new();
        pathbuf2.push(cstr16!("\\first"));
        pathbuf2.push(cstr16!("second"));

        assert_eq!(pathbuf2, pathbuf2);
        assert_ne!(pathbuf1, pathbuf2);
    }
}
