    {-# LANGUAGE CPP                       #-}
    {-# LANGUAGE ExistentialQuantification #-}
    {-# LANGUAGE TypeFamilies              #-}
    {-# LANGUAGE FlexibleContexts          #-}
    {-# LANGUAGE FlexibleInstances         #-}
    {-# LANGUAGE MultiParamTypeClasses     #-}
    {-# LANGUAGE ScopedTypeVariables       #-}
    {-# LANGUAGE ConstraintKinds           #-}

    -- | This module exposes the low-level internal details of ciphers. Do
    -- not import this module unless you want to implement a new cipher or
    -- give a new implementation of an existing cipher.
    module Raaz.Cipher.Internal
           (
             -- * Internals of a cipher.
             -- $cipherdoc$
             Cipher, CipherMode(..)
             -- ** Cipher implementation
           , CipherI(..), SomeCipherI(..)

             -- ** Stream ciphers.
             -- $streamcipher$
           , StreamCipher, makeCipherI
           , transform, transform'
           -- ** Unsafe encryption and decryption.
           -- $unsafecipher$
           --
           , unsafeEncrypt, unsafeDecrypt, unsafeEncrypt', unsafeDecrypt'

           ) where

    import Control.Monad.IO.Class          (liftIO)
    import Data.ByteString.Internal as IB
    import Foreign.Ptr                     (castPtr)
    import System.IO.Unsafe                (unsafePerformIO)

    import Raaz.Core
    import Raaz.Core.Util.ByteString as B

    -- $cipherdoc$
    --
    -- Ciphers provide symmetric encryption in the raaz library and are
    -- captured by the type class `Cipher`.  They are instances of the
    -- class `Symmetric` and the associated type `Key` captures the all
    -- that is required to determine the encryption and decryption
    -- process. In most ciphers, this includes what is know as the
    -- _encryption key_ as well as the _initialisation vector_.
    --
    -- Instances of `Cipher` is only required to provide full block
    -- encryption/decryption algorithms.  Implementations are captured by
    -- two types.
    --
    -- [`CipherI`:] Values of this type that captures implementations of a
    -- cipher.  This type is parameterised over the memory element that is
    -- used internally by the implementation.
    --
    -- [`SomeCipherI`:] The existentially quantified version of `CipherI`
    -- over its memory element. By wrapping the memory element inside the
    -- existential quantifier, values of this type exposes only the
    -- interface and not the internals of the implementation. The
    -- `Implementation` associated type of a cipher is the type
    -- `SomeCipherI`
    --
    -- To support a new cipher, a developer needs to:
    --
    -- 1. Define a new type which captures the cipher. This type should be
    --    an instance of the class `Cipher`.
    --
    -- 2. Define an implementation, i.e. a value of the type `SomeCipherI`.
    --
    -- 3. Define a recommended implementation, i.e. an instance of the
    --    type class `Raaz.Core.Primitives.Recommendation`
    --

    -- $streamcipher$
    --
    -- Stream ciphers are special class of ciphers which can encrypt
    -- messages of any length (not necessarily multiples of block length).
    -- Typically, stream ciphers are obtained by xoring the data with a
    -- stream of prg values that the stream ciphers generate. As a
    -- consequence, the encryption and decryption is the same algorithm.
    -- one can also use the stream cipher as a pseudo-random generator.
    --
    -- We have the class `StreamCipher` that captures valid stream ciphers.
    --

    -- | Block cipher modes.
    data CipherMode = CBC -- ^ Cipher-block chaining
                    | CTR -- ^ Counter
                    deriving (Show, Eq)

    -- | The implementation of a block cipher.
    data CipherI cipher encMem decMem = CipherI
         { cipherIName         :: String
         , cipherIDescription  :: String
           -- | The underlying block encryption function.
         , encryptBlocks :: Pointer -> BLOCKS cipher -> MT encMem ()
           -- | The underlying block decryption function.
         , decryptBlocks :: Pointer -> BLOCKS cipher -> MT decMem ()
         , cipherStartAlignment :: Alignment
         }

    -- | Type constraints on the memory of a block cipher implementation.
    type CipherM cipher encMem decMem = ( Initialisable encMem (Key cipher)
                                        , Initialisable decMem (Key cipher)
                                        , Primitive cipher
                                        ) -- TODO: More need initialisable from buffer.

    -- | Some implementation of a block cipher. This type is existentially
    -- quantifies over the memory used in the implementation.
    data SomeCipherI cipher =
      forall encMem decMem . CipherM cipher encMem decMem
      => SomeCipherI (CipherI cipher encMem decMem)

    instance BlockAlgorithm (CipherI cipher encMem decMem) where
      bufferStartAlignment = cipherStartAlignment

    instance Describable (CipherI cipher encMem decMem) where
      name        = cipherIName
      description = cipherIDescription

    instance Describable (SomeCipherI cipher) where
      name         (SomeCipherI cI) = name cI
      description  (SomeCipherI cI) = description cI

    instance BlockAlgorithm (SomeCipherI cipher) where
      bufferStartAlignment (SomeCipherI imp) = bufferStartAlignment imp

    -- | Class capturing ciphers. The implementation of this class should
    -- give an encryption and decryption algorithm for messages of length
    -- which is a multiple of the block size.  Needless to say, the
    -- encryption and decryption should be inverses of each other for such
    -- messages.
    class (Primitive cipher, Implementation cipher ~ SomeCipherI cipher, Describable cipher)
          => Cipher cipher

    -- | Class that captures stream ciphers. An instance of `StreamCipher`
    -- should be an instance of `Cipher`, with the following additional
    -- constraints.
    --
    -- 1. The encryption and decryption should be the same algorithm.
    --
    -- 2. Encryption/decryption can be applied to a messages of length @l@
    --    even if @l@ is not a multiple of block length.
    --
    -- 3. The encryption of a prefix of a length @l@ of a message @m@
    --    should be the same as the @l@ length prefix of the encryption of
    --    @m@.
    --
    -- It is the duty of the implementer of the cipher to ensure that the
    -- above conditions are true before declaring an instance of a stream
    -- cipher.
    class Cipher cipher => StreamCipher cipher

    -- | Constructs a `CipherI`  value out of a stream transformation function. Useful in
    --   building a Cipher instance of a stream cipher.
    makeCipherI :: String                                -- ^ name
                -> String                                -- ^ description
                -> (Pointer -> BLOCKS prim -> MT mem ()) -- ^ stream transformer
                -> Alignment                             -- ^ buffer starting alignment
                -> CipherI prim mem mem
    makeCipherI nm des trans = CipherI nm des trans trans

    ------------------ Unsafe cipher operations ------------------------

    -- $unsafecipher$
    --
    -- We expose some unsafe functions to encrypt and decrypt bytestrings.
    -- These function works correctly only if the input byte string has a
    -- length which is a multiple of the block size of the cipher and
    -- hence are unsafe to use as general methods of encryption and
    -- decryption of data.  Use these functions for testing and
    -- benchmarking and nothing else.
    --
    -- There are multiple ways to handle arbitrary sized strings like
    -- padding, cipher block stealing etc. They are not exposed here
    -- though.

    -- | Encrypt the given `ByteString`. This function is unsafe because
    -- it only works correctly when the input `ByteString` is of length
    -- which is a multiple of the block length of the cipher.
    unsafeEncrypt' :: Cipher c
                   => c                -- ^ The cipher to use
                   -> Implementation c -- ^ The implementation to use
                   -> Key c            -- ^ The key to use
                   -> ByteString       -- ^ The string to encrypt.
                   -> ByteString
    unsafeEncrypt' c simp@(SomeCipherI imp) key bs = IB.unsafeCreate sbytes go
      where sz           = atMost (B.length bs) `asTypeOf` blocksOf 1 c
            BYTES sbytes = inBytes sz
            go    ptr    = allocBufferFor simp sz $ \ buf -> insecurely $ do
              initialise key
              liftIO $ unsafeNCopyToPointer sz bs buf -- Copy the input to buffer.
              encryptBlocks imp buf sz
              liftIO $ Raaz.Core.memcpy (destination (castPtr ptr)) (source buf) sz

    -- | Transforms a given bytestring using a stream cipher. We use the
    -- transform instead of encrypt/decrypt because for stream ciphers
    -- these operations are same.

    transform' :: StreamCipher c
               => c
               -> Implementation c
               -> Key c
               -> ByteString
               -> ByteString
    transform' c simp@(SomeCipherI imp) key bs = unsafePerformIO $ IB.createAndTrim (fromEnum $ inBytes blks) action
       where blks          = atLeast len `asTypeOf` blocksOf 1 c
             len           = B.length bs
             action ptr    = allocBufferFor simp blks $ \ buf -> insecurely $ do
               initialise key
               liftIO $ unsafeCopyToPointer bs buf -- copy data into the buffer
               encryptBlocks imp buf blks          -- encrypt it
               liftIO $ Raaz.Core.memcpy (destination (castPtr ptr)) (source buf) len
                                                   -- copy it back to the actual pointer.
               return $ fromIntegral len

    -- | Transform a given bytestring using the recommended implementation
    -- of a stream cipher.
    transform :: (StreamCipher c, Recommendation c)
               => c
               -> Key c
               -> ByteString
               -> ByteString
    transform c = transform' c $ recommended c

    -- | Encrypt using the recommended implementation. This function is
    -- unsafe because it only works correctly when the input `ByteString`
    -- is of length which is a multiple of the block length of the cipher.
    unsafeEncrypt :: (Cipher c, Recommendation c)
                  => c            -- ^ The cipher
                  -> Key c        -- ^ The key to use
                  -> ByteString   -- ^ The string to encrypt
                  -> ByteString
    unsafeEncrypt c = unsafeEncrypt' c $ recommended c

    -- | Decrypts the given `ByteString`. This function is unsafe because
    -- it only works correctly when the input `ByteString` is of length
    -- which is a multiple of the block length of the cipher.
    unsafeDecrypt' :: Cipher c
                   => c                -- ^ The cipher to use
                   -> Implementation c -- ^ The implementation to use
                   -> Key c            -- ^ The key to use
                   -> ByteString       -- ^ The string to encrypt.
                   -> ByteString
    unsafeDecrypt' c simp@(SomeCipherI imp) key bs = IB.unsafeCreate sbytes go
      where sz           = atMost (B.length bs) `asTypeOf` blocksOf 1 c
            BYTES sbytes = inBytes sz
            go    ptr    = allocBufferFor simp sz $ \ buf -> insecurely $ do
              initialise key
              liftIO $ unsafeNCopyToPointer sz bs buf -- Copy the input to buffer.
              decryptBlocks imp buf sz
              liftIO $ Raaz.Core.memcpy (destination (castPtr ptr)) (source buf) sz

    -- | Decrypt using the recommended implementation. This function is
    -- unsafe because it only works correctly when the input `ByteString`
    -- is of length which is a multiple of the block length of the cipher.
    unsafeDecrypt :: (Cipher c, Recommendation c)
                  => c            -- ^ The cipher
                  -> Key c        -- ^ The key to use
                  -> ByteString   -- ^ The string to encrypt
                  -> ByteString
    unsafeDecrypt c = unsafeDecrypt' c $ recommended c