-- |
-- Module      : Crypto.Multihash.Internal.Types
-- License     : BSD3
-- Maintainer  : Marcello Seri <marcello.seri@gmail.com>
-- Stability   : experimental
-- Portability : unknown
--
{-# LANGUAGE FlexibleContexts #-}
module Crypto.Multihash.Internal.Types where

import Crypto.Hash.Algorithms
import Data.ByteString (ByteString)
import Data.String (IsString(..))
import Data.String.Conversions

-- | 'Base' usable to encode the digest 
data Base = Base2   -- ^ Binary form
          | Base16  -- ^ Hex encoding
          | Base32  -- ^ Avoid. Wait for <https://github.com/jbenet/multihash/issues/31 this issue to resolve>
          | Base58  -- ^ Bitcoin base58 encoding
          | Base64  -- ^ Base64 encoding
          deriving (Eq, Show)

-- | 'Codable' hash algorithms are the algorithms supported for multihashing
class Codable a where
  -- | Return the first byte for the head of the multihash digest
  toCode :: a -> Int

instance Codable SHA1 where
  toCode SHA1 = 0x11
instance Codable SHA256 where
  toCode SHA256 = 0x12
instance Codable SHA512 where
  toCode SHA512 = 0x13
instance Codable SHA3_512 where
  toCode SHA3_512 = 0x14
instance Codable SHA3_384 where
  toCode SHA3_384 = 0x15
instance Codable SHA3_256 where
  toCode SHA3_256 = 0x16
instance Codable SHA3_224 where
  toCode SHA3_224 = 0x17
instance Codable Blake2b_512 where
  toCode Blake2b_512 = 0x40
instance Codable Blake2s_256 where
  toCode Blake2s_256 = 0x41

-- TODO: add shake-128/256 to Codable. Probably
-- fromCode 0x18 = Keccak_256
-- fromCode 0x19 = Keccak_512

class Encodable a where
  -- | Safe encoder for 'Encodable'.
  encode :: IsString s => Base -> a -> Either String s
  -- | Unsafe encoder for 'Encodable'.
  --   Throws an error if there are encoding issues.
  encode' :: IsString s => Base -> a -> s
  encode' base digest = fromString <$> eitherToErr $ encode base digest

  -- | Safely check the correctness of an encoded 'Encodable' against a plain
  --   'Encodable'.
  check :: (IsString s, ConvertibleStrings s ByteString) => s -> a -> Either String Bool
  -- | Unsafe version of 'check'. Throws on encoding/decoding errors instead of returning an Either type.
  check' :: (IsString s, ConvertibleStrings s ByteString) => s -> a -> Bool
  check' encoded digest = eitherToErr $ check encoded digest

class Checkable b where
  -- | Safely check the correctness of an encoded 'Encodable' against the 
  --   corresponding data.
  checkPayload :: (IsString s, ConvertibleStrings s ByteString) => s -> b -> Either String Bool
  -- | Unsafe version of 'checkPayload'. 
  --   Throws on encoding/decoding errors instead of returning an 'Either' type.
  checkPayload' :: (IsString s, ConvertibleStrings s ByteString) => s -> b -> Bool
  checkPayload' encoded payload = eitherToErr $ checkPayload encoded payload

eitherToErr :: Either String b -> b
eitherToErr v = case v of
  Right val -> val
  Left  err -> error err