#![allow(unused_macros)]
use std::borrow::Cow;
use std::collections::VecDeque;
use std::fmt::Display;
use std::ops::Add;
const PATH_SEPARATOR: char = '.';
#[derive(Debug, Clone, PartialEq)]
pub enum JsonPathComponent<'a> {
    Root,
    NameSelector(Cow<'a, str>),
    WildcardSelector,
    IndexSelector(usize),
    RangeSelector(usize, usize),
}
impl<'a> Display for JsonPathComponent<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Root => write!(f, "$"),
            Self::NameSelector(s) => write!(f, "{}", s),
            Self::WildcardSelector => write!(f, "[*]"),
            Self::IndexSelector(i) => write!(f, "[{}]", i),
            Self::RangeSelector(i, j) => write!(f, "[{}..{}]", i, j),
        }
    }
}
macro_rules! is_index_selector {
    ($comp : expr) => {
        match $comp {
            JsonPathComponent::IndexSelector(_) => true,
            _ => false,
        }
    };
}
macro_rules! is_root_selector {
    ($comp : expr) => {
        match $comp {
            JsonPathComponent::Root => true,
            _ => false,
        }
    };
}
macro_rules! is_wildcard_selector {
    ($comp : expr) => {
        match $comp {
            JsonPathComponent::WildcardSelector => true,
            _ => false,
        }
    };
}
macro_rules! is_name_selector {
    ($comp : expr) => {
        match $comp {
            JsonPathComponent::NameSelector(_) => true,
            _ => false,
        }
    };
}
macro_rules! is_range_selector {
    ($comp : expr) => {
        match $comp {
            JsonPathComponent::RangeSelector(_, _) => true,
            _ => false,
        }
    };
}
#[derive(Debug)]
pub struct JsonPath<'a> {
    components: Vec<JsonPathComponent<'a>>,
}
impl<'a> JsonPath<'a> {
    pub fn new_partial() -> Self {
        JsonPath { components: vec![] }
    }
    pub fn new() -> Self {
        JsonPath {
            components: vec![JsonPathComponent::Root],
        }
    }
    pub fn is_empty(&self) -> bool {
        self.components.is_empty()
    }
    pub fn is_partial(&self) -> bool {
        if !self.is_empty() {
            self.components[0] != JsonPathComponent::Root
        } else {
            true
        }
    }
    pub fn as_string(&self) -> Cow<'a, str> {
        Cow::Owned(
            self.components
                .iter()
                .map(|s| s.to_string())
                .collect::<Vec<String>>()
                .join("."),
        )
    }
    pub fn push_str_selector(&mut self, name: &str) {
        self.components
            .push(JsonPathComponent::NameSelector(Cow::Owned(String::from(
                name.replace("\"", ""),
            ))));
    }
    pub fn push_index_select(&mut self, index: usize) {
        self.components
            .push(JsonPathComponent::IndexSelector(index));
    }
    pub fn push_range_selector(&mut self, start: usize, end: usize) {
        self.components
            .push(JsonPathComponent::RangeSelector(start, end));
    }
    pub fn push_wildcard_selector(&mut self) {
        self.components.push(JsonPathComponent::WildcardSelector);
    }
    pub fn push(&mut self, component: JsonPathComponent<'a>) {
        self.components.push(component);
    }
    pub fn pop(&mut self) -> Option<JsonPathComponent<'a>> {
        self.components.pop()
    }
    pub fn is_array_path(&self) -> bool {
        if self.is_empty() {
            return false;
        }
        is_index_selector!(self.components.last().unwrap())
    }
    pub fn len(&self) -> usize {
        self.components.len()
    }
    pub fn matches_strict(&self, rhs: &JsonPath<'a>) -> bool {
        if self.is_empty() && rhs.is_empty() {
            return true;
        }
        if self.len() != rhs.len() {
            return false;
        };
        self.components
            .iter()
            .zip(rhs.components.iter())
            .fold(true, |acc, comps| acc && comps.0 == comps.1)
    }
    pub fn matches(&self, rhs: &JsonPath<'a>) -> bool {
        if self.is_empty() && rhs.is_empty() {
            return true;
        }
        if self.len() != rhs.len() {
            return false;
        };
        self.components
            .iter()
            .zip(rhs.components.iter())
            .fold(true, |acc, comp| acc && Self::match_path_components(comp))
    }
    fn match_path_components(pair: (&JsonPathComponent, &JsonPathComponent)) -> bool {
        match pair {
            (JsonPathComponent::IndexSelector(i), JsonPathComponent::IndexSelector(j)) => i == j,
            (JsonPathComponent::IndexSelector(i), JsonPathComponent::RangeSelector(j, k)) => {
                j <= i && i <= k
            }
            (JsonPathComponent::RangeSelector(j, k), JsonPathComponent::IndexSelector(i)) => {
                j <= i && i <= k
            }
            (JsonPathComponent::WildcardSelector, JsonPathComponent::IndexSelector(_)) => true,
            (JsonPathComponent::IndexSelector(_), JsonPathComponent::WildcardSelector) => true,
            (a, b) => a == b,
        }
    }
}
impl<'a> Display for JsonPath<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_string())
    }
}
impl<'a> Add<&JsonPath<'a>> for JsonPath<'a> {
    type Output = Self;
    fn add(mut self, rhs: &JsonPath<'a>) -> Self {
        if !self.is_partial() && !rhs.is_partial() {
            panic!("attempted to concatenate two rooted paths")
        }
        if self.is_partial() && !rhs.is_partial() {
            panic!("attempted to concatenate a rooted path to a partial path")
        }
        rhs.components
            .iter()
            .for_each(|c| self.components.push(c.clone()));
        self
    }
}
#[cfg(test)]
mod tests {
    use crate::paths::{JsonPath, JsonPathComponent};
    #[test]
    fn a_new_partial_path_should_be_partial() {
        let path = JsonPath::new_partial();
        assert!(path.is_partial())
    }
    #[test]
    fn a_new_partial_path_should_have_an_empty_representation() {
        let path = JsonPath::new_partial();
        assert_eq!("".to_string(), path.as_string())
    }
    #[test]
    fn a_new_partial_path_should_be_empty() {
        let path = JsonPath::new_partial();
        assert!(path.is_empty())
    }
    #[test]
    fn a_new_rooted_path_should_not_be_partial() {
        let path = JsonPath::new();
        assert!(!path.is_partial())
    }
    #[test]
    fn a_new_rooted_path_should_have_the_correct_representation() {
        let path = JsonPath::new();
        assert_eq!("$".to_string(), path.as_string())
    }
    #[test]
    fn a_new_rooted_path_should_not_be_empty() {
        let path = JsonPath::new();
        assert!(!path.is_empty())
    }
    #[test]
    fn simple_paths_should_have_correct_representations() {
        let mut path = JsonPath::new();
        path.push_str_selector("a");
        path.push_str_selector("b");
        path.push_str_selector("c");
        path.push_str_selector("d");
        assert_eq!(&path.as_string(), "$.a.b.c.d")
    }
    #[test]
    fn complex_paths_should_have_correct_representations() {
        let mut path = JsonPath::new();
        path.push_str_selector("array");
        path.push_wildcard_selector();
        path.push_index_select(4);
        path.push_range_selector(6, 7);
        assert_eq!(path.as_string(), "$.array.[*].[4].[6..7]")
    }
    #[test]
    fn popping_elements_should_correctly_alter_representation() {
        let mut path = JsonPath::new();
        path.push_str_selector("a");
        path.push_str_selector("b");
        path.push_str_selector("c");
        path.push_str_selector("d");
        path.pop();
        path.pop();
        assert_eq!(&path.as_string(), "$.a.b")
    }
    #[test]
    fn array_paths_should_be_identified_as_such() {
        let mut path = JsonPath::new();
        path.push_str_selector("a");
        path.push_index_select(4);
        assert!(path.is_array_path())
    }
    #[test]
    fn a_root_and_partial_paths_can_be_concatenated_correctly() {
        let mut root = JsonPath::new();
        let mut partial = JsonPath::new_partial();
        partial.push_str_selector("a");
        root = root + &partial;
        assert_eq!(root.as_string(), "$.a")
    }
    #[test]
    #[should_panic]
    fn concatenating_two_rooted_paths_should_panic() {
        let root1 = JsonPath::new();
        let root2 = JsonPath::new();
        let _combined = root1 + &root2;
    }
    #[test]
    #[should_panic]
    fn concatenating_a_root_path_to_a_partial_should_panic() {
        let partial = JsonPath::new_partial();
        let root = JsonPath::new();
        let _combined = partial + &root;
    }
    #[test]
    fn empty_paths_should_strictly_match() {
        let left = JsonPath::new();
        let right = JsonPath::new();
        assert!(left.matches_strict(&right))
    }
    #[test]
    fn complex_paths_should_strictly_match() {
        let mut left = JsonPath::new();
        let mut right = JsonPath::new();
        left.push_str_selector("a");
        right.push_str_selector("a");
        left.push_index_select(6);
        right.push_index_select(6);
        left.push_range_selector(4, 5);
        right.push_range_selector(4, 5);
        assert!(left.matches_strict(&right));
        assert_eq!(left.to_string(), right.to_string())
    }
    #[test]
    fn slightly_different_complex_paths_should_not_strictly_match() {
        let mut left = JsonPath::new();
        let mut right = JsonPath::new();
        left.push_str_selector("a");
        right.push_str_selector("a");
        left.push_index_select(6);
        right.push_index_select(6);
        left.push_range_selector(4, 5);
        right.push_range_selector(3, 5);
        assert!(!left.matches_strict(&right));
        assert_ne!(left.to_string(), right.to_string())
    }
    #[test]
    fn partial_paths_should_match_strictly() {
        let mut left = JsonPath::new_partial();
        let mut right = JsonPath::new_partial();
        left.push_str_selector("a");
        right.push_str_selector("a");
        left.push_wildcard_selector();
        right.push_wildcard_selector();
        assert!(left.matches(&right));
        assert_eq!(left.to_string(), right.to_string())
    }
    #[test]
    fn indexes_should_match_on_ranges() {
        let mut left = JsonPath::new();
        let mut right = JsonPath::new();
        left.push_range_selector(0, 4);
        right.push_index_select(3);
        assert!(left.matches(&right));
        let mut left = JsonPath::new();
        let mut right = JsonPath::new();
        left.push_range_selector(0, 4);
        right.push_index_select(3);
        assert!(right.matches(&left))
    }
    #[test]
    fn indexes_should_not_match_outside_of_ranges() {
        let mut left = JsonPath::new();
        let mut right = JsonPath::new();
        left.push_range_selector(0, 4);
        right.push_index_select(6);
        assert!(!left.matches(&right));
        let mut left = JsonPath::new();
        let mut right = JsonPath::new();
        left.push_range_selector(0, 4);
        right.push_index_select(6);
        assert!(!right.matches(&left))
    }
}