{-# LANGUAGE RankNTypes, TypeFamilies, FlexibleContexts, FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables, MultiParamTypeClasses #-}
{-# LANGUAGE LambdaCase, TypeOperators #-}

-- | Common operators for references. References bind the types of the read and write monads of
-- a reference.
--
-- The naming of the operators follows the given convetions:
--
--  * There are four kinds of operator for every type of reference.
-- The operators are either getters (@^_@), setters (@_=@), monadic updaters (@_~@),
-- pure updaters (@_-@) or action performers (@_|@).
-- The @_@ will be replaced with the signs of the monads accessable.
--
-- * There are pure operators for 'Lens' (@.@), partial operators for 'Partial' lenses (@?@),
-- operators for 'Traversal' (@*@), and operators that work inside 'IO' for 'IOLens' (@!@).
--
-- * Different reference types can be combined, the outermost monad is the first character.
-- Example: Partial IO lens (@?!@). But partial lens and traversal combined is simply a traversal.
--
-- * Generic operators (@#@) do not bind the types of the monads, so they must disambiguated manually.
--

module Control.Reference.Operators where
import Control.Reference.Representation
import Control.Monad.Identity
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.List

-- * Getters

-- | Gets the referenced data in the monad of the lens.
-- Does not bind the type of the writer monad, so the reference must have its type disambiguated.
(^#) :: RefMonads w r => s -> Reference w r s t a b -> r a
a ^# l = refGet l return a
infixl 4 ^#

-- | Pure version of '^#'
(^.) :: s -> Lens' s t a b -> a
a ^. l = runIdentity (a ^# l)
infixl 4 ^.

-- | Partial version of '^#'
(^?) :: s -> Partial' s t a b -> Maybe a
a ^? l = a ^# l
infixl 4 ^?

-- | Traversal version of '^#'
(^*) :: s -> Traversal' s t a b -> [a]
a ^* l = a ^# l
infixl 4 ^*

-- | IO version of '^#'
(^!) :: s -> IOLens' s t a b -> IO a
a ^! l = a ^# l
infixl 4 ^!

-- | IO partial version of '^#'
(^?!) :: s -> IOPartial' s t a b -> IO (Maybe a)
a ^?! l = runMaybeT (a ^# l)
infixl 4 ^?!

-- | IO traversal version of '^#'
(^*!) :: s -> IOTraversal' s t a b -> IO [a]
a ^*! l = runListT (a ^# l)
infixl 4 ^*!

-- * Setters

-- | Sets the referenced data to the given pure value in the monad of the reference.
--
-- Does not bind the type of the reader monad, so the reference must have its type disambiguated.
(#=) :: Reference w r s t a b -> b -> s -> w t
l #= v = refSet l v
infixl 4 #=

-- | Pure version of '#='
(.=) :: Lens' s t a b -> b -> s -> t
l .= v = runIdentity . (l #= v)
infixl 4 .=

-- | Partial version of '#='
(?=) :: Partial' s t a b -> b -> s -> t
l ?= v = runIdentity . (l #= v)
infixl 4 ?=
         
-- | Traversal version of '#='
(*=) :: Traversal' s t a b -> b -> s -> t
l *= v = runIdentity . (l #= v)
infixl 4 *=

-- | IO version of '#='
(!=) :: IOLens' s t a b -> b -> s -> IO t
l != v = l #= v
infixl 4 !=

-- | Partial IO version of '#='
(?!=) :: IOPartial' s t a b -> b -> s -> IO t
l ?!= v = l #= v
infixl 4 ?!=

-- | Traversal IO version of '#='
(*!=) :: IOTraversal' s t a b -> b -> s -> IO t
l *!= v = l #= v
infixl 4 *!=

-- * Updaters

-- | Applies the given monadic function on the referenced data in the monad of the lens.
--
-- Does not bind the type of the reader monad, so the reference must have its type disambiguated.
(#~) :: Reference w r s t a b -> (a -> w b) -> s -> w t
l #~ trf = refUpdate l trf
infixl 4 #~

-- | Pure version of '#~'
(.~) :: Lens' s t a b -> (a -> Identity b) -> s -> t
l .~ trf = runIdentity . (l #~ trf)
infixl 4 .~

-- | Partial version of '#~'
(?~) :: Partial' s t a b -> (a -> Identity b) -> s -> t
l ?~ trf = runIdentity . (l #~ trf)
infixl 4 ?~

-- | Traversal version of '#~'
(*~) :: Traversal' s t a b -> (a -> Identity b) -> s -> t
l *~ trf = runIdentity . (l #~ trf)
infixl 4 *~

-- | IO version of '#~'
(!~) :: IOLens' s t a b -> (a -> IO b) -> s -> IO t
l !~ trf = l #~ trf
infixl 4 !~

-- | Partial IO version of '#~'
(?!~) :: IOPartial' s t a b -> (a -> IO b) -> s -> IO t
l ?!~ trf = l #~ trf
infixl 4 ?!~

-- | Traversal IO version of '#~'
(*!~) :: IOTraversal' s t a b -> (a -> IO b) -> s -> IO t
l *!~ trf = l #~ trf
infixl 4 *!~

-- * Updaters with pure function inside

-- | Applies the given pure function on the referenced data in the monad of the lens.
--
-- Does not bind the type of the reader monad, so the reference must have its type disambiguated.
(#-) :: Monad w => Reference w r s t a b -> (a -> b) -> s -> w t
l #- trf = l #~ return . trf
infixl 4 #-

-- | Pure version of '#-'
(.-) :: Lens' s t a b -> (a -> b) -> s -> t
l .- trf = l .~ return . trf
infixl 4 .-

-- | Partial version of '#-'
(?-) :: Partial' s t a b -> (a -> b) -> s -> t
l ?- trf = l ?~ return . trf
infixl 4 ?-

-- | Traversal version of '#-'
(*-) :: Traversal' s t a b -> (a -> b) -> s -> t
l *- trf = l *~ return . trf
infixl 4 *-

-- | IO version of '#-'
(!-) :: IOLens' s t a b -> (a -> b) -> s -> IO t
l !- trf = l !~ return . trf
infixl 4 !-

-- | Partial IO version of '#-'
(?!-) :: IOPartial' s t a b -> (a -> b) -> s -> IO t
l ?!- trf = l ?!~ return . trf
infixl 4 ?!-

-- | Traversal IO version of '#-'
(*!-) :: IOTraversal' s t a b -> (a -> b) -> s -> IO t
l *!- trf = l *!~ return . trf
infixl 4 *!-

-- * Updaters with only side-effects

-- | Performs the given monadic action on referenced data while giving back the original data.
--
-- Does not bind the type of the reader monad, so the reference must have its type disambiguated.
(#|) :: Monad w => Reference w r s s a a -> (a -> w x) -> s -> w s
l #| act = l #~ (\v -> act v >> return v)
infixl 4 #|

-- | IO version of '#|'
(!|) :: IOLens' s s a a -> (a -> IO c) -> s -> IO s
l !| act = l #| act
infixl 4 !|

-- | Partial IO version of '#|'
(?!|) :: IOPartial' s s a a -> (a -> IO c) -> s -> IO s
l ?!| act = l #| act
infixl 4 ?!|

-- | Traversal IO version of '#|'
(*!|) :: IOTraversal' s s a a -> (a -> IO c) -> s -> IO s
l *!| act = l #| act
infixl 4 *!|

-- * Binary operators on references

-- | Composes two references. They must be of the same kind.
--
-- If reference @r@ accesses @b@ inside the context @a@, and reference @p@ accesses @c@ inside the context @b@,
-- than the reference @r&p@ will access @c@ inside @a@.
--
-- Composition is associative: @ (r&p)&q = r&(p&q) @
(&) :: (Monad w, Monad r) => Reference w r s t c d -> Reference w r c d a b
    -> Reference w r s t a b
(&) l1 l2 = Reference (refGet l1 . refGet l2) 
                      (refUpdate l1 . refSet l2) 
                      (refUpdate l1 . refUpdate l2)
infixl 6 &

-- | Adds two references.
--
-- Using this operator may result in accessing the same parts of data multiple times.
-- For example @ twice = self &+& self @ is a reference that accesses itself twice:
--
-- > a ^* twice == [a,a]
-- > (twice *= x) a == x
-- > (twice *- f) a == f (f a)
--
-- Addition is commutative only if we do not consider the order of the results from a get,
-- or the order in which monadic actions are performed.
--
(&+&) :: (Monad w, MonadPlus r, MMorph [] r)
         => Reference w r s s a a -> Reference w r s s a a
         -> Reference w r s s a a
l1 &+& l2 = Reference (\f a -> refGet l1 f a `mplus` refGet l2 f a) 
                      (\v -> refSet l1 v >=> refSet l2 v )
                      (\trf -> refUpdate l1 trf
                                 >=> refUpdate l2 trf )
infixl 5 &+&