{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings          #-}
module Data.ByteArray.HexString where
import           Data.Aeson              (FromJSON (..), ToJSON (..),
                                          Value (..), withText)
import           Data.ByteArray          (ByteArray, ByteArrayAccess, convert)
import qualified Data.ByteArray          as BA (drop, take)
import           Data.ByteArray.Encoding (Base (Base16), convertFromBase,
                                          convertToBase)
import           Data.ByteString         (ByteString)
import           Data.Monoid             (Monoid, (<>))
import           Data.Semigroup          (Semigroup)
import           Data.String             (IsString (..))
import           Data.Text               (Text)
import           Data.Text.Encoding      (decodeUtf8, encodeUtf8)
import           GHC.Generics            (Generic)
newtype HexString = HexString { unHexString :: ByteString }
  deriving (Eq, Ord, Generic, Semigroup, Monoid, ByteArrayAccess, ByteArray)
instance Show HexString where
    show = ("HexString " ++) . show . format
instance IsString HexString where
    fromString = hexString' . fromString
      where
        hexString' :: ByteString -> HexString
        hexString' = either error id . hexString
instance FromJSON HexString where
    parseJSON Null = pure (fromBytes ("" :: ByteString))
    parseJSON v = withText "HexString" (either fail pure . hexString . encodeUtf8) v
instance ToJSON HexString where
    toJSON = String . toText
hexString :: ByteArray ba => ba -> Either String HexString
hexString bs = HexString <$> convertFromBase Base16 bs'
  where
    hexStart = convert ("0x" :: ByteString)
    bs' | BA.take 2 bs == hexStart = BA.drop 2 bs
        | otherwise = bs
fromBytes :: ByteArrayAccess ba => ba -> HexString
fromBytes = HexString . convert
toBytes :: ByteArray ba => HexString -> ba
toBytes = convert . unHexString
toText :: HexString -> Text
toText = decodeUtf8 . convertToBase Base16 . unHexString
format :: HexString -> Text
format a = "0x" <> toText a