module Data.Blockchain.Types.Hex
    ( Hex256(..)
    , hex256
    , hex256LeadingZeros
    ) where

import           Control.Monad   (unless)
import qualified Data.Aeson      as Aeson
import qualified Data.Maybe      as Maybe
import           Data.Monoid     ((<>))
import qualified Data.Text       as Text
import qualified Data.Word       as Word
import qualified Numeric
import qualified Numeric.Natural as Natural

-- Types -----------------------------------------------------------------------------------------------------

newtype Hex256 = Hex256 { unHex256 :: Natural.Natural }
  deriving (Enum, Eq, Integral, Num, Real, Ord)

instance Show Hex256 where
    show = zeroPadded 64 . showHex . unHex256

instance Bounded Hex256 where
    minBound = 0
    maxBound = Maybe.fromMaybe (error "Unexpected parse failure") $ readHexMaybe $ replicate 64 'f'

instance Aeson.ToJSON Hex256 where
    toJSON = Aeson.String . Text.pack . show

instance Aeson.FromJSON Hex256 where
    parseJSON = Aeson.withText "Hex256" parseHex256
      where
        parseHex256 = maybe (fail "Invalid hex 256 string") return . hex256 . Text.unpack

-- Construction helpers --------------------------------------------------------------------------------------

-- | Create a Hex256 value with the specificed amount of leading zeros.
-- Useful for creating a 'Data.Blockchain.Types.BlockchainConfig.difficulty1Target' when creating a blockchain.
--
-- >>> hex256LeadingZeros 4
-- 0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
--
hex256LeadingZeros :: Word.Word -> Hex256
hex256LeadingZeros n = maxBound `div` Hex256 (16 ^ n)

hex256 :: String -> Maybe Hex256
hex256 str = do
    unless (length str == 64) Nothing
    x <- readHexMaybe str
    return (Hex256 x)

readHexMaybe :: (Num a, Eq a) => String -> Maybe a
readHexMaybe str = case Numeric.readHex str of
    [(x, "")] -> Just x
    _         -> Nothing

-- Utils -----------------------------------------------------------------------------------------------------

showHex :: (Show a, Integral a) => a -> String
showHex x = Numeric.showHex x mempty

zeroPadded :: Int -> String -> String
zeroPadded x str = replicate (x - length str) '0' <> str