{-# LANGUAGE ExistentialQuantification #-}
module Data.Binding.Simple (module Data.Variable, Bindable, bind, Source) where

import Data.Variable

-- | A data binding:
-- @a@ is the type of the data source
-- @a -> d@ is a function that extracts data from the source
-- @t@ is the binding target
-- @d -> t -> IO ()@ is a function that applies data to the target
data Binding a = forall d t. Binding (a -> d) t (t -> d -> IO ())

-- | Binding Source
data Source v a = Variable v => Source {bindings :: v [Binding a] -- ^ the source's bindings
                                       ,var      :: v a}          -- ^ the bound variable

-- | Update a single binding.
update' :: a -> Binding a -> IO ()
update' source (Binding extract target apply) = apply target $ extract source

-- | Update a binding source's bindings.
update :: Source v a -> IO ()
update (Source bindings var) = do bindings <- readVar bindings
                                  a <- readVar var
                                  mapM_ (update' a) bindings

instance Variable v => Variable (Source v) where
   newVar a = do bindings <- newVar []
                 v <- newVar a
                 return $ Source bindings v

   readVar = readVar . var

   writeVar s a = writeVar (var s) a >> update s

   modifyVar s f = modifyVar (var s) f >> update s

   modifyVar' s f = do b <- modifyVar' (var s) f
                       update s
                       return b

-- | Sources for data binding.
class Variable b => Bindable b where
   -- | Create a data binding
   bind :: b a               -- ^ the binding source
        -> (a -> d)          -- ^ a function that extracts data from the source
        -> t                 -- ^ the binding target
        -> (t -> d -> IO ()) -- ^ a function that applies data to the target
        -> IO ()

instance Variable v => Bindable (Source v) where
   bind (Source bindings var) extract target apply =
      do let binding = Binding extract target apply
         --update the new binding
         a <- readVar var
         update' a binding
         --add the new binding to the list
         modifyVar bindings (binding:)