Documentation
mod effect_fn;

use effect_fn::EffectFn;
use std::{
    cell::RefCell,
    collections::{HashMap, HashSet},
    rc::Rc,
};

type ActiveEffectType = RefCell<Option<Rc<Effect>>>;
type BucketType = RefCell<HashMap<usize, HashMap<String, Rc<RefCell<HashSet<Rc<EffectFn>>>>>>>;
thread_local! {
    pub static LOCK_SET_ACTIVE_EFFECT: RefCell<bool> = RefCell::new(false);
    pub static ACTIVE_EFFECT: ActiveEffectType = RefCell::new(None);
    // HashMap<TargetPtr, HashMap<Key, Deps>>
    static BUCKET: BucketType = RefCell::new(HashMap::default());
}

#[derive(Clone)]
pub struct Effect(Rc<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::rjson::json::Index + ToString + Clone>(target_ptr: usize, index: &I) {
        ACTIVE_EFFECT.with_borrow(|active_effect| {
            if active_effect.is_none() {
                return;
            }
            let active_effect = active_effect.clone().unwrap();
            BUCKET.with_borrow_mut(|bucket| {
                let deps_map = bucket.entry(target_ptr).or_insert(HashMap::default());
                let deps_set = deps_map.entry(index.to_string()).or_insert(Rc::new(RefCell::new(HashSet::default())));
                let mut inner_deps_set = deps_set.borrow_mut();
                if !inner_deps_set.contains(&active_effect.0) {
                    inner_deps_set.insert(active_effect.0.clone());
                    active_effect.0.deps_set.borrow_mut().push(deps_set.clone());
                    active_effect.0.on_track(target_ptr, index);
                }
            });
        });
    }
    pub fn trigger<I: crate::rjson::json::Index + ToString + Clone>(target_ptr: usize, index: &I) {
        BUCKET.with_borrow(|bucket| {
            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.borrow();
                    for dep in inner_deps_set.iter() {
                        dep.on_trigger(target_ptr, index);
                        dep.run();
                    }
                }
            }
        });
    }
    pub fn remove_target_ptr(target_ptr: usize) {
        BUCKET.with_borrow_mut(|bucket| {
            if bucket.contains_key(&target_ptr) {
                bucket.remove(&target_ptr);
            }
        })
    }
    pub fn replace_target_ptr(target_ptr: usize, new_target_ptr: usize) {
        BUCKET.with_borrow_mut(|bucket| {
            if let Some(value) = bucket.remove(&target_ptr) {
                bucket.insert(new_target_ptr, value);
            }
        });
    }
}

impl Effect {
    pub fn new<F>(closure: F) -> Rc<Effect>
    where
        F: Fn() -> () + 'static,
    {
        let effect = Rc::new(Effect(Rc::new(EffectFn::new(closure))));
        LOCK_SET_ACTIVE_EFFECT.with_borrow_mut(|lock_set_active_effect| {
            *lock_set_active_effect = true;
        });
        {
            ACTIVE_EFFECT.with_borrow_mut(|active_effect| {
                *active_effect = Some(effect.clone());
            });
        }
        effect.0.run();
        {
            ACTIVE_EFFECT.with_borrow_mut(|active_effect| {
                *active_effect = None;
            });
        }
        LOCK_SET_ACTIVE_EFFECT.with_borrow_mut(|lock_set_active_effect| {
            *lock_set_active_effect = true;
        });
        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.borrow();
        for dep_effect_fn in deps_effect_fn.iter() {
            let mut deps_effect_fn = dep_effect_fn.borrow_mut();
            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
            }
        });

        {
            BUCKET.with_borrow(|bucket| {
                assert_eq!(bucket.keys().len(), 0);
            });
        }
        base_test(serde_json_value.clone().into());
        {
            BUCKET.with_borrow(|bucket| {
                assert_eq!(bucket.keys().len(), 0);
            });
        }
        object_test(serde_json_value.clone().into());
        {
            BUCKET.with_borrow(|bucket| {
                assert_eq!(bucket.keys().len(), 0);
            });
        }
        array_test(serde_json_value.clone().into());
        {
            BUCKET.with_borrow(|bucket| {
                assert_eq!(bucket.keys().len(), 0);
            });
        }
    }
    fn base_test(json: crate::RJson) {
        {
            let count = Rc::new(RefCell::new(0));
            let eff_json1 = json.clone();
            let eff_count = count.clone();
            let _eff = crate::effect(move || {
                *eff_count.borrow_mut() += 1;
                eff_json1.get("name");
            });
            {
                BUCKET.with_borrow(|bucket| {
                    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.borrow().len(), 1);
                });
            }
            {
                assert_eq!(*count.borrow(), 1);
            }
            json.set("age2", 0);
            json.set("name", "name1");
            {
                assert_eq!(*count.borrow(), 2);
            }
            json.set("name", "name2");
            json.set("age", 18);
            {
                assert_eq!(*count.borrow(), 3);
            }
            json.set("name", "name2");
            {
                assert_eq!(*count.borrow(), 4);
            }
        }
        {
            BUCKET.with_borrow(|bucket| {
                let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("name").unwrap();
                assert_eq!(deps.borrow().len(), 0);
            });
        }
    }
    fn object_test(json: crate::RJson) {
        let count = Rc::new(RefCell::new(0));
        let eff_json = json.clone();
        let eff_count = count.clone();
        let _eff = crate::effect(move || {
            *eff_count.borrow_mut() += 1;
            eff_json.get("name");
            eff_json.get("address").get("street");
        });
        {
            BUCKET.with_borrow(|bucket| {
                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.borrow().len(), 1);
                let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("address").unwrap();
                assert_eq!(deps.borrow().len(), 1);
            });
        }
        {
            assert_eq!(*count.borrow(), 1);
        }
        json.set("age2", 0);
        json.set("name", "name1");
        json.set("age", "18");
        {
            assert_eq!(*count.borrow(), 2);
        }
        json.get("address").set("street", "street1");
        {
            assert_eq!(*count.borrow(), 3);
        }
    }
    fn array_test(json: crate::RJson) {
        let count = Rc::new(RefCell::new(0));
        let eff_json1 = json.clone();
        let eff_count = count.clone();
        let _eff = crate::effect(move || {
            *eff_count.borrow_mut() += 1;
            eff_json1.get("name");
            eff_json1.get("phones").get(0);
        });
        {
            BUCKET.with_borrow(|bucket| {
                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.borrow().len(), 1);
                let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("phones").unwrap();
                assert_eq!(deps.borrow().len(), 1);
            });
        }
        {
            assert_eq!(*count.borrow(), 1);
        }
        json.set("age2", 0);
        json.set("name", "name1");
        json.set("age", "18");
        {
            assert_eq!(*count.borrow(), 2);
        }
        json.get("phones").set(0, "1234567");
        {
            assert_eq!(*count.borrow(), 3);
        }

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