{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Extremely simple JSON helper. Don't do anything too fancy with this!

module Distribution.Utils.Json
    ( Json(..)
    , (.=)
    , renderJson
    ) where

import Distribution.Compat.Prelude
import qualified Data.ByteString.Lazy as LBS
import Data.ByteString.Builder
  ( Builder, stringUtf8, intDec, toLazyByteString )

data Json = JsonArray [Json]
          | JsonBool !Bool
          | JsonNull
          | JsonNumber !Int -- No support for Floats, Doubles just yet
          | JsonObject [(String, Json)]
          | JsonString !String
  deriving Show

-- | Convert a 'Json' into a 'ByteString'
renderJson :: Json -> LBS.ByteString
renderJson json = toLazyByteString (go json)
  where
    go (JsonArray objs)   =
      surround "[" "]" $ mconcat $ intersperse "," $ map go objs
    go (JsonBool True)    = stringUtf8 "true"
    go (JsonBool False)   = stringUtf8 "false"
    go  JsonNull          = stringUtf8 "null"
    go (JsonNumber n)     = intDec n
    go (JsonObject attrs) =
      surround "{" "}" $ mconcat $ intersperse "," $ map render attrs
      where
        render (k,v) = (surround "\"" "\"" $ stringUtf8 (escape k)) <> ":" <> go v
    go (JsonString s)     = surround "\"" "\"" $ stringUtf8 (escape s)

surround :: Builder -> Builder -> Builder -> Builder
surround begin end middle = mconcat [ begin , middle , end]

escape :: String -> String
escape ('\"':xs) = "\\\"" <> escape xs
escape ('\\':xs) = "\\\\" <> escape xs
escape ('\b':xs) = "\\b"  <> escape xs
escape ('\f':xs) = "\\f"  <> escape xs
escape ('\n':xs) = "\\n"  <> escape xs
escape ('\r':xs) = "\\r"  <> escape xs
escape ('\t':xs) = "\\t"  <> escape xs
escape (x:xs)    = x : escape xs
escape []        = mempty

-- | A shorthand for building up 'JsonObject's
-- >>> JsonObject [ "a" .= JsonNumber 42, "b" .= JsonBool True ]
-- JsonObject [("a",JsonNumber 42),("b",JsonBool True)]
(.=) :: String -> Json -> (String, Json)
k .= v = (k, v)