module Crypto.Classes
	( Hash(..)
	, BlockCipher(..)
	, blockSizeBytes
	, StreamCipher(..)
	, AsymCipher(..)
	, Signing(..)
	, for
	, (.::.)
	, hash
	, hash'
	, hashFunc
	, hashFunc'
	) where
import Data.Serialize
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as B
import qualified Data.ByteString.Internal as I
import Data.List (foldl')
import Data.Tagged
import Crypto.Types
import Crypto.Random
class (Serialize d, Eq d, Ord d)
    => Hash ctx d | d -> ctx, ctx -> d where
  outputLength	:: Tagged d BitLength	      
  blockLength	:: Tagged d BitLength	      
  initialCtx	:: ctx			      
  updateCtx	:: ctx -> B.ByteString -> ctx 
                                              
  finalize	:: ctx -> B.ByteString -> d   
hash :: (Hash ctx d) => L.ByteString -> d
hash msg = res
  where
  res = finalize ctx end
  ctx = foldl' updateCtx initialCtx blks
  (blks,end) = makeBlocks msg blockLen
  blockLen = (blockLength .::. res) `div` 8
hash' :: (Hash ctx d) => B.ByteString -> d
hash' msg = res
  where
  res = finalize (updateCtx initialCtx top) end
  (top, end) = B.splitAt remlen msg
  remlen = B.length msg  (B.length msg `rem` bLen)
  bLen = blockLength `for` res `div` 8
hashFunc :: Hash c d => d -> (L.ByteString -> d)
hashFunc d = f
  where
  f = hash
  a = f undefined `asTypeOf` d
hashFunc' :: Hash c d => d -> (B.ByteString -> d)
hashFunc' d = f
  where
  f = hash'
  a = f undefined `asTypeOf` d
makeBlocks :: L.ByteString -> ByteLength -> ([B.ByteString], B.ByteString)
makeBlocks msg len = go (L.toChunks msg)
  where
  go [] = ([],B.empty)
  go (x:xs)
    | B.length x >= len =
	let l = B.length x  B.length x `rem` len
	    (top,end) = B.splitAt l x
	    (rest,trueEnd) = go (end:xs)
	in (top:rest, trueEnd)
    | otherwise =
	case xs of
		[] -> ([], x)
		(a:as) -> go (B.append x a : as)
for :: Tagged a b -> a -> b
for t _ = unTagged t
(.::.) :: Tagged a b -> a -> b
(.::.) = for
class ( Serialize k) => BlockCipher k where
  blockSize	:: Tagged k BitLength			
  encryptBlock	:: k -> B.ByteString -> B.ByteString	
  decryptBlock	:: k -> B.ByteString -> B.ByteString	
  buildKey	:: B.ByteString -> Maybe k		
  keyLength	:: k -> BitLength			
blockSizeBytes :: (BlockCipher k) => Tagged k ByteLength
blockSizeBytes = fmap (`div` 8) blockSize
class (Serialize p) => AsymCipher p where
  buildKeyPair :: CryptoRandomGen g => g -> BitLength -> Maybe ((p,p),g) 
  encryptAsym     :: p -> B.ByteString -> B.ByteString	
  decryptAsym     :: p -> B.ByteString -> B.ByteString  
  asymKeyLength   :: p -> BitLength
signUsing :: (Hash c d, AsymCipher p) => d -> p -> L.ByteString -> B.ByteString
signUsing d p = encryptAsym p . Data.Serialize.encode . hashFunc d
signUsing' :: (Hash c d, AsymCipher p) => d -> p -> B.ByteString -> B.ByteString
signUsing' d p = encryptAsym p . Data.Serialize.encode . hashFunc' d
class (Serialize k) => StreamCipher k iv | k -> iv where
  buildStreamKey	:: B.ByteString -> Maybe k
  encryptStream		:: k -> iv -> B.ByteString -> (B.ByteString, iv)
  decryptStream 	:: k -> iv -> B.ByteString -> (B.ByteString, iv)
  streamKeyLength	:: k -> BitLength
class (Serialize p, Serialize v) => Signing p v | p -> v, v -> p  where
  sign	 :: v -> L.ByteString -> B.ByteString
  verify :: p -> L.ByteString -> B.ByteString -> Bool
  buildSigningPair :: CryptoRandomGen g => g -> BitLength -> Maybe ((p, v), g)
  signingKeyLength :: v -> BitLength
  verifyingKeyLength :: p -> BitLength