module CryptoID.Cached
  ( encrypt, decrypt
  ) where

import Import.NoModel

import qualified Data.Binary as Binary

import qualified Data.CryptoID.Class.ImplicitNamespace as I


newtype CryptoIDDecryption ciphertext plaintext = CryptoIDDecryption plaintext
  deriving (Typeable)
newtype CryptoIDEncryption ciphertext plaintext = CryptoIDEncryption ciphertext
  deriving (Typeable)

encrypt :: forall plaintext ciphertext m.
           ( I.HasCryptoID ciphertext plaintext (HandlerFor (HandlerSite m))
           , Typeable plaintext, Typeable ciphertext
           , Binary plaintext, Binary ciphertext
           , MonadHandler m
           )
        => plaintext -> m (I.CryptoID ciphertext plaintext)
encrypt plain = liftHandler $ do
  (cachedEnc :: Maybe (CryptoIDEncryption ciphertext plaintext)) <- cacheByGet cacheKey
  case cachedEnc of
    Nothing -> do
      cID@(I.CryptoID crypt) <- I.encrypt plain
      cacheBySet cacheKey (CryptoIDEncryption crypt :: CryptoIDEncryption ciphertext plaintext)
      cacheBySet (toStrict $ Binary.encode crypt) (CryptoIDDecryption plain :: CryptoIDDecryption ciphertext plaintext)
      return cID
    Just (CryptoIDEncryption crypt) -> return $ I.CryptoID crypt
  where cacheKey = toStrict $ Binary.encode plain

decrypt :: forall plaintext ciphertext m.
           ( I.HasCryptoID ciphertext plaintext (HandlerFor (HandlerSite m))
           , Typeable plaintext, Typeable ciphertext
           , Binary plaintext, Binary ciphertext
           , MonadHandler m
           )
        => I.CryptoID ciphertext plaintext -> m plaintext
decrypt cID@(I.CryptoID crypt) = liftHandler $ do
  (cachedDec :: Maybe (CryptoIDDecryption ciphertext plaintext)) <- cacheByGet cacheKey
  case cachedDec of
    Nothing -> do
      plain <- I.decrypt cID
      cacheBySet (toStrict $ Binary.encode plain) (CryptoIDEncryption crypt :: CryptoIDEncryption ciphertext plaintext)
      cacheBySet cacheKey (CryptoIDDecryption plain :: CryptoIDDecryption ciphertext plaintext)
      return plain
    Just (CryptoIDDecryption plain) -> return plain
  where cacheKey = toStrict $ Binary.encode crypt
