{-# LANGUAGE LambdaCase             #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
module Control.Tweak.Internal where
import Control.Tweak.Var
import Control.Tweak.Tweakable
import Control.Concurrent.STM
import Control.Applicative

-- | 'Maker' is the 'Applicative' used to create 'Tweakable' expressions
--  Use the 'Applicative' interface or the 'Applicative' helpers
-- '.$.' and '.*.'
data Maker a = Maker { runmaker :: IO (Tweakable a) }

-- | Turn a 'Tweakable' into a 'Maker' so it can be combined
--   with other 'Maker's
make :: Tweakable a -> Maker a
make = Maker . pure

instance Functor Maker where
   fmap f (Maker mx) = Maker $ do
      af <- Pure <$> newVar f
      apply (return af) mx

instance Applicative Maker where
   pure = Maker . fmap Pure . newVar

   Maker mf <*> Maker mx = Maker $ do
      apply mf mx

-- This is workhorse or both fmap and ap
apply :: IO (Tweakable (a -> b)) -> IO (Tweakable a) -> IO (Tweakable b)
apply mf mx = do 
   -- get the Tweakables out of IO 
   x <- mx
   f <- mf
   
   -- readCacheuate them and apply the result
   let evalApp = readCacheSTM f <*> readCacheSTM x
   -- make the new var with the result of the args applied
   c <- atomically $ newVarSTM =<< evalApp

   -- the function to call on an update
   let updater = writeVarSTM c =<< evalApp

   -- add the update respectively
   addChild x (AnyVar c) updater
   addChild f (AnyVar c) updater

   return $ App c f x


-- TODO make something like applicative that works for either 

class Funktor g f where
   fcrap :: (a -> b) -> f a -> g b

instance Funktor Maker Var where
   fcrap f = fcrap f . Pure

instance Funktor Maker Tweakable where
   fcrap f = fcrap f . make 

instance Funktor Maker Maker where
   fcrap = fmap

infixl 4 .$.
-- | This is slight variation on '<$>'. Use '.$.' and '.*.' avoid explicit 
--  calls to 'make' and 'Pure'. 
--
-- Unlike Functor the input and output * -> * type can change. There is no reasoning
-- or laws behind it, it is just sugar.
-- 
-- The Funktor type class is closed and private. There are only instances
-- for 'Maker', 'Tweakable', and 'Var'.   
(.$.) :: Funktor g f => (a -> b) -> f a -> g b
(.$.) = fcrap

infixl 4 .*.
-- | This is slight variation on '<*>'. Use '.$.' and '.*.' avoid explicit 
--  calls to 'make' and 'Pure'. 
--
-- Unlike Apply, with Comply the input and output * -> * type can change. 
-- Like Funktor, there is no reasoning or laws behind it, it is just sugar.
-- 
-- The Comply type class is closed and private. There are only instances
-- for 'Maker', 'Tweakable', and 'Var'.   
(.*.) :: Comply g h => g (a -> b) -> h a -> g b
(.*.) = connect   

class Comply g h where
   connect :: g (a -> b) -> h a -> g b

instance Comply Maker Var where
   connect f = connect f . Pure 

instance Comply Maker Tweakable where
   connect f = connect f . make

instance Comply Maker Maker where
   connect f x = f <*> x