{-# LANGUAGE
        CPP,
        MultiParamTypeClasses,
        FlexibleInstances,
        IncoherentInstances
  #-}

-- |This module exports no new symbols of its own.  It defines 
--  basic class instances for creating, reading, and writing 'TVar's and
--  (if available) 'TMVar's, and re-exports the types for which it defines 
--  instances as well as the 'atomically' function, which is indispensible
--  when playing with this stuff in ghci.
--
--  Note that this module declares incoherent instances.  The universe should
--  refrain from imploding on itself as long as you don't define
--  \"instance MonadIO STM\".  However, hugs doesn't seem to support 
--  overlapping instances, so I may have to give up on the dream of MonadIO 
--  everywhere, or introduce some major conditional compilation stuff. (or
--  abandon hugs support)

module Data.StateRef.Instances.STM
    ( STM
    , TVar
#ifdef useTMVar
    , TMVar
#endif

    , atomically
    ) where

import Data.StateRef.Types
import Control.Monad.Trans
import Control.Concurrent.STM

-- (STM a) in STM and IO-compatible monads
instance ReadRef (STM a) STM a where
    readReference = id
instance MonadIO m => ReadRef (STM a) m a where
    readReference = liftIO . atomically

-- TVar in STM monad
instance HasRef STM where
    newRef x = do
        sr <- newTVar x
        return (Ref sr)
instance NewRef (TVar a) STM a where
    newReference = newTVar
instance ReadRef (TVar a) STM a where
    readReference = readTVar
instance WriteRef (TVar a) STM a where
    writeReference = writeTVar
instance ModifyRef (TVar a) STM a where
    atomicModifyReference   = defaultAtomicModifyReference
    modifyReference         = defaultModifyReference

-- TVar in IO-compatible monads
instance MonadIO m => NewRef (TVar a) m a where
    newReference = liftIO . newTVarIO
instance MonadIO m => ReadRef (TVar a) m a where
    readReference = liftIO . atomically . readReference
instance MonadIO m => WriteRef (TVar a) m a where
    writeReference ref = liftIO . atomically . writeReference ref
instance MonadIO m => ModifyRef (TVar a) m a where
    modifyReference ref         = liftIO . atomically . modifyReference ref
    atomicModifyReference ref   = liftIO . atomically . atomicModifyReference ref

-- @Ref STM@ in IO-compatible monads
instance MonadIO m => NewRef (Ref STM a) m a where
    newReference x = do
        sr <- liftIO (newTVarIO x)
        return (Ref sr)
instance MonadIO m => ReadRef (Ref STM a) m a where
    readReference (Ref sr) = liftIO (atomically (readReference sr))
instance MonadIO m => WriteRef (Ref STM a) m a where
    writeReference (Ref sr) = liftIO . atomically . writeReference sr
instance MonadIO m => ModifyRef (Ref STM a) m a where
    modifyReference (Ref sr)        = liftIO . atomically . modifyReference sr
    atomicModifyReference (Ref sr)  = liftIO . atomically . atomicModifyReference sr

#ifdef useTMVar
-- TMVar in STM monad
instance NewRef (TMVar a) STM (Maybe a) where
    newReference Nothing = newEmptyTMVar
    newReference (Just x) = newTMVar x
instance ReadRef (TMVar a) STM (Maybe a) where
    readReference tmv = fmap Just (readTMVar tmv) `orElse` return Nothing

-- TMVar in IO-compatible monad
instance MonadIO m => NewRef (TMVar a) m (Maybe a) where
    newReference Nothing = liftIO newEmptyTMVarIO
    newReference (Just x) = liftIO (newTMVarIO x)
instance MonadIO m => ReadRef (TMVar a) m (Maybe a) where
    readReference = liftIO . atomically . readReference
#endif