use std::{
  rc::Rc,
  cell::{
    RefCell,
    Ref,
    RefMut
  }
};

pub struct RcCell<A> {
  value: Rc<RefCell<A>>
}

impl<A> Clone for RcCell<A> {
  fn clone(&self) -> RcCell<A> {
    let value = Rc::clone(&self.value);
    RcCell {
      value
    }
  }
}

impl<A> RcCell<A> {
  pub fn new(value: A) -> RcCell<A> {
    let value = RefCell::new(value);
    let value = Rc::new(value);
    RcCell {
      value
    }
  }

  pub fn borrow(&self) -> Ref<A> {
    self.value.borrow()
  }

  pub fn borrow_mut(&self) -> RefMut<A> {
    self.value.borrow_mut()
  }

  pub fn update(&self, u: fn(RefMut<A>)) {
    u(self.borrow_mut())
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[derive(Debug, PartialEq)]
  struct Data<A> {
    value: A
  }

  impl<A> Data<A> {
    fn new(value: A) -> Data<A> {
      Data {
        value,
      }
    }
  }

  #[test]
  fn multiple_readers() {
    let data: Data<i32> = Data::new(1);
    let data = RcCell::new(data);
  
    let clone1 = data.clone();
    let clone2 = data.clone();

    assert_eq!(clone1.borrow().value, 1);
    assert_eq!(*clone1.borrow(), *clone2.borrow());
  }

  #[test]
  fn multiple_writers() {
    let data: Data<i32> = Data::new(1);
    let data = RcCell::new(data);
  
    let clone1 = data.clone();
    clone1.update(|mut d| d.value += 1);
    let clone2 = data.clone();
    clone2.update(|mut d| d.value *= 3);

    assert_eq!(data.borrow().value, 6);
    assert_eq!(*clone1.borrow(), *clone2.borrow());
  }

  #[test]
  fn example() {
    #[derive(Debug, PartialEq)]
    struct Data<A> {
      value: A
    }
  
    impl<A> Data<A> {
      fn new(value: A) -> Data<A> {
        Data {
          value,
        }
      }
    }

    let data: Data<i32> = Data::new(1);

    let counter: RcCell<Data<i32>> = RcCell::new(data);
    let counter_a: RcCell<Data<i32>> = counter.clone();
    let counter_b: RcCell<Data<i32>> = counter.clone();

    counter_a.update(|mut v| v.value += 1);
    counter_b.borrow_mut().value *= 3;

    assert_eq!(counter.borrow().value, 6);
    assert_eq!(*counter_a.borrow(), *counter_b.borrow());
  }
}
