{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
-- |
-- Module:       Control.Monad.Freer.State
-- Description:  State effects, for state-carrying computations.
-- Copyright:    (c) 2016 Allele Dev; 2017 Ixperta Solutions s.r.o.
-- License:      BSD3
-- Maintainer:   ixcom-core@ixperta.com
-- Stability:    experimental
-- Portability:  GHC specific language extensions.
--
-- Composable handler for 'State' effects. Handy for passing an updatable state
-- through a computation.
--
-- Some computations may not require the full power of 'State' effect:
--
-- * For a read-only state, see "Control.Monad.Freer.Reader".
-- * To accumulate a value without using it on the way, see
--   "Control.Monad.Freer.Writer".
--
-- Using <http://okmij.org/ftp/Haskell/extensible/Eff1.hs> as a starting point.
module Control.Monad.Freer.State
    (
    -- * State Effect
      State(..)

    -- * State Operations
    , get
    , put
    , modify

    -- * State Handlers
    , runState
    , evalState
    , execState

    -- * State Utilities
    , transactionState
    )
  where

import Control.Monad ((>>), (>>=), return)
import Data.Either (Either(Left, Right))
import Data.Functor ((<$>), fmap)
import Data.Maybe (Maybe(Just))
import Data.Proxy (Proxy)
import Data.Tuple (fst, snd)

import Control.Monad.Freer.Internal
    ( Eff(E, Val)
    , Member
    , Union
    , decomp
    , prj
    , qApp
    , qComp
    , send
    , tsingleton
    )


--------------------------------------------------------------------------------
                         -- State, strict --
--------------------------------------------------------------------------------

-- | Strict 'State' effects: one can either 'Get' values or 'Put' them.
data State s a where
    Get :: State s s
    Put :: !s -> State s ()

-- | Retrieve the current value of the state of type @s :: *@.
get :: Member (State s) effs => Eff effs s
get = send Get

-- | Set the current state to a specified value of type @s :: *@.
put :: Member (State s) effs => s -> Eff effs ()
put s = send (Put s)

-- | Modify the current state of type @s :: *@ using provided function
-- @(s -> s)@.
modify :: Member (State s) effs => (s -> s) -> Eff effs ()
modify f = fmap f get >>= put

-- | Handler for 'State' effects.
runState :: Eff (State s ': effs) a -> s -> Eff effs (a, s)
runState (Val x) s = return (x, s)
runState (E u q) s = case decomp u of
    Right Get      -> runState (qApp q s) s
    Right (Put s') -> runState (qApp q ()) s'
    Left  u'       -> E u' (tsingleton (\x -> runState (qApp q x) s))

-- | Run a 'State' effect, returning only the final state.
execState :: Eff (State s ': effs) a -> s -> Eff effs s
execState st s = snd <$> runState st s

-- | Run a State effect, discarding the final state.
evalState :: Eff (State s ': effs) a -> s -> Eff effs a
evalState st s = fst <$> runState st s

-- | An encapsulated State handler, for transactional semantics. The global
-- state is updated only if the 'transactionState' finished successfully.
transactionState
    :: forall s effs a
    .  Member (State s) effs
    => Proxy s
    -> Eff effs a
    -> Eff effs a
transactionState _ m = do s <- get; loop s m
  where
    loop :: s -> Eff effs a -> Eff effs a
    loop s (Val x)                = put s >> return x
    loop s (E (u :: Union r b) q) = case prj u :: Maybe (State s b) of
        Just Get      -> loop s (qApp q s)
        Just (Put s') -> loop s'(qApp q ())
        _             -> E u (tsingleton k) where k = qComp q (loop s)