{-# LANGUAGE MagicHash #-}
{-# LANGUAGE BangPatterns #-}
module Crypto.Cipher.Twofish.Primitive
    ( Twofish
    , initTwofish
    , encrypt
    , decrypt
    ) where

import           Crypto.Error
import           Crypto.Internal.ByteArray (ByteArrayAccess, ByteArray, Bytes)
import qualified Crypto.Internal.ByteArray as B
import           Crypto.Internal.WordArray
import           Crypto.Internal.Words
import           Data.Word
import           Data.Int
import           Data.Bits
import           Data.List
import           Control.Monad

-- Based on the Golang referance implementation
-- https://github.com/golang/crypto/blob/master/twofish/twofish.go


-- BlockSize is the constant block size of Twofish.
blockSize :: Int
blockSize = 16

mdsPolynomial, rsPolynomial :: Word32
mdsPolynomial = 0x169 -- x^8 + x^6 + x^5 + x^3 + 1, see [TWOFISH] 4.2
rsPolynomial = 0x14d  -- x^8 + x^6 + x^3 + x^2 + 1, see [TWOFISH] 4.3

data Twofish = Twofish { s :: (Array32, Array32, Array32, Array32)
                       , k :: Array32 }

data ByteSize = Bytes16 | Bytes24 | Bytes32 deriving (Eq)

data KeyPackage ba = KeyPackage { rawKeyBytes :: ba
                                , byteSize :: ByteSize }

buildPackage :: ByteArray ba => ba -> Maybe (KeyPackage ba)
buildPackage key
    | B.length key == 16 = return $ KeyPackage key Bytes16
    | B.length key == 24 = return $ KeyPackage key Bytes24
    | B.length key == 32 = return $ KeyPackage key Bytes32
    | otherwise = Nothing

-- | Initialize a 128-bit, 192-bit, or 256-bit key
--
-- Return the initialized key or a error message if the given
-- keyseed was not 16-bytes in length.
initTwofish :: ByteArray key
            => key -- ^ The key to create the twofish context
            -> CryptoFailable Twofish
initTwofish key =
    case buildPackage key of Nothing -> CryptoFailed CryptoError_KeySizeInvalid
                             Just keyPackage -> CryptoPassed Twofish { k = generatedK, s = generatedS }
                                  where generatedK = array32 40 $ genK