//! Structured keys.

use crate::MaybeStaticStr;
use std::borrow::Borrow;
use std::fmt;

/// A type that can be converted into a [`Key`](struct.Key.html).
pub trait ToKey {
    /// Perform the conversion.
    fn to_key(&self) -> Key<'_>;
}

impl<T> ToKey for &T
where
    T: ToKey + ?Sized,
{
    fn to_key(&self) -> Key<'_> {
        (**self).to_key()
    }
}

impl<'k> ToKey for Key<'k> {
    fn to_key(&self) -> Key<'_> {
        Key { key: self.key }
    }
}

impl ToKey for str {
    fn to_key(&self) -> Key<'_> {
        Key::from_str(self)
    }
}

/// A key in a key-value.
// These impls must only be based on the as_str() representation of the key
// If a new field (such as an optional index) is added to the key they must not affect comparison
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Key<'k> {
    key: MaybeStaticStr<'k>,
}

impl<'k> Key<'k> {
    /// Get a key from a borrowed string.
    #[allow(clippy::should_implement_trait)] // Part of the public API now.
    pub fn from_str(key: &'k str) -> Self {
        Key {
            key: MaybeStaticStr::Borrowed(key),
        }
    }

    /// Get a key from a static str.
    pub fn from_str_static(key: &'static str) -> Self {
        Key {
            key: MaybeStaticStr::Static(key),
        }
    }

    /// Get a borrowed string from this key.
    ///
    /// The lifetime of the returned string is bound to the borrow of `self` rather
    /// than to `'k`.
    pub fn as_str(&self) -> &str {
        self.key.get()
    }

    /// Try to get a borrowed string for the lifetime `'k` from this key.
    ///
    /// If the key is a borrow of a longer lived string, this method will return `Some`.
    /// If the key is internally buffered, this method will return `None`.
    pub fn to_borrowed_str(&self) -> Option<&'k str> {
        // NOTE: If the internals of `Key` support buffering this
        // won't be unconditionally `Some` anymore. We want to keep
        // this option open
        Some(self.key.get())
    }

    /// Try to get a static string from this key.
    pub fn to_static_str(&self) -> Option<&'static str> {
        match self.key {
            MaybeStaticStr::Static(s) => Some(s),
            MaybeStaticStr::Borrowed(_) => None,
        }
    }
}

impl<'k> fmt::Display for Key<'k> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.key.get().fmt(f)
    }
}

impl<'k> AsRef<str> for Key<'k> {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

impl<'k> Borrow<str> for Key<'k> {
    fn borrow(&self) -> &str {
        self.as_str()
    }
}

impl<'k> From<&'k str> for Key<'k> {
    fn from(s: &'k str) -> Self {
        Key::from_str(s)
    }
}

#[cfg(feature = "std")]
mod std_support {
    use super::*;

    use std::borrow::Cow;

    impl ToKey for String {
        fn to_key(&self) -> Key<'_> {
            Key::from_str(self)
        }
    }

    impl<'a> ToKey for Cow<'a, str> {
        fn to_key(&self) -> Key<'_> {
            Key::from_str(self)
        }
    }
}

#[cfg(feature = "kv_sval")]
mod sval_support {
    use super::*;

    use sval::Value;
    use sval_ref::ValueRef;

    impl<'a> Value for Key<'a> {
        fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(
            &'sval self,
            stream: &mut S,
        ) -> sval::Result {
            self.key.get().stream(stream)
        }
    }

    impl<'a> ValueRef<'a> for Key<'a> {
        fn stream_ref<S: sval::Stream<'a> + ?Sized>(&self, stream: &mut S) -> sval::Result {
            self.key.get().stream(stream)
        }
    }
}

#[cfg(feature = "kv_serde")]
mod serde_support {
    use super::*;

    use serde_core::{Serialize, Serializer};

    impl<'a> Serialize for Key<'a> {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            self.key.get().serialize(serializer)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn key_from_string() {
        assert_eq!("a key", Key::from_str("a key").as_str());
    }

    #[test]
    fn key_to_borrowed() {
        assert_eq!("a key", Key::from_str("a key").to_borrowed_str().unwrap());
    }
}
