-- | Base64 encoding/decoding.

module Serokell.Util.Base64
       ( encode
       , decode
       , encodeUrl
       , decodeUrl
       , formatBase64
       , base64F
       , JsonByteString (..)
       ) where

import Universum hiding (fail)

import Control.Monad (fail)
import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON))
import Data.Aeson.Types (FromJSONKey (..), FromJSONKeyFunction (FromJSONKeyTextParser),
                         ToJSONKey (..), toJSONKeyText)
import Data.Text.Lazy.Builder (Builder, fromText)
import Formatting (Format, later)

import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Base64.URL as B64url
import qualified Fmt as Fmt
-- | Apply base64 encoding to strict ByteString.
encode :: ByteString -> Text
encode = Fmt.fmt . Fmt.base64F

-- | Decode base64-encoded ByteString.
decode :: Text -> Either Text ByteString
decode = first toText . B64.decode . encodeUtf8

-- | Apply base64url encoding to strict ByteString.
encodeUrl :: ByteString -> Text
encodeUrl = Fmt.fmt . Fmt.base64UrlF

-- | Decode base64url-encoded ByteString.
decodeUrl :: Text -> Either Text ByteString
decodeUrl = first toText . B64url.decode . encodeUtf8

-- | Construct Builder from bytestring formatting it in Base64.
formatBase64 :: ByteString -> Builder
formatBase64 = fromText . encode

-- | Format which uses Base64 to print bytestring.
base64F :: Format r (ByteString -> r)
base64F = later formatBase64

----------------------------------------------------------------------------
-- Aeson helpers
----------------------------------------------------------------------------

-- | Wrapper on top of ByteString with JSON serialization (in base64
-- encoding).
newtype JsonByteString = JsonByteString
    { getJsonByteString :: ByteString
    } deriving (Show, Eq, Ord, Hashable)

instance ToJSON JsonByteString where
    toJSON = toJSON . encode . getJsonByteString

instance ToJSONKey JsonByteString where
    toJSONKey = toJSONKeyText (encode . getJsonByteString)

instance FromJSON JsonByteString where
    parseJSON = parseJSON >=> jsonBSParser

instance FromJSONKey JsonByteString where
    fromJSONKey = FromJSONKeyTextParser jsonBSParser

jsonBSParser :: MonadFail m => Text -> m JsonByteString
jsonBSParser = either (fail . toString) (pure . JsonByteString) . decode