{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}
-- | 'derivePersistFieldPB' uses Template Haskell to produce
--   'Database.Persist.PersistField' instances for types with
--   'Text.ProtocolBuffers.Reflections.ReflectDescriptor' and
--   'Text.ProtocolBuffers.WireMessage.Wire' instances just as
--   'Database.Persist.TH.derivePersistField' produces
--   'Database.Persist.PersistField' instances for types with 'Read' and 'Show'
--   instances.
module Database.Persist.TH.ProtocolBuffers (
    derivePersistFieldPB
  ) where
import Database.Persist.Store
import Language.Haskell.TH.Syntax
import Text.ProtocolBuffers.WireMessage (messageGet, messagePut)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as T

strictify :: BSL.ByteString -> BS.ByteString
strictify x = BS.concat $ BSL.toChunks x

lazify :: BS.ByteString -> BSL.ByteString
lazify x = BSL.fromChunks [x]

-- | Derive 'Database.Persist.PersistField' instances for 'typName'. 'typName'
--   should be an instance of the
--   'Text.ProtocolBuffers.Reflections.ReflectDescriptor' and
--   'Text.ProtocolBuffers.WireMessage.Wire' classes.
derivePersistFieldPB :: String  -- ^ Name of the type to derive instances for.
                     -> Q [Dec]
derivePersistFieldPB typName = do
    ss <- [|SqlBlob|]
    tpv <- [|PersistByteString . strictify . messagePut|]
    fpv <- [|\dt v ->
                case fromPersistValue v of
                    Left e -> Left e
                    Right s' ->
                        case (messageGet . lazify) s' of
                            Left e ->
                              Left $ T.concat ["Invalid ", dt, ": ", T.pack e]
                            Right (_, x) | BSL.length x /= 0 ->
                              Left $ T.concat ["Invalid ", dt, ": excess input"]
                            Right (msg, _) ->
                              Right msg|]
    return
        [ InstanceD [] (ConT ''PersistField `AppT` ConT (mkName typName))
            [ FunD (mkName "sqlType")
                [ Clause [WildP] (NormalB ss) []
                ]
            , FunD (mkName "toPersistValue")
                [ Clause [] (NormalB tpv) []
                ]
            , FunD (mkName "fromPersistValue")
                [ Clause [] (NormalB $ fpv `AppE` LitE (StringL typName)) []
                ]
            ]
        ]