content: -- |
  -- Module      : Data.Git.Ref
  -- License     : BSD-style
  -- Maintainer  : Vincent Hanquez <vincent@snarc.org>
  -- Stability   : experimental
  -- Portability : unix
  --
  {-# LANGUAGE DeriveDataTypeable #-}
  module Data.Git.Ref
      ( Ref
      , SHA1
      , Crypto.Hash.HashAlgorithm
      , Crypto.Hash.hashDigestSize
      -- * Exceptions
      , RefInvalid(..)
      , RefNotFound(..)
      -- * convert from bytestring and string
      , isHex
      , isHexString
      , fromHex
      , fromHexString
      , fromBinary
      , fromDigest
      , toBinary
      , toHex
      , toHexString
      -- * Misc function related to ref
      , refPrefix
      , cmpPrefix
      , toFilePathParts
      -- * Hash ByteString types to a ref
      , hash
      , hashLBS
      , hashAlg
      , hashAlgFromRef
      ) where
  
  import qualified Crypto.Hash
  import           Crypto.Hash (Digest, SHA1, digestFromByteString)
  import           Data.ByteString (ByteString)
  import qualified Data.ByteString.Unsafe as B (unsafeIndex)
  import qualified Data.ByteString.Lazy as L
  import qualified Data.ByteString.Char8 as BC
  import           Data.ByteArray.Encoding
  import qualified Data.ByteArray as B (convert)
  
  import Data.Char (isHexDigit)
  import Data.Data
  
  import Control.Exception (Exception, throw)
  
  -- | represent a git reference (SHA1)
  newtype Ref hash = Ref (Digest hash)
      deriving (Eq,Ord,Typeable)
  
  instance Show (Ref hash) where
      show = BC.unpack . toHex
  
  -- | Invalid Reference exception raised when
  -- using something that is not a ref as a ref.
  data RefInvalid = RefInvalid ByteString
      deriving (Show,Eq,Data,Typeable)
  
  -- | Reference wasn't found
  data RefNotFound hash = RefNotFound (Ref hash)
      deriving (Show,Eq,Typeable)
  
  instance Exception RefInvalid
  instance Typeable hash => Exception (RefNotFound hash)
  
  isHex :: ByteString -> Bool
  isHex = and . map isHexDigit . BC.unpack
  
  isHexString :: String -> Bool
  isHexString = and . map isHexDigit
  
  -- | take a hexadecimal bytestring that represent a reference
  -- and turn into a ref
  fromHex :: Crypto.Hash.HashAlgorithm hash => ByteString -> Ref hash
  fromHex s =
      case either (const Nothing) Just (convertFromBase Base16 s :: Either String ByteString) >>= digestFromByteString of
          Nothing  -> throw $ RefInvalid s
          Just hsh -> Ref hsh
  
  -- | take a hexadecimal string that represent a reference
  -- and turn into a ref
  fromHexString :: Crypto.Hash.HashAlgorithm hash => String -> Ref hash
  fromHexString = fromHex . BC.pack
  
  -- | transform a ref into an hexadecimal bytestring
  toHex :: Ref hash -> ByteString
  toHex (Ref bs) = convertToBase Base16 bs
  
  -- | transform a ref into an hexadecimal string
  toHexString :: Ref hash -> String
  toHexString (Ref d) = show d
  
  -- | transform a bytestring that represent a binary bytestring
  -- and returns a ref.
  fromBinary :: Crypto.Hash.HashAlgorithm hash => ByteString -> Ref hash
  fromBinary b = maybe (throw $ RefInvalid b) Ref $ digestFromByteString b
  
  -- | transform a bytestring that represent a binary bytestring
  -- and returns a ref.
  fromDigest :: Crypto.Hash.HashAlgorithm hash => Digest hash -> Ref hash
  fromDigest = Ref
  
  -- | turn a reference into a binary bytestring
  toBinary :: Ref hash -> ByteString
  toBinary (Ref b) = B.convert b
  
  -- | returns the prefix (leading byte) of this reference
  refPrefix :: Ref hash -> Int
  refPrefix (Ref b) = fromIntegral $ B.unsafeIndex (B.convert b) 0
  
  -- | compare prefix
  cmpPrefix :: String -> Ref hash -> Ordering
  cmpPrefix pre ref = pre `compare` (take (length pre) $ toHexString ref)
  
  -- | returns the splitted format "prefix/suffix" for addressing the loose object database
  toFilePathParts :: Ref hash -> (String, String)
  toFilePathParts ref = splitAt 2 $ show ref
  
  -- | hash a bytestring into a reference
  hash :: Crypto.Hash.HashAlgorithm hash => ByteString -> Ref hash
  hash = Ref . Crypto.Hash.hash
  
  -- | hash a lazy bytestring into a reference
  hashLBS :: Crypto.Hash.HashAlgorithm hash => L.ByteString -> Ref hash
  hashLBS = Ref . Crypto.Hash.hashlazy
  
  -- | Any hash algorithm
  hashAlg :: Crypto.Hash.HashAlgorithm hash => hash
  hashAlg = error "polymorphic hash algorithm. only to use with hashDigestSize"
  
  hashAlgFromRef :: Crypto.Hash.HashAlgorithm hash => Ref hash -> hash
  hashAlgFromRef _ = hashAlg
  