{-# LANGUAGE RankNTypes #-}
module Control.MLens.ExtRef
    ( module Control.MLens.NewRef
    -- * Monads with state expansion
    , ExtRef (extRef)
    -- * Applications
    , undoTr
    ) where

--import Data.IORef
import Control.Monad
import Control.Category
import Prelude hiding ((.), id)

import Control.MLens.NewRef
import Data.MLens
import Data.MLens.Ref

{- |
Suppose that @k@ is a pure lens, and

@s <- extRef r k a0@.

The following laws should hold:

 *  @s@ is a pure reference.

 *  @(k . s)@ behaves exactly as @r@.

 *  The initial value of @s@ is the result of @(readRef r >>= setL k a0)@.

Moreover, @(extRef r k a0)@ should not change the value of @r@.

The following two operations should be identical:

@newRew x@

@extRef unitLens unitLens x@

For examples, see "Control.MLens.ExtRef.Pure.Test".
-}
class NewRef m => ExtRef m where
    extRef :: Ref m b -> MLens m a b -> a -> m (Ref m a)


-- | Undo-redo state transformation
undoTr
    :: ExtRef m =>
       (a -> a -> Bool)     -- ^ equality on state
    -> Ref m a              -- ^ reference of state
    -> m ( m (Maybe (m ()))   
         , m (Maybe (m ()))
         )  -- ^ undo and redo actions
undoTr eq r = do
    ku <- extRef r undoLens ([], [])
    let try f = liftM (fmap (writeRef ku) . f) $ readRef ku
    return (try undo, try redo)
  where
    undoLens = lens get set where
        get = head . fst
        set x (x' : xs, ys) | eq x x' = (x: xs, ys)
        set x (xs, _) = (x : xs, [])

    undo (x: xs@(_:_), ys) = Just (xs, x: ys)
    undo _ = Nothing

    redo (xs, y: ys) = Just (y: xs, ys)
    redo _ = Nothing