{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE UndecidableInstances  #-}
{-# OPTIONS_GHC -Wno-orphans #-}
-- | This module exports 'ToSourceIO' and 'FromSourceIO' for 'MachineT' instances.
module Servant.Machines (
    MachineToSourceIO (..),
    ) where

import           Control.Monad.IO.Class
                 (MonadIO (..))
import           Data.Machine
                 (MachineT (..), Step (..))
import           Servant.API.Stream
import qualified Servant.Types.SourceT  as S

-- | Helper class to implement @'ToSourceIO' 'MachineT'@ instance
-- for various monads.
class MachineToSourceIO m where
    machineToSourceIO :: MachineT m k o -> S.SourceT IO o

instance MachineToSourceIO IO where
    machineToSourceIO ma = S.SourceT ($ go ma) where
        go (MachineT m) = S.Effect $ do
            step <- m
            case step of
                Stop         -> return S.Stop
                Yield x m'   -> return (S.Yield x (go m'))
                Await _ _ m' -> return (S.Skip (go m'))

instance MachineToSourceIO m => ToSourceIO o (MachineT m k o) where
    toSourceIO = machineToSourceIO

instance MonadIO m => FromSourceIO o (MachineT m k o) where
    fromSourceIO src = MachineT $ liftIO $ S.unSourceT src go
      where
        go :: S.StepT IO o -> IO (Step k o (MachineT m k o))
        go S.Stop        = return Stop
        go (S.Error err) = fail err
        go (S.Skip s)    = go s
        go (S.Effect ms) = ms >>= go
        go (S.Yield x s) = return (Yield x (MachineT (liftIO (go s))))
    {-# SPECIALIZE INLINE fromSourceIO :: SourceIO o -> MachineT IO k o #-}