{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE UnicodeSyntax              #-}


module ZIO.Trans (
    EIO(..), ZIO(..)
  , UEIO, URIO, UZIO, Task, RIO
  , elift, ezlift, zlift
  , uelift, uzlift
  , euUnlift, zuUnlift
  , mapEError, mapZError
  , runEIO, runZIO
  , withEIO, withZIO
  , module Control.Monad.Except
  , module Control.Monad.Reader
  , module UnexceptionalIO
  , module UnexceptionalIO.Trans

) where

import           Control.Monad.Except
import           Control.Monad.Reader hiding (lift)
import           Data.Void (Void, absurd)
import           UnexceptionalIO hiding (fromIO, lift, run)
import           UnexceptionalIO.Trans (UIO, fromIO, run)

-- | Corresponds to IO[E, A] in Scala
newtype EIO e a = EIO { _unEIO :: ExceptT e UIO a }
  deriving ( Functor, Applicative, Monad, MonadError e, MonadFix, Unexceptional )

newtype ZIO r e a = ZIO { _unZIO :: ReaderT r (EIO e) a }
  deriving ( Functor, Applicative, Monad, MonadError e, MonadFix, MonadReader r, Unexceptional )

type UEIO a = EIO Void a

type URIO r a = ZIO r Void a

type UZIO a = ZIO Void Void a

type Task a = ZIO Void SomeNonPseudoException a

type RIO r a = ZIO r SomeNonPseudoException a

elift :: IO a -> EIO SomeNonPseudoException a
elift =  EIO . fromIO

zlift :: IO a -> ZIO r SomeNonPseudoException a
zlift = ZIO . lift . elift

ezlift :: forall r e a. EIO e a -> ZIO r e a
ezlift = ZIO . lift

uelift ::  e a. UIO a -> EIO e a
uelift = EIO . lift

uzlift ::  r e a. UIO a -> ZIO r e a
uzlift = ezlift . uelift

euUnlift :: UEIO a -> UIO a
euUnlift ueio = (either absurd id) <$> ((runExceptT . _unEIO) ueio)

zuUnlift :: UZIO a -> UIO a
zuUnlift = euUnlift . flip runReaderT undefined . _unZIO

runEIO :: MonadIO m => EIO e a -> (e -> m a) -> m a
runEIO eio handler = do
  resEi :: Either e a <- (run . runExceptT . _unEIO) eio
  either handler pure resEi

withEIO :: (e -> e') -> EIO e a -> EIO e' a
withEIO h =  EIO
           . withExceptT h
           . _unEIO

mapEError :: (e -> e') -> EIO e a -> EIO e' a
mapEError = withEIO

runZIO :: MonadIO m => ZIO r e a -> r -> (e -> m a) -> m a
runZIO app env handler = do
  let eio = runReaderT (_unZIO app) env
  runEIO eio handler

withZIO :: r -> (e -> e') -> ZIO r e a -> ZIO r e' a
withZIO r h =  ezlift
             . EIO
             . withExceptT h
             . _unEIO
             . flip runReaderT r
             . _unZIO

mapZError :: (e -> e') -> ZIO r e a -> ZIO r e' a
mapZError h m = do
  r <- ask
  withZIO r h m