-----------------------------------------------------------------------------
-- |
-- Module      :  Text.JSON.Fields
-- Copyright   :  (c) Scrive 2011
-- License     :  BSD-style (see the LICENSE file in the distribution)
--
-- Maintainer  :  mariusz@scrive.com
-- Stability   :  development
-- Portability :  portable
--
-- Abusing monadic do notation library for generating JSON object. 
-- Hard-binded to json package from hackage.
-- Main ideas
--
-- * Overloaded function 'field', that may set values of fields of different types - 'Bool', 'Int', 'String', lists  etc.
--
-- * Internal IO - value of the field can be IO a, is we know how to put a into JSON. That means that there is no need to do prebinding 
--
-- * Compositionality - value of field can also be fields. Easy to do embeded  objects
--
-- * Monadic notation - it really looks nicer then composition with '.' or some magic combinator
--
-- >
-- > json $ do
-- >     field "a" "a"
-- >     field "b" [1,2,3]
-- >     field "c" $ do
-- >         field "x" True
-- >         field "y" False
-- >
-- 
-- Will generate json object 
--  {a : "a", b: [1,2,3], c: {x: true, y : false}} 
--

module Text.JSON.Fields (json, JSField(..))where

import Text.JSON
import Text.JSON.ToJSON
import Data.Map as Map
import Control.Monad.State.Strict


type JSFields = State ([(String, IO JSValue)]) ()


{- | Function for changing 'JSFields' into real JSON object -}

json :: JSFields -> IO JSValue
json fields = fmap (JSObject . toJSObject) $ sequence $ fmap pack $ execState fields []

{- | The 'JSField' class instances are object that in some sence can be interpreted as value of JSON object fields. 
     To derive new instances use existing instances since internal structure 'JSFields' is hidden.
-}

class JSField a where
  field :: String -> a -> JSFields
  
instance (ToJSON a) => JSField a where  
  field n v = modify $ \s -> (n, return $ toJSON v) : s

instance (ToJSON a) => JSField (IO a) where  
  field n v = modify $ \s -> (n, fmap toJSON v) : s

instance JSField (JSFields) where  
  field n v = modify $ \s -> (n, fmap toJSON val) : s
   where
      val = fmap Map.fromList (sequence $ fmap pack $ execState v [])
  
instance JSField [JSFields] where  
  field n fs = modify $ \s -> (n, fmap toJSON $ mapM vals fs) : s
      where
        vals f = fmap Map.fromList  (sequence $ fmap pack $ execState f [])

pack :: (Functor f) => (a, f b) -> f (a, b)
pack (name, comp)=  fmap (\res -> (name,res)) comp