use core::fmt::{Display, Formatter};
use std::ops::{Add, Index, Range};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Item {
    Key(String),
    Index(usize),
}
impl Display for Item {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        match self {
            Item::Key(s) => f.write_str(s.as_ref()),
            Item::Index(n) => f.write_str(&n.to_string()),
        }
    }
}
impl Item {
    pub fn is_key(&self) -> bool {
        use Item::*;
        match self {
            Key(_) => true,
            Index(_) => false,
        }
    }
    pub fn is_index(&self) -> bool {
        use Item::*;
        match self {
            Key(_) => false,
            Index(_) => true,
        }
    }
    pub fn as_key(&self) -> Option<&str> {
        use Item::*;
        match self {
            Key(v) => Some(v.as_ref()),
            Index(_) => None,
        }
    }
    pub fn as_index(&self) -> Option<usize> {
        use Item::*;
        match self {
            Key(_) => None,
            Index(v) => Some(*v),
        }
    }
}
impl From<usize> for Item {
    fn from(index: usize) -> Self {
        use Item::*;
        Index(index)
    }
}
impl From<&str> for Item {
    fn from(key: &str) -> Self {
        use Item::*;
        Key(String::from(key))
    }
}
impl From<String> for Item {
    fn from(key: String) -> Self {
        use Item::*;
        Key(String::from(key))
    }
}
impl From<&String> for Item {
    fn from(key: &String) -> Self {
        use Item::*;
        Key(String::from(key.as_str()))
    }
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KeyPath {
    items: Vec<Item>
}
impl KeyPath {
    pub fn new(items: Vec<Item>) -> Self {
        Self { items }
    }
    pub fn len(&self) -> usize {
        self.items.len()
    }
    pub fn get(&self, index: usize) -> Option<&Item> {
        self.items.get(index)
    }
    pub fn last(&self) -> Option<&Item> {
        self.items.last()
    }
    pub fn is_empty(&self) -> bool {
        self.items.is_empty()
    }
}
impl Default for KeyPath {
    fn default() -> Self {
        Self { items: vec![] }
    }
}
impl AsRef<KeyPath> for KeyPath {
    fn as_ref(&self) -> &KeyPath {
        &self
    }
}
impl Display for KeyPath {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        let s = self.items.iter().map(|i| i.to_string()).collect::<Vec<String>>().join(".");
        f.write_str(&s)
    }
}
impl<'a, T> Add<T> for &KeyPath where T: Into<Item> {
    type Output = KeyPath;
    fn add(self, rhs: T) -> Self::Output {
        let mut items = self.items.clone();
        items.push(rhs.into());
        KeyPath { items }
    }
}
impl<'a, T> Add<T> for KeyPath where T: Into<Item> {
    type Output = Self;
    fn add(self, rhs: T) -> Self::Output {
        (&self).add(rhs)
    }
}
impl Index<usize> for KeyPath {
    type Output = Item;
    fn index(&self, index: usize) -> &Self::Output {
        &self.items[index]
    }
}
impl Index<Range<usize>> for KeyPath {
    type Output = [Item];
    fn index(&self, index: Range<usize>) -> &Self::Output {
        &self.items[index]
    }
}
impl From<&[Item]> for KeyPath {
    fn from(items: &[Item]) -> Self {
        Self { items: items.to_vec() }
    }
}
#[macro_export]
macro_rules! path {
    (@single $($x:tt)*) => (());
    (@count $($rest:expr),*) => (<[()]>::len(&[$(path!(@single $rest)),*]));
    (@item $other: expr) => ($crate::Item::from($other));
    ($($key:expr,)+) => { path!($($key),+) };
    ($($key:expr),*) => {
        {
            let _cap = path!(@count $($key),*);
            let mut _items = ::std::vec::Vec::with_capacity(_cap);
            $(
                let _ = _items.push(path!(@item $key));
            )*
            $crate::KeyPath::new(_items)
        }
    };
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn macro_works_for_empty() {
        let result = path![];
        assert_eq!(result, KeyPath::default());
    }
    #[test]
    fn macro_works_for_2_strings() {
        let result = path!["a", "b"];
        assert_eq!(result, KeyPath { items: vec![Item::Key("a".into()), Item::Key("b".into())]});
    }
    #[test]
    fn macro_works_for_2_numbers() {
        let result = path![2, 5];
        assert_eq!(result, KeyPath { items: vec![Item::Index(2), Item::Index(5)]});
    }
    #[test]
    fn macro_works_for_2_mixed_items() {
        let string = "where".to_owned();
        let result = path![string, 5];
        assert_eq!(result, KeyPath { items: vec![Item::Key("where".to_owned().into()), Item::Index(5)]});
    }
    #[test]
    fn macro_works_for_2_items_with_trailing_comma() {
        let string = "where".to_owned();
        let result = path![string, 5,];
        assert_eq!(result, KeyPath { items: vec![Item::Key("where".to_owned().into()), Item::Index(5)]});
    }
    #[test]
    fn macro_works_for_3_items() {
        let string = "where".to_owned();
        let result = path![string, 5, 7];
        assert_eq!(result, KeyPath { items: vec![Item::Key("where".to_owned().into()), Item::Index(5), Item::Index(7)]});
    }
    #[test]
    fn macro_works_for_3_items_with_trailing_comma() {
        let string = "where".to_owned();
        let result = path![string, 5, 7, ];
        assert_eq!(result, KeyPath { items: vec![Item::Key("where".to_owned().into()), Item::Index(5), Item::Index(7)]});
    }
    #[test]
    fn add_works_for_number() {
        let path = KeyPath::default();
        let result = path + 45;
        assert_eq!(result, KeyPath { items: vec![Item::Index(45)] })
    }
    #[test]
    fn add_works_for_str() {
        let path = KeyPath::default();
        let result = path + "";
        assert_eq!(result, KeyPath { items: vec![Item::Key("".into())] })
    }
    #[test]
    fn add_works_for_string() {
        let path = KeyPath::default();
        let result = path + "a".to_owned();
        assert_eq!(result, KeyPath { items: vec![Item::Key("a".to_owned().into())] })
    }
    #[test]
    fn add_works_for_string_ref() {
        let path = KeyPath::default();
        let string = "abc".to_owned();
        let string_ref = &string;
        let result = path + string_ref;
        assert_eq!(result, KeyPath { items: vec![Item::Key("abc".into())] })
    }
    #[test]
    fn key_path_can_be_debug_printed() {
        let path = path!["where", "items", 5, "name"];
        let result = format!("{}", path);
        assert_eq!(&result, "where.items.5.name");
    }
    #[test]
    fn index_works() {
        let path = path!["orderBy", "name"];
        let result = &path[0];
        assert_eq!(result, &("orderBy".into()))
    }
    #[test]
    fn index_with_range_works() {
        let path = path!["orderBy", "name", 3, "good"];
        let result = &path[0..2];
        assert_eq!(result, &[Item::Key("orderBy".to_string()), Item::Key("name".to_string())])
    }
    #[test]
    fn get_works() {
        let path = path!["orderBy", "name"];
        let result = path.get(0).unwrap();
        assert_eq!(result, &("orderBy".into()))
    }
    #[test]
    fn last_works() {
        let path = path!["orderBy", "name"];
        let result = path.last().unwrap();
        assert_eq!(result, &("name".into()))
    }
    #[test]
    fn as_ref_works() {
        let path = path!["a", "b"];
        let path2 = path.as_ref();
        let path3 = path2.as_ref();
        assert_eq!(&path, path2);
        assert_eq!(path2, path3);
    }
}