module Data.CBRef
   ( CBRef
   , newCBRef
   , readCBRef
   , writeCBRef
   , modifyCBRef
   , atomicModifyCBRef
   , installCallbackCBRef
   )
  where

import Data.IORef

data CBRef a = CBRef (IORef (a, [IO ()]))

newCBRef :: a -> IO (CBRef a)
newCBRef v = fmap CBRef $ newIORef (v,[])

readCBRef :: CBRef a -> IO a
readCBRef (CBRef ref) = fmap fst $ readIORef ref

writeCBRef :: CBRef a -> a -> IO ()
writeCBRef (CBRef ref) v = do
  modifyIORef ref (\(_,cb) -> (v,cb))
  runCallbacks ref

runCallbacks :: IORef (a, [IO ()]) -> IO ()
runCallbacks ref = (sequence_.snd) =<< readIORef ref

modifyCBRef :: CBRef a -> (a -> a) -> IO ()
modifyCBRef (CBRef ref) f = do
 modifyIORef ref (\(v,cb) -> (f v,cb))
 runCallbacks ref

atomicModifyCBRef :: CBRef a -> (a -> (a, b)) -> IO b
atomicModifyCBRef (CBRef ref) f = do
  v <- atomicModifyIORef ref modifier
  runCallbacks ref
  return v
 where modifier (v,cb) = let (v', r) = f v in ((v',cb), r)

installCallbackCBRef :: CBRef a -> IO () -> IO ()
installCallbackCBRef (CBRef ref) p =
  modifyIORef ref (\(v,cb) -> (v,cb ++ [p]))