initState :: HashAlgorithm -> HashState
initState MD5 = fromCtx MD5.initialCtx DL.empty targetBlockSizeBytes
  where fromCtx :: MD5.MD5Context -> DL.DList ByteString -> Int -> HashState
        fromCtx ctx leftovers remaining =
          ctx `seq` HashState { update = abstractUpdate ctx leftovers remaining
                              , digest = abstractFinalize ctx leftovers
                              }
        abstractUpdate ctx leftovers remaining new =
          let leftovers' = DL.snoc leftovers new
              remaining' = remaining - BS.length new
          in if remaining' <= 0
             then let (chunks, lastChunk) = chunk targetBlockSizeBytes $ leftoversBytes leftovers'
                      ctx' = MD5.updateCtx ctx chunks
                  in if BS.null lastChunk
                     then fromCtx ctx' DL.empty targetBlockSizeBytes
                     else fromCtx ctx' (DL.singleton lastChunk) (targetBlockSizeBytes - BS.length lastChunk)
             else fromCtx ctx leftovers' remaining'
        abstractFinalize ctx leftovers = encode $ MD5.finalize ctx (leftoversBytes leftovers)
        leftoversBytes = BS.concat . DL.toList
        chunk target bs =
          let splitPoint = BS.length bs - (BS.length bs `rem` target)
          in BS.splitAt splitPoint bs
        targetBlockSizeBytes = untag (MD5.blockLength :: Tagged MD5.MD5Digest BitLength) `div` 8
initState :: HashAlgorithm -> HashState
initState MD5 = fromCtx MD5.initialCtx DL.empty targetBlockSizeBytes
  where fromCtx :: MD5.MD5Context -> DL.DList ByteString -> Int -> HashState
        fromCtx ctx leftovers remaining =
          ctx `seq` HashState { update = abstractUpdate ctx leftovers remaining
                              , digest = abstractFinalize ctx leftovers
                              }
        abstractUpdate ctx leftovers remaining new =
          let leftovers' = DL.snoc leftovers new
              remaining' = remaining - BS.length new
          in if remaining' <= 0
             then let (chunks, lastChunk) = chunk targetBlockSizeBytes $ leftoversBytes leftovers'
                      ctx' = MD5.updateCtx ctx chunks
                  in if BS.null lastChunk
                     then fromCtx ctx' DL.empty targetBlockSizeBytes
                     else fromCtx ctx' (DL.singleton lastChunk) (targetBlockSizeBytes - BS.length lastChunk)
             else fromCtx ctx leftovers' remaining'
        abstractFinalize ctx leftovers = encode $ MD5.finalize ctx (leftoversBytes leftovers)
        leftoversBytes = BS.concat . DL.toList
        chunk target bs =
          let splitPoint = BS.length bs - (BS.length bs `rem` target)
          in BS.splitAt splitPoint bs
        targetBlockSizeBytes = untag (MD5.blockLength :: Tagged MD5.MD5Digest BitLength) `div` 8
initState MD5 = fromCtx MD5.initialCtx DL.empty targetBlockSizeBytes
  where fromCtx :: MD5.MD5Context -> DL.DList ByteString -> Int -> HashState
        fromCtx ctx leftovers remaining =
          ctx `seq` HashState { update = abstractUpdate ctx leftovers remaining
                              , digest = abstractFinalize ctx leftovers
                              }
        abstractUpdate ctx leftovers remaining new =
          let leftovers' = DL.snoc leftovers new
              remaining' = remaining - BS.length new
          in if remaining' <= 0
             then let (chunks, lastChunk) = chunk targetBlockSizeBytes $ leftoversBytes leftovers'
                      ctx' = MD5.updateCtx ctx chunks
                  in if BS.null lastChunk
                     then fromCtx ctx' DL.empty targetBlockSizeBytes
                     else fromCtx ctx' (DL.singleton lastChunk) (targetBlockSizeBytes - BS.length lastChunk)
             else fromCtx ctx leftovers' remaining'
        abstractFinalize ctx leftovers = encode $ MD5.finalize ctx (leftoversBytes leftovers)
        leftoversBytes = BS.concat . DL.toList
        chunk target bs =
          let splitPoint = BS.length bs - (BS.length bs `rem` target)
          in BS.splitAt splitPoint bs
        targetBlockSizeBytes = untag (MD5.blockLength :: Tagged MD5.MD5Digest BitLength) `div` 8