use std::borrow::Borrow;
use std::fmt;
use std::hash::Hash;
use std::hash::Hasher;
use std::num::NonZeroUsize;
use std::ops::Deref;

#[cfg(feature = "singlethreaded")]
use std::rc::Rc;

#[cfg(not(feature = "singlethreaded"))]
use std::sync::Arc;

#[cfg(not(feature = "singlethreaded"))]
pub(crate) type Rc<T> = Arc<T>;

/// A shared string that can be either static or dynamic with copy-on-write semantic.
#[derive(Debug, Clone)]
pub enum CowStr {
    /// A static string can be shared immutably without reference counting.
    Static(&'static str),
    /// Shared strings are reference counted Strings.
    Shared(Rc<String>),
}

impl CowStr {
    /// Creates a new empty `CowStr`.
    #[must_use]
    pub fn new() -> Self {
        CowStr::Shared(Rc::new(String::new()))
    }

    /// Creates a new `CowStr` that holds a static string.
    #[must_use]
    pub fn from_static(s: &'static str) -> Self {
        CowStr::Static(s)
    }

    /// Gets a mutable reference to the underlying String, when the the `CowStr` is static or
    /// the reference count is bigger than one the String will be cloned.
    #[must_use]
    #[allow(clippy::missing_panics_doc)]
    pub fn to_mut(&mut self) -> &mut String {
        match self {
            Self::Static(s) => {
                *self = CowStr::Shared(Rc::new(String::from(*s)));
            }
            Self::Shared(r) => {
                Rc::make_mut(r);
            }
        }
        if let CowStr::Shared(rc) = self {
            Rc::get_mut(rc).unwrap()
        } else {
            unreachable!()
        }
    }

    /// Returns `Ok(&'static str)` when the underlying string has static lifetime and
    /// `Err(&'self str)` when it is dynamically allocated.
    pub fn deref_static(&self) -> Result<&'static str, &str> {
        match self {
            Self::Static(s) => Ok(s),
            Self::Shared(s) => Err(s),
        }
    }

    /// Returns the reference count of the underlying allocation. May be 'None' when self
    /// refers to a static string.
    #[must_use]
    #[allow(clippy::missing_panics_doc)]
    pub fn strong_count(&self) -> Option<NonZeroUsize> {
        match self {
            Self::Static(_) => None,
            Self::Shared(r) => Some(NonZeroUsize::new(Rc::strong_count(r)).unwrap()),
        }
    }
}

impl Default for CowStr {
    fn default() -> Self {
        Self::new()
    }
}

/// Implements the `CowStr::from()` for anything that a `String` can be constructed from.
impl<T> From<T> for CowStr
where
    String: From<T>,
{
    fn from(source: T) -> Self {
        CowStr::Shared(Rc::new(String::from(source)))
    }
}

impl fmt::Write for CowStr {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.to_mut().write_str(s)
    }

    fn write_char(&mut self, c: char) -> fmt::Result {
        self.to_mut().write_char(c)
    }

    fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
        self.to_mut().write_fmt(args)
    }
}

/// `CowStr` dereferences to `&str`.
impl Deref for CowStr {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        match self {
            Self::Static(s) => s,
            Self::Shared(r) => r.as_ref(),
        }
    }
}

impl AsRef<str> for CowStr {
    fn as_ref(&self) -> &str {
        match self {
            Self::Static(s) => s,
            Self::Shared(r) => r.as_ref(),
        }
    }
}

impl Borrow<str> for CowStr {
    fn borrow(&self) -> &str {
        match self {
            Self::Static(s) => s,
            Self::Shared(r) => r.as_ref(),
        }
    }
}

impl PartialEq for CowStr {
    fn eq(&self, other: &Self) -> bool {
        self.as_ref() == other.as_ref()
    }
}

impl Eq for CowStr {}

impl PartialOrd for CowStr {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.as_ref().partial_cmp(other.as_ref())
    }
}

impl Ord for CowStr {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.as_ref().cmp(other.as_ref())
    }
}

impl Hash for CowStr {
    fn hash<H: Hasher>(&self, state: &mut H) {
        (**self).hash(state);
    }
}

impl fmt::Display for CowStr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&**self, f)
    }
}

#[test]
fn smoke() {
    let _empty_string = CowStr::new();
    let _static_string = CowStr::from_static("test string");
    let _dynamic_string = CowStr::from("test string");
}

#[test]
fn push() {
    let mut my_string = CowStr::from_static("test string");

    my_string.to_mut().push(' ');
    my_string.to_mut().push_str("foobar");
    assert_eq!(&*my_string, "test string foobar");
}

#[test]
#[ignore]
fn print() {
    let my_string = CowStr::from_static("test string");
    println!("my_string = {my_string}");
}
