{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE PolyKinds           #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Generics.MRSOP.HDiff.Digest where
import Data.Proxy
import Data.Functor.Const
import Data.Void
import Data.Word (Word8,Word64)
import Data.Bits
import Data.List (splitAt,foldl')
import qualified Data.ByteString        as BS
import qualified Data.ByteString.Char8  as BS8
import qualified Data.ByteArray         as BA
import qualified Data.ByteArray.Mapping as BA
import qualified Crypto.Hash            as Hash
import qualified Crypto.Hash.Algorithms as Hash (Blake2s_256)
import Generics.MRSOP.Base
newtype Digest
  = Digest { getDigest :: Hash.Digest Hash.Blake2s_256 }
  deriving (Eq , Show)
toW64s :: Digest -> [Word64]
toW64s = map combine . chunksOf 8 . BA.unpack . getDigest
  where
    chunksOf n l
      | length l <= n = [l]
      | otherwise     = let (h , t) = splitAt n l
                         in h : chunksOf n t
    
    combine :: [Word8] -> Word64
    combine = foldl' (\acu (n , next)
                       -> shiftL (fromIntegral next) (8*n) .|. acu) 0
            . zip [0,8,16,24,32,40,48,56]
snat2W64 :: SNat n -> Word64
snat2W64 SZ     = 0
snat2W64 (SS c) = 1 + snat2W64 c
hash :: BS.ByteString -> Digest
hash = Digest . Hash.hash
hashStr :: String -> Digest
hashStr = hash . BS8.pack
digestConcat :: [Digest] -> Digest
digestConcat = hash . BA.concat . map getDigest
class Digestible (v :: *) where
  digest :: v -> Digest
instance Digestible Word64 where
  digest = hash . BA.fromW64BE
class DigestibleHO (f :: k -> *) where
  digestHO :: forall ki . f ki -> Digest
instance DigestibleHO (Const Void) where
  digestHO (Const _impossible) = error "DigestibleHO (Const Void)"
authPeel' :: forall sum ann i
           . (forall ix . ann ix -> Digest)
          -> Word64
          -> Constr sum i
          -> NP ann (Lkup i sum)
          -> Digest
authPeel' proj salt cnstr p
  = digestConcat $ ([digest (constr2W64 cnstr) , digest salt] ++)
                 $ elimNP proj p
  where
    
    
    
    constr2W64 :: Constr sum' n -> Word64
    constr2W64 CZ     = 0
    constr2W64 (CS c) = 1 + constr2W64 c
authPeel :: forall codes ix ann i
          . IsNat ix
         => (forall iy . ann iy -> Digest)
         -> Proxy codes
         -> Proxy ix
         -> Constr (Lkup ix codes) i
         -> NP ann (Lkup i (Lkup ix codes))
         -> Digest
authPeel proj _ pix = authPeel' proj (snat2W64 $ getSNat pix)