{-| Extend vformat to 'Aeson'

The instance will try the following conditions one by one
when formatting 'Value':

(1) the arg is a 'Number', use 'formatInteger' or 'formatRealFloat' to format
its value.

(2) the arg is a 'String', use 'formatString' to format its value.

(3) the key is an empty key (i.e. 'mempty'), encode the whole 'Value' into
string, then use 'formatString' to format the string.

(4) the arg is 'Array' and the topmost key is 'Index', get the element by the
index, then format the element.

(5) the arg is 'Object' and the topmost key is 'Name', get the value by the
name, then format the value.

(6) raise an 'ArgKeyError'.


If you have a 'ToJSON' datatype, you can extend vformat to it directly,
or just convert it into 'Value' (use 'toJSON') and then format the 'Value'.

=== Example
>>> :set -XDeriveGeneric
>>> import GHC.Generics
>>> data Color = Red | Yellow | Blue deriving Generic
>>> instance ToJSON Color
>>> data Flag = Flag Color Int Int deriving Generic
>>> instance ToJSON Flag
>>> data Country = Country { name :: String, flag :: Flag } deriving Generic
>>> instance ToJSON Country
>>> let country = toJSON $ Country "X" $ Flag Blue 100 50
>>> format1 "{name} {flag!0} {flag!1} {flag!2}" country
"X Blue 100 50"
>>> format "{}" country
"{\"flag\":[\"Blue\",100,50],\"name\":\"X\"}"
-}
module Text.Format.Aeson ( ) where


import           Control.Exception
import           Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as B
import qualified Data.HashMap.Strict        as M
import           Data.Scientific            hiding (formatScientific)
import qualified Data.Text                  as T
import           Data.Vector
import           Text.Format


instance FormatArg Value where
  formatArg (Number x) k   = formatScientific (floatingOrInteger x) k
  formatArg (String x) k   = formatString (T.unpack x) k
  formatArg x k            | k == mempty = formatJSON x k
  formatArg (Array xs) k   = formatArray (topKey k) xs (popKey k)
  formatArg (Object obj) k = formatObject (topKey k) obj (popKey k)
  formatArg _ _            = const $ Left $ toException ArgKeyError


formatJSON :: ToJSON a => a -> Formatter
formatJSON = formatString . B.unpack . encode


formatScientific :: (RealFloat r, Integral i) => Either r i -> Formatter
formatScientific (Left x)  = formatRealFloat x
formatScientific (Right x) = formatInteger $ toInteger x


formatArray :: ArgKey -> Array -> Formatter
formatArray (Index i) xs = case (xs !? i) of (Just x) -> formatArg x

formatObject :: ArgKey -> Object -> Formatter
formatObject (Name k) obj =
  case (M.lookup (T.pack k) obj) of (Just x) -> formatArg x