{-# LANGUAGE DoAndIfThenElse, FlexibleInstances , MultiParamTypeClasses,GADTs, TypeOperators, TupleSections, ScopedTypeVariables,ConstraintKinds,FlexibleContexts,UndecidableInstances #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Control.FRPNow.Until
-- Copyright   :  (c) Atze van der Ploeg 2015
-- License     :  BSD-style
-- Maintainer  :  atzeus@gmail.org
-- Stability   :  provisional
-- Portability :  portable
-- 
-- The until abstraction, and related definitions.
--
--
-- A value of type @BehaviorEnd@ is a behavior and an ending event.
-- This also forms a monad, such that we can write
-- 
-- > do a1 `Until` e1
-- >    b1 `Until` e2
--
-- for behaviors consisting of multiple phases.
-- This concept is similar to "Monadic FRP" (Haskell symposium 2013, van der Ploeg) and
--  the Task monad abstraction (Lambda in motion: Controlling robots with haskell, Peterson, Hudak and Elliot, PADL 1999) 
module Control.FRPNow.BehaviorEnd(
   -- * Until
   BehaviorEnd(..), combineUntil, (.:),parList,
   -- * Derived monads
   -- $compose
   
   till,
   (:.)(..), 
   Swap(..),
   liftLeft,
   liftRight)
  where
import Control.FRPNow.Core
import Control.FRPNow.Lib
import Control.FRPNow.EvStream
import Control.Monad

data BehaviorEnd x a = Until { behavior :: Behavior x, end ::  Event a }

instance Monad (BehaviorEnd x) where
  return x = pure (error "ended!") `Until` pure x
  (b `Until` e) >>= f  =
     let v = f <$> e
         b' = b `switch` (behavior <$> v)
         e' = v >>= end
     in b' `Until` e'

instance Functor (BehaviorEnd x) where fmap = liftM
instance Applicative (BehaviorEnd x) where pure = return ; (<*>) = ap

-- | Combine the behavior of the @Until@ and the other behavior until the
-- with the given function until the end event happens.
combineUntil :: (a -> b -> b) -> BehaviorEnd a x -> Behavior b -> Behavior b
combineUntil f (bx `Until` e) b = (f <$> bx <*> b) `switch` fmap (const b) e

-- | Add the values in the behavior of the @Until@ to the front of the list 
-- until the end event happsens.
(.:) :: BehaviorEnd a x -> Behavior [a] -> Behavior [a]
(.:) = combineUntil (:)

-- | Given an eventstream that spawns behaviors with an end,
-- returns a behavior with list of the values of currently active 
-- behavior ends.
parList :: EvStream (BehaviorEnd b ()) -> Behavior (Behavior [b])
parList = foldBs (pure []) (flip (.:))

-- $compose
-- The monad for @Until@ is a bit restrictive, because we cannot sample other behaviors 
-- in this monad. For this reason we also define a monad for @(Behavior :. Until x)@, 
-- where @ :. @ is functor composition, which can sample other monads. 
-- This relies on the @swap@ construction from "Composing monads", Mark Jones and Luc Duponcheel.
--   

-- | Like 'Until', but the event can now be generated by a behavior (@Behavior (Event a)@) or even
-- (@Now (Event a)@). 
--
-- Name is not "until" to prevent a clash with 'Prelude.until'.
till :: Swap b (BehaviorEnd x) =>
          Behavior x -> b (Event a) -> (b :. BehaviorEnd x) a
till b e = liftLeft e >>= liftRight . (b `Until`)

instance (Swap b e, Sample b) => Sample (b :. e) where sample b = liftLeft (sample b)

assoc :: Functor f => ((f :. g) :. h) x -> (f :. (g :. h)) x
assoc = Close . fmap Close . open . open

coassoc :: Functor f => (f :. (g :. h)) x -> ((f :. g) :. h) x
coassoc = Close . Close . fmap open . open

instance (Functor a, Functor b) => Functor (a :. b) where 
  fmap f = Close . fmap (fmap f) . open

-- | Composition of functors.
newtype (f :. g) x = Close { open :: f (g x) }

-- | Lift a value from the left monad into the composite monad.
liftLeft :: (Monad f, Monad g) => f x -> (f :. g) x 
liftLeft = Close . liftM return 

-- | Lift a value from the right monad into the composite monad.
liftRight :: Monad f => g x -> (f :. g) x 
liftRight  = Close . return 


class (Monad f, Monad g) => Swap f g where
  -- | Swap the composition of two monads.
  -- Laws (from Composing Monads, Jones and Duponcheel)
  -- 
  -- > swap . fmap (fmap f) == fmap (fmap f) . swap
  -- > swap . return        == fmap unit
  -- > swap . fmap return   == return
  -- > prod . fmap dorp     == dorp . prod 
  -- >            where prod = fmap join . swap
  -- >                  dorp = join . fmap swap
  swap :: g (f a) -> f (g a)

instance Plan b => Swap b Event where
  swap = plan

instance (Monad b, Plan b) => Swap b (BehaviorEnd x) where
  swap (Until b e) = liftM (Until b) (plan e)

instance Swap f g => Monad (f :. g) where
  -- see (Composing Monads, Jones and Duponcheel) for proof
  return  = Close . return . return
  m >>= f = joinComp (fmap2m f m)

-- anoyance that Monad is not a subclass of functor
fmap2m f = Close . liftM (liftM f) . open

joinComp :: (Swap b e) => (b :. e) ((b :. e) x) -> (b :. e) x
joinComp = Close . joinFlip . open . fmap2m open

joinFlip :: (Swap b e, Monad e, Monad b) => b (e (b (e x))) -> b (e x)
joinFlip =  liftM join . join . liftM swap 
-- this works as follows, we have 
-- b . e . b . e      flip middle two
-- b . b . e . e      join left and right
-- b . e 


instance (Applicative b, Applicative e) => Applicative (b :. e) where
   pure = Close . pure . pure
   x <*> y = Close $ (<*>) <$> open x <*> open y