use crate::error::IndexError;
use super::JsoncValue;
pub trait JsoncIndex<T>: Sized {
    type Indexer: JsoncIndexer<Self, T>;
}
impl<I, F, In: JsoncIndex<JsoncValue<I, F>>> std::ops::Index<In> for JsoncValue<I, F> {
    type Output = <In::Indexer as JsoncIndexer<In, JsoncValue<I, F>>>::Output;
    fn index(&self, index: In) -> &Self::Output {
        In::Indexer::index(self, index)
    }
}
impl<I, F, In: JsoncIndex<JsoncValue<I, F>>> std::ops::IndexMut<In> for JsoncValue<I, F> {
    fn index_mut(&mut self, index: In) -> &mut Self::Output {
        In::Indexer::index_mut(self, index)
    }
}
impl<I, F> JsoncValue<I, F> {
    pub fn get<In: JsoncIndex<Self>>(
        &self,
        index: In,
    ) -> Option<&<In::Indexer as JsoncIndexer<In, JsoncValue<I, F>>>::Output> {
        In::Indexer::get(self, index)
    }
    pub fn get_mut<In: JsoncIndex<Self>>(
        &mut self,
        index: In,
    ) -> Option<&mut <In::Indexer as JsoncIndexer<In, JsoncValue<I, F>>>::Output> {
        In::Indexer::get_mut(self, index)
    }
}
pub trait JsoncIndexer<T, V>
where
    T: JsoncIndex<V>,
{
    type Output: ?Sized;
    fn get(value: &V, index: T) -> Option<&Self::Output>;
    fn get_mut(value: &mut V, index: T) -> Option<&mut Self::Output>;
    fn index(value: &V, index: T) -> &Self::Output;
    fn index_mut(value: &mut V, index: T) -> &mut Self::Output;
}
pub enum StringIndexer {}
impl<'a, I, F> JsoncIndexer<&'a str, JsoncValue<I, F>> for StringIndexer {
    type Output = JsoncValue<I, F>;
    fn get<'b>(value: &'b JsoncValue<I, F>, index: &'a str) -> Option<&'b Self::Output> {
        value.as_map().and_then(|map| map.get(index))
    }
    fn get_mut<'b>(value: &'b mut JsoncValue<I, F>, index: &'a str) -> Option<&'b mut Self::Output> {
        value.as_map_mut().and_then(|map| map.get_mut(index))
    }
    fn index<'b>(value: &'b JsoncValue<I, F>, index: &'a str) -> &'b Self::Output {
        &value.as_map().unwrap_or_else(|| panic!("{}", IndexError::StringIndex { value: value.value_type() }))[index]
    }
    fn index_mut<'b>(value: &'b mut JsoncValue<I, F>, index: &'a str) -> &'b mut Self::Output {
        match value {
            JsoncValue::Object(map) => {
                map.get_mut(index).unwrap_or_else(|| panic!("{}", IndexError::NotExistKey { key: index.to_string() }))
            }
            _ => panic!("{}", IndexError::StringIndex { value: value.value_type() }),
        }
    }
}
pub enum SliceIndexer {}
impl<I, F, S: std::slice::SliceIndex<[JsoncValue<I, F>]> + JsoncIndex<JsoncValue<I, F>>>
    JsoncIndexer<S, JsoncValue<I, F>> for SliceIndexer
{
    type Output = <S as std::slice::SliceIndex<[JsoncValue<I, F>]>>::Output;
    fn get(value: &JsoncValue<I, F>, index: S) -> Option<&Self::Output> {
        value.as_vec().and_then(|v| v.get(index))
    }
    fn get_mut(value: &mut JsoncValue<I, F>, index: S) -> Option<&mut Self::Output> {
        value.as_vec_mut().and_then(|v| v.get_mut(index))
    }
    fn index(value: &JsoncValue<I, F>, index: S) -> &Self::Output {
        &value.as_vec().unwrap_or_else(|| panic!("{}", IndexError::StringIndex { value: value.value_type() }))[index]
    }
    fn index_mut(value: &mut JsoncValue<I, F>, index: S) -> &mut Self::Output {
        match value {
            JsoncValue::Array(v) => &mut v[index],
            _ => panic!("{}", IndexError::StringIndex { value: value.value_type() }),
        }
    }
}
impl<I, F> JsoncIndex<JsoncValue<I, F>> for &str {
    type Indexer = StringIndexer;
}
impl<I, F> JsoncIndex<JsoncValue<I, F>> for usize {
    type Indexer = SliceIndexer;
}
impl<I, F> JsoncIndex<JsoncValue<I, F>> for std::ops::Range<usize> {
    type Indexer = SliceIndexer;
}
impl<I, F> JsoncIndex<JsoncValue<I, F>> for std::ops::RangeFrom<usize> {
    type Indexer = SliceIndexer;
}
impl<I, F> JsoncIndex<JsoncValue<I, F>> for std::ops::RangeFull {
    type Indexer = SliceIndexer;
}
impl<I, F> JsoncIndex<JsoncValue<I, F>> for std::ops::RangeInclusive<usize> {
    type Indexer = SliceIndexer;
}
impl<I, F> JsoncIndex<JsoncValue<I, F>> for std::ops::RangeTo<usize> {
    type Indexer = SliceIndexer;
}
impl<I, F> JsoncIndex<JsoncValue<I, F>> for std::ops::RangeToInclusive<usize> {
    type Indexer = SliceIndexer;
}
#[cfg(test)]
mod tests {
    use crate::{jsonc, jsonc_generics};
    use super::*;
    #[test]
    fn test_index_and_get() {
        let value = jsonc!({
            "name": "json-with-comments",
            "keywords": [
                "JSON with comments",
                "parser",
                "serde",
            ]
        });
        assert_eq!(value["name"], JsoncValue::String("json-with-comments".to_string()));
        assert_eq!(value["keywords"][0], JsoncValue::String("JSON with comments".to_string()));
        assert_eq!(value.get("name"), Some(&JsoncValue::String("json-with-comments".to_string())));
        assert_eq!(value.get("version"), None);
        assert_eq!(value.get("keywords").and_then(|k| k.get(1)), Some(&JsoncValue::String("parser".to_string())));
        assert_eq!(value.get("keywords").and_then(|k| k.get(100)), None);
        assert_eq!(value.get("keywords").and_then(|k| k.get("one")), None);
    }
    #[test]
    fn test_index_mut_and_get_mut() {
        let mut value = jsonc!({
            "name": "json-with-comments",
            "keywords": [
                "JSON with comments",
                "parser",
                "serde",
            ]
        });
        value["name"] = JsoncValue::String("JSON with comments".to_string());
        value["keywords"][0] = JsoncValue::Array(vec!["JSON".into(), "with".into(), "comments".into()]);
        assert_eq!(
            value,
            jsonc!({
                "name": "JSON with comments",
                "keywords": [
                    ["JSON", "with", "comments"],
                    "parser",
                    "serde",
                ]
            })
        );
        value.get_mut("keywords").unwrap().get_mut(0).unwrap().as_vec_mut().unwrap().push("!".into());
        assert_eq!(
            value,
            jsonc!({
                "name": "JSON with comments",
                "keywords": [
                    ["JSON", "with", "comments", "!"],
                    "parser",
                    "serde",
                ]
            })
        );
    }
    #[test]
    fn test_range_index_and_range_get() {
        let value = jsonc!({
            "number": ["one", "two", "three", "four", "five"],
        });
        assert_eq!(
            value["number"][1..3],
            [JsoncValue::String("two".to_string()), JsoncValue::String("three".to_string())]
        );
        assert_eq!(
            value["number"][3..],
            [JsoncValue::String("four".to_string()), JsoncValue::String("five".to_string())]
        );
        assert_eq!(
            value["number"][..],
            [
                JsoncValue::String("one".to_string()),
                JsoncValue::String("two".to_string()),
                JsoncValue::String("three".to_string()),
                JsoncValue::String("four".to_string()),
                JsoncValue::String("five".to_string()),
            ]
        );
        assert_eq!(
            value["number"][1..=2],
            [JsoncValue::String("two".to_string()), JsoncValue::String("three".to_string()),]
        );
        assert_eq!(value["number"][..1], [JsoncValue::String("one".to_string())]);
        assert_eq!(
            value["number"][..=1],
            [JsoncValue::String("one".to_string()), JsoncValue::String("two".to_string()),]
        );
        assert_eq!(
            value["number"].get(1..3),
            Some(&[JsoncValue::String("two".to_string()), JsoncValue::String("three".to_string())][..])
        );
        assert_eq!(
            value["number"].get(3..),
            Some(&[JsoncValue::String("four".to_string()), JsoncValue::String("five".to_string())][..])
        );
        assert_eq!(
            value["number"].get(..),
            Some(
                &[
                    JsoncValue::String("one".to_string()),
                    JsoncValue::String("two".to_string()),
                    JsoncValue::String("three".to_string()),
                    JsoncValue::String("four".to_string()),
                    JsoncValue::String("five".to_string()),
                ][..]
            )
        );
        assert_eq!(
            value["number"].get(1..=2),
            Some(&[JsoncValue::String("two".to_string()), JsoncValue::String("three".to_string()),][..])
        );
        assert_eq!(value["number"].get(..1), Some(&[JsoncValue::String("one".to_string())][..]));
        assert_eq!(
            value["number"].get(..=1),
            Some(&[JsoncValue::String("one".to_string()), JsoncValue::String("two".to_string())][..])
        );
        assert_eq!(value["number"].get(2..2), Some(&[][..]));
        assert_eq!(value["number"].get(10000..), None);
    }
    #[test]
    fn test_nested_index_and_nested_get() {
        let value = jsonc!({
            "object1": {
                "object2": {
                    "array": [false, true],
                }
            }
        });
        assert_eq!(value["object1"]["object2"]["array"][1], JsoncValue::Bool(true));
        assert_eq!(
            ["object1", "object2", "array"].iter().fold(&value, |val, &key| &val[key]),
            &JsoncValue::Array(vec![JsoncValue::Bool(false), JsoncValue::Bool(true)])
        );
        assert_eq!(
            ["object1", "object2", "array"].iter().try_fold(&value, |val, &key| val.get(key)),
            Some(&JsoncValue::Array(vec![JsoncValue::Bool(false), JsoncValue::Bool(true)]))
        );
    }
    #[test]
    #[should_panic]
    fn test_index_unmatched_type() {
        let value: JsoncValue<u64, f64> = jsonc_generics!({"version": 1});
        _ = value[1];
    }
    #[test]
    #[should_panic]
    fn test_index_number_by_number() {
        let value: JsoncValue<u64, f64> = jsonc_generics!({"version": 1});
        _ = value["version"][3];
    }
}