{-# LANGUAGE GADTs #-}
-- | Defines 'Phases', an 'Applicative' transformer for scheduling
-- effects during different phases of execution.
module Control.Applicative.Phases
  ( Phases(..)
  , runPhasesForwards, runPhasesBackwards
  , now, later, delay
  ) where

import Control.Applicative (liftA2, (<**>))

-- | An applicative transformer to organize effects into an arbitrary number of
-- phases of execution.
--
-- Use 'now' to schedule actions for the current phase of execution:
--
-- >>> say name = putStrLn name *> pure name
-- >>> runPhasesForwards $ (,,) <$> now (say "Huey") <*> now (say "Dewey") <*> now (say "Louie")
-- Huey
-- Dewey
-- Louie
-- ("Huey","Dewey","Louie")
--
-- Or 'later' to schedule it for the next phase of execution:
--
-- >>> runPhasesForwards $ (,,) <$> later (say "Huey") <*> now (say "Dewey") <*> now (say "Louie")
-- Dewey
-- Louie
-- Huey
-- ("Huey","Dewey","Louie")
--
-- And 'delay' to delay a set of phased actions by one phase:
-- 
-- >>> runPhasesForwards $ delay ((,,) <$> later (say "Huey") <*> now (say "Dewey")) <*> now (say "Louie")
-- Louie
-- Dewey
-- Huey
-- ("Huey","Dewey","Louie")
--
-- Phases can also be run in reverse, but all actions in the same phase still occur in the same order:
--
-- >>> runPhasesBackwards $ (,,) <$> later (say "Huey") <*> now (say "Dewey") <*> now (say "Louie")
-- Huey
-- Dewey
-- Louie
-- ("Huey","Dewey","Louie")
data Phases f a where
  Lift :: f a -> Phases f a
  (:<*>) :: f (a -> b) -> Phases f a -> Phases f b

-- | run the phased actions in forwards order
--
-- >>> runPhasesForwards $ now (putStrLn "hello") *> later (putStrLn "world")
-- hello
-- world
-- >>> runPhasesForwards $ later (putStrLn "hello") *> now (putStrLn "world")
-- world
-- hello
runPhasesForwards :: Applicative f => Phases f a -> f a
runPhasesForwards (Lift ma) = ma
runPhasesForwards (mg :<*> tx) = mg <*> runPhasesForwards tx

-- | run the phased actions in backwards order
--
-- >>> runPhasesBackwards $ now (putStrLn "hello") *> later (putStrLn "world")
-- world
-- hello
-- >>> runPhasesBackwards $ later (putStrLn "hello") *> now (putStrLn "world")
-- hello
-- world
runPhasesBackwards :: Applicative f => Phases f a -> f a
runPhasesBackwards (Lift ma) = ma
runPhasesBackwards (mg :<*> tx) = runPhasesBackwards tx <**> mg

-- | schedule an action to run in the current phase
now :: f a -> Phases f a
now = Lift

-- | schedule  an action to run in the next phase
later :: Applicative f => f a -> Phases f a
later = delay . now

-- | delay all actions by a phase
delay :: Applicative f => Phases f a -> Phases f a
delay ta = pure id :<*> ta

instance Functor f => Functor (Phases f) where
  fmap f (Lift ma) = Lift (fmap f ma)
  fmap f (mg :<*> tx) = fmap (f.) mg :<*> tx

instance Applicative f => Applicative (Phases f) where
  pure = now . pure
  Lift mf <*> Lift ma = Lift $ mf <*> ma
  Lift mf <*> (mh :<*> ty) = liftA2 (.) mf mh :<*> ty
  (mg :<*> tx) <*> Lift ma = liftA2 flip mg ma :<*> tx
  (mg :<*> tx) <*> (mh :<*> ty) = liftA2 (\g h ~(x,y) -> g x (h y)) mg mh :<*> liftA2 (,) tx ty