{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Crypto.TripleSec.IO where

import Control.Exception

import Control.Monad.Trans.Class
import Control.Monad.IO.Class
import Control.Monad.Except
import Crypto.Random

import Crypto.TripleSec.Class
import Crypto.TripleSec.Internal (ByteArray)
import Crypto.TripleSec.Types

-- | Monad that works "out of the box" for encrypting/decrypting.
--
-- Does not throw exceptions (returns @Either TripleSecException ba@). Use with 'runTripleSecIO'.
type TripleSecIOM = TripleSecIOT IO

-- | Monad transformer for use with any IO based monad stack.
--
-- Does not throw exceptions (returns @Either TripleSecException a@). Use with 'runTripleSecIO'.
newtype TripleSecIOT m a = TripleSecIOT (ExceptT TripleSecException m a)
  deriving (Functor, Applicative, Monad, MonadIO, MonadError TripleSecException, MonadTrans)

instance MonadIO m => MonadRandom (TripleSecIOT m) where
  getRandomBytes n = TripleSecIOT $ (liftIO . getRandomBytes) n

instance Monad m => CanTripleSecDecrypt (TripleSecIOT m)
instance MonadIO m => CanTripleSec (TripleSecIOT m)

-- | Evaluate a 'TripleSecIOT' computation.
runTripleSecIO :: TripleSecIOT m a -> m (Either TripleSecException a)
runTripleSecIO (TripleSecIOT m) = runExceptT m

runInIO :: TripleSecIOM a -> IO a
runInIO action = do
  result <- runTripleSecIO action
  case result of Left err -> throwIO err
                 Right ba -> return ba

-- | 'encrypt' specialized to 'IO'. Throws instead of returning a 'TripleSecException'.
encryptIO :: (ByteArray ba)
          => ba       -- ^ Passphrase
          -> ba       -- ^ Plaintext
          -> IO ba
encryptIO pass plaintext = runInIO (encrypt pass plaintext)

-- | 'encryptWithCipher' specialized to 'IO'. Throws instead of returning a 'TripleSecException'.
encryptWithCipherIO :: ByteArray ba
                    => TripleSec ba
                    -> ba             -- ^ Ciphertext
                    -> IO ba
encryptWithCipherIO cipher plaintext = runInIO (encryptWithCipher cipher plaintext)

-- | 'newCipher' specialized to 'IO'. Throws instead of returning a 'TripleSecException'.
newCipherIO :: ByteArray ba
            => ba             -- ^ Passphrase
            -> IO (TripleSec ba)
newCipherIO password = runInIO (newCipher password)

-- | 'newCipherWithSalt' specialized to 'IO'. Throws instead of returning a 'TripleSecException'.
newCipherWithSaltIO :: ByteArray ba
                    => ba     -- ^ Passphrase
                    -> ba     -- ^ Salt
                    -> IO (TripleSec ba)
newCipherWithSaltIO password salt = runInIO (newCipherWithSalt password salt)


-- | 'decrypt' specialized to 'IO'. Throws instead of returning a 'TripleSecException'.
decryptIO :: ByteArray ba
          => ba     -- ^ Passphrase
          -> ba     -- ^ Ciphertext
          -> IO ba
decryptIO password ciphertext = runInIO (decrypt password ciphertext)

-- | 'decryptWithCipher' specialized to 'IO'. Throws instead of returning a 'TripleSecException'.
decryptWithCipherIO :: ByteArray ba
                    => TripleSec ba
                    -> ba   -- ^ Ciphertext
                    -> IO ba
decryptWithCipherIO cipher ciphertext = runInIO (decryptWithCipher cipher ciphertext)