{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleInstances #-}
-- {-# LANGUAGE OverloadedStrings #-}
module Servant.EDE.Internal.ToObject where

import Data.Aeson
import Data.HashMap.Strict
import Data.Monoid
import Data.Text
import GHC.Generics

-- | Turn haskell values into JSON objects.
--
-- This is the mechanism used by EDE to marshall data from Haskell
-- to the templates. The rendering is then just about feeding the
-- resulting 'Object' to a compiled 'Template'. Example:
--
-- > import Text.EDE
-- >
-- > data User = User { name :: String, age :: Int }
-- >
-- > instance ToObject User where
-- >   toObject user =
-- >     fromPairs [ "name" .= name user
-- >               , "age"  .= age user
-- >               ]
--
-- However, you're not forced to write the instance yourself for such a type.
-- Indeed, for any record type (i.e a datatype with a single constructor and
-- with field selectors) you can let @GHC.Generics@ derive the 'ToObject' instance
-- for you.
--
-- > data User = User { name :: String, age :: Int } deriving Generic
-- > instance ToObject User
--
-- This will generate an equivalent instance to the previous one.
class ToObject a where

  -- | Turn values of type @a@ into JSON 'Object's.
  --
  -- @ 
  -- -- Reminder:
  -- type Object = 'HashMap' 'Text' 'Value'
  -- @
  toObject :: a -> Object
  
  default toObject :: (Generic a, GToObject (Rep a)) => a -> Object
  toObject = genericToObject

instance ToObject (HashMap Text Value) where
  toObject = id

class GToObject f where
  gtoObject :: f a -> Object

instance GToObject V1 where
  gtoObject _ = mempty

instance GToObject U1 where
  gtoObject U1 = mempty

instance (GToObject f, GToObject g)
      => GToObject (f :*: g) where
  gtoObject (f :*: g) = gtoObject f <> gtoObject g

instance GToObject a => GToObject (M1 D d a) where
  gtoObject (M1 x) = gtoObject x

instance GToObject a => GToObject (M1 C c a) where
  gtoObject (M1 x) = gtoObject x

instance (Selector s, ToJSON a) => GToObject (M1 S s (K1 r a)) where
  gtoObject s@(M1 (K1 x)) = fromList [(fieldname, value)]
    where fieldname = pack (selName s)
          value     = toJSON x

genericToObject :: (Generic a, GToObject (Rep a)) => a -> Object
genericToObject = gtoObject . from