Documentation
mod effect_fn;

use effect_fn::EffectFn;
use once_cell::sync::Lazy;
use std::{
    collections::{HashMap, HashSet},
    sync::{Arc, Mutex},
};

pub static LOCK_SET_ACTIVE_EFFECT: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
pub static ACTIVE_EFFECT: Lazy<Mutex<Option<Arc<Effect>>>> = Lazy::new(|| Mutex::new(None));
// HashMap<TargetPtr, HashMap<Key, Deps>>
static BUCKET: Lazy<Mutex<HashMap<usize, HashMap<String, Arc<Mutex<HashSet<Arc<EffectFn>>>>>>>> = Lazy::new(|| Mutex::new(HashMap::default()));

#[derive(Clone)]
pub struct Effect(Arc<EffectFn>);

impl core::hash::Hash for Effect {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.get_effect_fn_ptr_address().hash(state);
    }
}
impl std::cmp::PartialEq for Effect {
    fn eq(&self, other: &Self) -> bool {
        self.get_effect_fn_ptr_address() == other.get_effect_fn_ptr_address()
    }
}
impl std::cmp::Eq for Effect {}

impl Effect {
    pub fn track<I: crate::syn::rjson::json::Index + ToString + Clone>(target_ptr: usize, index: &I) {
        let active_effect = ACTIVE_EFFECT.lock().unwrap();
        if active_effect.is_none() {
            return;
        }
        let active_effect = active_effect.as_ref().unwrap();
        let mut bucket = BUCKET.lock().unwrap();
        let deps_map = bucket.entry(target_ptr).or_insert(HashMap::default());
        let deps_set = deps_map.entry(index.to_string()).or_insert(Arc::new(Mutex::new(HashSet::default())));
        let mut inner_deps_set = deps_set.lock().unwrap();
        if !inner_deps_set.contains(&active_effect.0) {
            inner_deps_set.insert(active_effect.0.clone());
            active_effect.0.deps_set.lock().unwrap().push(deps_set.clone());
            active_effect.0.on_track(target_ptr, index);
        }
    }
    pub fn trigger<I: crate::syn::rjson::json::Index + ToString + Clone>(target_ptr: usize, index: &I) {
        let bucket = BUCKET.lock().unwrap();
        if let Some(deps_map) = bucket.get(&target_ptr) {
            if let Some(deps_set) = deps_map.get(&index.to_string()) {
                let inner_deps_set = deps_set.lock().unwrap();
                for dep in inner_deps_set.iter() {
                    dep.on_trigger(target_ptr, index);
                    dep.run();
                }
            }
        }
    }
    pub fn remove_target_ptr(target_ptr: usize) {
        let mut bucket = BUCKET.lock().unwrap();
        if bucket.contains_key(&target_ptr) {
            bucket.remove(&target_ptr);
        }
    }
    pub fn replace_target_ptr(target_ptr: usize, new_target_ptr: usize) {
        let mut bucket = BUCKET.lock().unwrap();
        if let Some(value) = bucket.remove(&target_ptr) {
            bucket.insert(new_target_ptr, value);
        }
    }
}

impl Effect {
    pub fn new<F>(closure: F) -> Arc<Effect>
    where
        F: Fn() -> () + Send + Sync + 'static,
    {
        let effect = Arc::new(Effect(Arc::new(EffectFn::new(closure))));
        *LOCK_SET_ACTIVE_EFFECT.lock().unwrap() = true;
        {
            *ACTIVE_EFFECT.lock().unwrap() = Some(effect.clone());
        }
        effect.0.run();
        {
            *ACTIVE_EFFECT.lock().unwrap() = None;
        }
        *LOCK_SET_ACTIVE_EFFECT.lock().unwrap() = false;
        effect
    }
    pub fn get_effect_fn_ptr_address(&self) -> usize {
        self.0.get_ptr_address()
    }
    fn cleanup(&mut self) {
        let deps_effect_fn = self.0.deps_set.lock().unwrap();
        for dep_effect_fn in deps_effect_fn.iter() {
            let mut deps_effect_fn = dep_effect_fn.lock().unwrap();
            deps_effect_fn.remove(&self.0);
        }
    }
}

impl Drop for Effect {
    fn drop(&mut self) {
        log::trace!("drop effect: {:p}, inner effect_fn address {:?}", self, self.get_effect_fn_ptr_address());
        self.cleanup();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn it_works() {
        let serde_json_value = serde_json::json!({
            "name": "John Doe",
            "age": 43,
            "phones": [
                "+44",
                "+44 1234567",
                "+44 2345678"
            ],
            "address": {
                "street": "123 Main St",
                "city": "London",
                "state": "UK",
                "zip": 12345
            }
        });

        {
            let bucket = BUCKET.lock().unwrap();
            assert_eq!(bucket.keys().len(), 0);
        }
        base_test(serde_json_value.clone().into());
        {
            let bucket = BUCKET.lock().unwrap();
            assert_eq!(bucket.keys().len(), 0);
        }
        object_test(serde_json_value.clone().into());
        {
            let bucket = BUCKET.lock().unwrap();
            assert_eq!(bucket.keys().len(), 0);
        }
        array_test(serde_json_value.clone().into());
        {
            let bucket = BUCKET.lock().unwrap();
            assert_eq!(bucket.keys().len(), 0);
        }
    }
    fn base_test(json: crate::syn::RJson) {
        {
            let count = Arc::new(Mutex::new(0));
            let eff_json1 = json.clone();
            let eff_count = count.clone();
            let _eff = crate::syn::effect(move || {
                *eff_count.lock().unwrap() += 1;
                eff_json1.get("name");
            });
            {
                let bucket = BUCKET.lock().unwrap();
                assert_eq!(bucket.keys().len(), 1);
                assert_eq!(bucket.get(&json.get_json_ptr_address()).unwrap().keys().len(), 1);
                let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("name").unwrap();
                assert_eq!(deps.lock().unwrap().len(), 1);
            }
            {
                assert_eq!(*count.lock().unwrap(), 1);
            }
            json.set("age2", 0);
            json.set("name", "name1");
            {
                assert_eq!(*count.lock().unwrap(), 2);
            }
            json.set("name", "name2");
            json.set("age", 18);
            {
                assert_eq!(*count.lock().unwrap(), 3);
            }
            json.set("name", "name2");
            {
                assert_eq!(*count.lock().unwrap(), 4);
            }
        }
        {
            let bucket = BUCKET.lock().unwrap();
            let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("name").unwrap();
            assert_eq!(deps.lock().unwrap().len(), 0);
        }
    }
    fn object_test(json: crate::syn::RJson) {
        let count = Arc::new(Mutex::new(0));
        let eff_json = json.clone();
        let eff_count = count.clone();
        let _eff = crate::syn::effect(move || {
            *eff_count.lock().unwrap() += 1;
            eff_json.get("name");
            eff_json.get("address").get("street");
        });
        {
            let bucket = BUCKET.lock().unwrap();
            assert_eq!(bucket.keys().len(), 2);
            assert_eq!(bucket.get(&json.get_json_ptr_address()).unwrap().keys().len(), 2);
            assert_eq!(bucket.get(&json.get("address").get_json_ptr_address()).unwrap().keys().len(), 1);
            let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("name").unwrap();
            assert_eq!(deps.lock().unwrap().len(), 1);
            let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("address").unwrap();
            assert_eq!(deps.lock().unwrap().len(), 1);
        }
        {
            assert_eq!(*count.lock().unwrap(), 1);
        }
        json.set("age2", 0);
        json.set("name", "name1");
        json.set("age", "18");
        {
            assert_eq!(*count.lock().unwrap(), 2);
        }
        json.get("address").set("street", "street1");
        {
            assert_eq!(*count.lock().unwrap(), 3);
        }
    }
    fn array_test(json: crate::syn::RJson) {
        let count = Arc::new(Mutex::new(0));
        let eff_json1 = json.clone();
        let eff_count = count.clone();
        let _eff = crate::syn::effect(move || {
            *eff_count.lock().unwrap() += 1;
            eff_json1.get("name");
            eff_json1.get("phones").get(0);
        });
        {
            let bucket = BUCKET.lock().unwrap();
            assert_eq!(bucket.keys().len(), 2);
            assert_eq!(bucket.get(&json.get_json_ptr_address()).unwrap().keys().len(), 2);
            assert_eq!(bucket.get(&json.get("phones").get_json_ptr_address()).unwrap().keys().len(), 1);
            let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("name").unwrap();
            assert_eq!(deps.lock().unwrap().len(), 1);
            let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("phones").unwrap();
            assert_eq!(deps.lock().unwrap().len(), 1);
        }
        {
            assert_eq!(*count.lock().unwrap(), 1);
        }
        json.set("age2", 0);
        json.set("name", "name1");
        json.set("age", "18");
        {
            assert_eq!(*count.lock().unwrap(), 2);
        }
        json.get("phones").set(0, "1234567");
        {
            assert_eq!(*count.lock().unwrap(), 3);
        }

        json.set("phones", serde_json::json!(["1"]));
        {
            assert_eq!(*count.lock().unwrap(), 4);
        }
        json.get("phones").set(0, "2");
        {
            assert_eq!(*count.lock().unwrap(), 5);
        }
    }
}