{-# LANGUAGE OverloadedStrings #-}
{-|
Efficient build ByteString from `Json` with escaped string.

/For example - use in YESOD:/

@
    import Yesod
    import Data.JSON2       as JSON
    import Data.JSON2.Blaze as JSON
    import Blaze.ByteString.Builder (toLazyByteString)
    --
    toRepJson :: ToJson a => a -> RepJson
    toRepJson =  RepJson . toContent . toLazyByteString . (JSON.blazeJson) . (JSON.toJson)
@

-}

module Data.JSON2.Blaze (blazeJson) where
import Data.JSON2.Types
import Data.ByteString.Char8 ()
import Blaze.ByteString.Builder
import Blaze.ByteString.Builder.Internal (getPoke, boundedWrite)
import Blaze.ByteString.Builder.Char.Utf8 (writeChar, fromChar, fromString, fromShow)
import Data.Ratio (denominator, numerator)
import Data.Monoid (mempty, mappend, mconcat)
import Data.Map as Map

blazeJson :: Json -> Builder
blazeJson JNull           = fromByteString "null"
blazeJson (JBool True)    = fromByteString "true"
blazeJson (JBool False)   = fromByteString "false"
blazeJson (JString xs)    = escQuoted xs
blazeJson (JNumber x)
    | denominator x == 1  = fromShow (numerator x)
    | otherwise           = fromShow (fromRational x :: Double)
blazeJson (JArray [])     = fromByteString "[]"
blazeJson (JArray (x:xs)) = mconcat [fromChar '[', blazeJson x, go xs, fromChar ']' ]
    where go xs = mconcat [fromChar ',' `mappend` blazeJson x | x <- xs]
blazeJson (JObject x)   = goo (Map.toList x)
    where goo []     = fromByteString "{}"
          goo (p:ps) = mconcat [fromChar '{', buildPair p, goo' ps, fromChar '}' ]
          goo' vs = mconcat [fromChar ',' `mappend` buildPair w | w <- vs]
          buildPair (k,v) = mconcat[escQuoted k, fromChar ':', blazeJson v]

escQuoted :: String -> Builder
escQuoted xs = mconcat [fromChar '\"', (fromWriteList writeJsonEscChar xs), fromChar '\"']

writeJsonEscChar :: Char -> Write
writeJsonEscChar x = 
    boundedWrite 2 (wc x)  where
        wc '\"'  = getPoke $ writeByteString "\\\""
        wc '\\'  = getPoke $ writeByteString "\\\\"
        wc '/'   = getPoke $ writeByteString "\\/"
        wc '\b'  = getPoke $ writeByteString "\\b"
        wc '\f'  = getPoke $ writeByteString "\\f"
        wc '\n'  = getPoke $ writeByteString "\\n"
        wc '\r'  = getPoke $ writeByteString "\\r"
        wc '\t'  = getPoke $ writeByteString "\\t"
        wc c     = getPoke $ writeChar c