{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}

-- | Extremely simple JSON helper. Don't do anything too fancy with this!
module Distribution.Utils.Json
  ( Json (..)
  , (.=)
  , renderJson
  ) where

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

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)
