{-# LANGUAGE FlexibleInstances, TypeFamilies, GeneralizedNewtypeDeriving #-}

-- | Provides a 'MonadArray' implementation for any 'MArray'.  Examples of when this would be useful include unboxed arrays and array implementations for specialized monads like STM.
module Control.Monad.Array.MArray (MArrayM, liftMArray, evalMArrayM, execMArrayM, evalMArrayM_, execMArrayM_) where

import Control.Monad.Array.Class
import Control.Monad.ST.Class

import GHC.Arr

import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.Trans

import Data.Array.Base
import Control.Monad

-- | Provides a monadic wrapper around any 'MArray' implementation.
newtype MArrayM a e m x = MArrayM {runMArrayM :: ReaderT e (StateT (a Int e) m) x} deriving (Monad, MonadFix, MonadPlus, MonadIO, MonadST)

-- | Executes an 'MArrayM' computation with the specified initial size and default element.
evalMArrayM :: (Monad m, MArray a e m) => Int -> e -> MArrayM a e m x -> m x
evalMArrayM n d m = newArray (0, n-1) d >>= evalStateT (runReaderT (runMArrayM m) d)

-- | Executes an 'MArrayM' computation with the specified initial size and default element, returning the final array.
execMArrayM :: (Monad m, MArray a e m) => Int -> e -> MArrayM a e m x -> m (a Int e)
execMArrayM n d m = newArray (0, n-1) d >>= execStateT (runReaderT (runMArrayM m) d)

-- | Executes an 'MArrayM' computation with the specified initial size and no default element.
evalMArrayM_ :: (Monad m, MArray a e m) => Int -> MArrayM a e m x -> m x
evalMArrayM_ n = evalMArrayM n emptyElement

-- | Executes an 'MArrayM' computation with the specified initial size and no default element, returning the final array.
execMArrayM_ :: (Monad m, MArray a e m) => Int -> MArrayM a e m x -> m (a Int e)
execMArrayM_ n = execMArrayM n emptyElement

emptyElement :: e
emptyElement = error "Undefined array element"

instance (Monad m, MArray a e m) => MonadArray (MArrayM a e m) where
	type ArrayElem (MArrayM a e m) = e
	unsafeWriteAt i x = MArrayM $ do	arr <- get
						lift2 $ unsafeWrite arr i x
	unsafeReadAt i = MArrayM $ do	arr <- get
					lift2 $ unsafeRead arr i
	askSize = MArrayM (get >>= lift2 . liftM rangeSize . getBounds)
	resize n = do	prevSize <- askSize
			prevConts <- mapM unsafeReadAt [0..prevSize-1]
			def <- MArrayM ask
			arr' <- liftMArray $ newListArray (0, n-1) (prevConts ++ replicate (n - prevSize) def)
			MArrayM $ put arr'
	askElems = MArrayM $ get >>= lift2 . getElems
	askAssocs = MArrayM $ get >>= lift2 . getAssocs

-- | Lifts a computation in the underlying monad to an 'MArrayM' computation on an array in the same monad.
liftMArray :: (Monad m, MArray a e m) => m x -> MArrayM a e m x
liftMArray = MArrayM . lift2

lift2 :: (MonadTrans t1, MonadTrans t2, Monad m, Monad (t2 m)) => m a -> t1 (t2 m) a
lift2 = lift . lift