{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase            #-}
{-# LANGUAGE NamedFieldPuns        #-}
{-# LANGUAGE OverloadedStrings     #-}
module Trans.PureToHaskell (fileToModules) where

import Data.Word

import Control.Monad (guard)
import Text.Printf   (printf)

import qualified Data.Text as T

import IR.Haskell
import Trans.ToHaskellCommon

import qualified IR.Common as C
import qualified IR.Name   as Name
import qualified IR.Pure   as P


-- | Modules imported by all generated modules.
commonImports :: [Import]
commonImports =
    [ ImportAs { importAs = "V", parts = reExp ["Data", "Vector"] }
    , ImportAs { importAs = "T", parts = reExp ["Data", "Text"] }
    , ImportAs { importAs = "BS", parts = reExp ["Data", "ByteString"] }
    , ImportAs { importAs = "Default", parts = reExp ["Data", "Default"] }
    , ImportAs { importAs = "Generics", parts = ["GHC", "Generics"] }
    , ImportAs { importAs = "MonadIO", parts = ["Control", "Monad", "IO", "Class"] }
    , ImportAs { importAs = "UntypedPure", parts = ["Capnp", "Untyped", "Pure"] }
    , ImportAs { importAs = "Untyped", parts = ["Capnp", "Untyped"] }
    , ImportAs { importAs = "Message", parts = ["Capnp", "Message"] }
    , ImportAs { importAs = "Classes", parts = ["Capnp", "Classes"] }
    , ImportAs { importAs = "BasicsPure", parts = ["Capnp", "Basics", "Pure" ] }
    , ImportAs { importAs = "GenHelpersPure", parts = ["Capnp", "GenHelpers", "Pure"] }
    ]


-- | Modules imported by generated modules that use rpc.
rpcImports :: [Import]
rpcImports =
    [ ImportAs { importAs = "Rpc", parts = ["Capnp", "Rpc", "Untyped"] }
    , ImportAs { importAs = "Server", parts = ["Capnp", "Rpc", "Server"] }
    , ImportAs { importAs = "RpcHelpers", parts = ["Capnp", "GenHelpers", "Rpc"] }
    , ImportAs { importAs = "STM", parts = reExp ["Control", "Concurrent", "STM"] }
    , ImportAs { importAs = "STM", parts = reExp [ "Control", "Monad", "STM", "Class"] }
    , ImportAs { importAs = "Supervisors", parts = reExp ["Supervisors"] }
    ]

reExp parts = ["Capnp", "GenHelpers", "ReExports"] ++ parts

-- | Whether the serialized and unserialized forms of this type
-- are the same. If not, there is a marshalling step, if so, the
-- accessors work with the decerialized form directly.
--
-- For example, this is True for Enums, basic integers, etc. but
-- False for structs, interfaces, etc.
cerialEq :: C.Type r -> Bool
cerialEq C.VoidType          = True
cerialEq (C.WordType _)      = True
cerialEq (C.CompositeType _) = False
cerialEq (C.PtrType _)       = False

fileToModules :: P.File -> [Module]
fileToModules file =
    [ fileToMainModule file
    , fileToModuleAlias file
    ]

fileToModuleAlias :: P.File -> Module
fileToModuleAlias P.File{fileName, fileId} =
    let reExport = ["Capnp", "Gen"] ++ makeModName fileName ++ ["Pure"]
    in Module
        { modName = idToModule fileId ++ ["Pure"]
        , modLangPragmas = []
        , modExports = Just [ExportMod reExport]
        , modImports = [ ImportAll { parts = reExport } ]
        , modDecls = []
        }

fileToMainModule :: P.File -> Module
fileToMainModule P.File{fileName, fileId, decls, reExportEnums, usesRpc} =
    fixImports $ Module
        { modName = ["Capnp", "Gen"] ++ makeModName fileName ++ ["Pure"]
        , modLangPragmas =
            [ "DeriveGeneric"
            , "DuplicateRecordFields"
            , "FlexibleContexts"
            , "FlexibleInstances"
            , "RecordWildCards"
            , "MultiParamTypeClasses"
            , "TypeFamilies"
            ]
        , modExports = Just $
            [ExportGCtors (gName (rawModule fileId) name) | name <- reExportEnums]
            ++ concatMap (declToExport fileId) decls
        , modImports =
            commonImports ++ (guard usesRpc >> rpcImports)
        , modDecls =
            concatMap (declToDecls fileId) decls
            ++ concatMap (enumInstances fileId) reExportEnums
        }


-- | Convert a declaration into the list of related exports we need.
-- The first argument is the id for this module.
declToExport :: Word64 -> P.Decl -> [Export]
declToExport fileId = \case
    P.DataDecl P.Data{typeName} ->
        [ ExportLCtors typeName ]
    P.ConstDecl P.Constant { name, value=C.WordValue _ _ } ->
        [ ExportGName $ gName (rawModule fileId) name ]
    P.ConstDecl P.Constant { name, value=C.VoidValue } ->
        [ ExportGName $ gName (rawModule fileId) name ]
    P.ConstDecl P.Constant { name, value=C.PtrValue _ _ } ->
        [ ExportLName name ]
    P.IFaceDecl P.IFace{ name=Name.CapnpQ{local} } ->
        [ ExportLCtors local
        , ExportLCtors (Name.mkSub local "server_")
        , ExportLName (Name.unQToLocal $ Name.UnQ $ "export_" <> Name.renderLocalQ local)
        ]

-- | enumInstances' generates some type class instances an enum data type.
enumInstances :: Word64 -> Name.LocalQ -> [Decl]
enumInstances thisMod name =
    let rawName = gName (rawModule thisMod) name in
    instance_ [] ["Classes"] "Decerialize" [TGName rawName]
        [ iType "Cerial" [tuName "msg", TGName rawName] (TGName rawName)
        , iValue "decerialize" [] (eStd_ "pure")
        ]
    : instance_ [] ["Classes"] "Cerialize" [TGName rawName]
        [ iValue "cerialize" [PVar "_"] (eStd_ "pure")
        ]
    : [ instance_ [] ["Classes"] "Cerialize" [t]
        [ iValue "cerialize" [] (egName ["Classes"] "cerializeBasicVec")
        ]
      | t <- take 6 $ drop 1 $ iterate
            (\t -> TApp (tgName ["V"] "Vector") [t])
            (TGName rawName)
      ]

-- | Convert an 'IR.Pure.Decl' into a series of Haskell declarations.
declToDecls :: Word64 -> P.Decl -> [Decl]
declToDecls thisMod (P.DataDecl data_)     = dataToDecls thisMod data_
declToDecls thisMod (P.ConstDecl constant) = constToDecls thisMod constant
declToDecls thisMod (P.IFaceDecl iface)    = ifaceToDecls thisMod iface

dataToDecls :: Word64 -> P.Data -> [Decl]
dataToDecls thisMod data_@P.Data{firstClass} = concat $
    [ dataToDataDecl thisMod data_
    , dataToSimpleInstances thisMod data_
    , dataToDecerialize thisMod data_
    , dataToMarshal thisMod data_
    ]
    ++
    [ firstClassInstances thisMod data_ | firstClass ]

-- | Get the name of a data constructor for a product type. Arguments are the
-- name of the type constructor and whether or not the type is "first class".
productVariantName :: Name.LocalQ -> Bool -> Name.LocalQ
productVariantName typeName firstClass
    | firstClass = typeName
    | otherwise = Name.mkSub typeName ""
    -- ^ This is a group. If it's part of a union, then the union
    -- will have a data constructor that is the same as typeName,
    -- so we appent a ' to avoid a colision.

dataToDataDecl :: Word64 -> P.Data -> [Decl]
dataToDataDecl thisMod P.Data
        { typeName
        , cerialName
        , firstClass
        , def
        } =
    let unknownCtor = Name.mkSub cerialName "unknown'" in
    [ DcData Data
        { dataName = Name.localToUnQ typeName
        , typeArgs = []
        , derives =
            ["Std_.Show", "Std_.Eq", "Generics.Generic"]
        , dataNewtype = False
        , dataVariants =
            case def of
                P.Product fields ->
                    [ DataVariant
                        { dvCtorName = Name.localToUnQ $ productVariantName typeName firstClass
                        , dvArgs = ARec (map (fieldToField thisMod) fields)
                        }
                    ]
                P.Sum variants ->
                    [ DataVariant
                        { dvCtorName = Name.localToUnQ name
                        , dvArgs = case arg of
                            Nothing -> APos []
                            Just ty -> APos [typeToType thisMod ty]
                        }
                    | P.Variant{name, arg} <- variants
                    ]
                    ++
                    [ DataVariant
                        { dvCtorName = Name.localToUnQ unknownCtor
                        , dvArgs = APos [TPrim $ C.PrimInt $ C.IntType C.Unsigned C.Sz16]
                        }
                    ]
        }
    ]

dataToSimpleInstances :: Word64 -> P.Data -> [Decl]
dataToSimpleInstances _thisMod P.Data{ typeName } =
    [ instance_ [] ["Default"] "Default" [TLName typeName]
        [ iValue "def" [] (egName ["GenHelpersPure"] "defaultStruct")
        ]
    , instance_ [] ["Classes"] "FromStruct" [tgName ["Message"] "ConstMsg", TLName typeName]
        [ iValue "fromStruct" [PVar "struct"] $ EBind
            (EApp (egName ["Classes"] "fromStruct") [euName "struct"])
            (egName ["Classes"] "decerialize")
        ]
    ]

dataToDecerialize :: Word64 -> P.Data -> [Decl]
dataToDecerialize thisMod P.Data
        { typeName
        , cerialName
        , firstClass
        , def
        } =
    [ instance_ [] ["Classes"] "Decerialize" [TLName typeName]
        [ iType "Cerial" [tuName "msg", TLName typeName] $
            TApp (tgName (rawModule thisMod) cerialName) [tuName "msg"]
        , iValue "decerialize" [PVar "raw"] $
            case def of
                P.Sum variants ->
                    variantsToDecerialize thisMod cerialName variants
                P.Product fields ->
                    fieldsToDecerialize thisMod typeName firstClass fields
        ]
    ]

variantsToDecerialize :: Word64 -> Name.LocalQ -> [P.Variant] -> Exp
variantsToDecerialize thisMod cerialName variants =
    let unknownCtor = Name.mkSub cerialName "unknown'"
        fieldGetter parentName name = egName
            (rawModule thisMod)
            (Name.unQToLocal $ Name.getterName $ Name.mkSub parentName name)
    in
    EDo
        [DoBind "raw" $ EApp (fieldGetter cerialName "") [euName "raw"]
        ]
        (ECase (ELName "raw") $
            [ case arg of
                Nothing ->
                    ( pgName (rawModule thisMod) name []
                    , EApp (eStd_ "pure") [ELName name]
                    )
                Just type_ | cerialEq type_ ->
                    ( pgName (rawModule thisMod) name [PVar "raw"]
                    , EApp (eStd_ "pure") [EApp (ELName name) [euName "raw"]]
                    )
                Just _ ->
                    ( pgName (rawModule thisMod) name [PVar "raw"]
                    , EFApp
                        (ELName name)
                        [EApp (egName ["Classes"] "decerialize") [euName "raw"]]
                    )
            | P.Variant{name, arg} <- variants
            ]
            ++
            [ ( pgName (rawModule thisMod) unknownCtor [PVar "tag"]
              , EApp
                  (eStd_ "pure")
                  [EApp (ELName unknownCtor) [euName "tag"]]
              )
            ]
        )

fieldsToDecerialize :: Word64 -> Name.LocalQ -> Bool -> [P.Field] -> Exp
fieldsToDecerialize thisMod typeName firstClass fields =
    let fieldGetter parentName name = egName
            (rawModule thisMod)
            (Name.unQToLocal $ Name.getterName $ Name.mkSub parentName name)
        variantName = productVariantName typeName firstClass
    in
    case fields of
        [] ->
            EApp (eStd_ "pure") [ELName variantName]
        _ ->
            EFApp
                (ELName variantName)
                [
                    let getter = EApp (fieldGetter typeName name) [euName "raw"] in
                    if name == "union'" then
                        -- unions decerialize from the same type as their parents. Don't
                        -- do anything but pass it off.
                        EApp (egName ["Classes"] "decerialize") [euName "raw"]
                    else if cerialEq type_ then
                        getter
                    else
                        EBind getter (egName ["Classes"] "decerialize")
                | P.Field{name, type_} <- fields
                ]


dataToMarshal :: Word64 -> P.Data -> [Decl]
dataToMarshal thisMod P.Data
        { typeName
        , cerialName
        , firstClass
        , def
        } =
    [ instance_ [] ["Classes"] "Marshal" [TLName typeName]
        [ iValue "marshalInto" [PVar "raw_", PVar "value_"] $
            case def of
                P.Sum variants ->
                    variantsToMarshal thisMod cerialName variants
                P.Product fields ->
                    fieldsToMarshal thisMod typeName firstClass fields
        ]
    ]

variantsToMarshal :: Word64 -> Name.LocalQ -> [P.Variant] -> Exp
variantsToMarshal thisMod cerialName variants =
    let unknownCtor = Name.mkSub cerialName "unknown'" in
    ECase (euName "value_") $
        [ let setter = Name.unQToLocal $ Name.setterName variantName
              setExp = EApp (egName (rawModule thisMod) setter) [euName "raw_"]
          in case arg of
            Nothing ->
                ( PLCtor variantName []
                , setExp
                )
            Just type_ ->
                ( PLCtor variantName [PVar "arg_"]
                , marshalField MarshalField
                    { thisMod
                    , into = "raw_"
                    , localQField = variantName
                    , from = "arg_"
                    , type_
                    , inUnion = True
                    }
                )
        | P.Variant{name=variantName, arg} <- variants
        ]
        ++
        let setter = Name.unQToLocal $ Name.setterName unknownCtor in
        [ ( PLCtor unknownCtor [PVar "tag"]
          , EApp (egName (rawModule thisMod) setter) [euName "raw_", euName "tag"]
          )
        ]

fieldsToMarshal :: Word64 -> Name.LocalQ -> Bool -> [P.Field] -> Exp
fieldsToMarshal thisMod typeName firstClass fields =
    let variantName = productVariantName typeName firstClass in
    ECase (euName "value_")
        [ ( case fields of
              -- If there are no fields, use of RecordWildCards will
              -- cause an error:
              [] -> PLCtor variantName []
              _  -> PLRecordWildCard variantName
          , EDo
              [ DoE $ marshalField MarshalField
                  { thisMod
                  , into = "raw_"
                  , localQField = Name.mkSub typeName fieldName
                  , from = fieldName
                  , type_
                  , inUnion = False
                  }
              | P.Field{name=fieldName, type_} <- fields
              ]
              ePureUnit
          )
        ]

firstClassInstances :: Word64 -> P.Data -> [Decl]
firstClassInstances _thisMod P.Data{ typeName } =
    [ instance_ [] ["Classes"] "Cerialize" [TLName typeName] []
    , instance_ [] ["Classes"] "Cerialize" [TApp (tgName ["V"] "Vector") [TLName typeName]]
        [ iValue "cerialize" [] (egName ["GenHelpersPure"] "cerializeCompositeVec")
        ]
    ] ++
    -- Generate instances of Cerialize (Vector (Vector ... t)) up to some reasonable
    -- nesting level. I(zenhack) can't figure out how to get a general case
    -- Cerialize (Vector a) => Cerialize (Vector (Vector a)) to type check, so this
    -- will have to do for now.
    [ instance_ [] ["Classes"] "Cerialize" [t]
        [ iValue "cerialize" [] (egName ["GenHelpersPure"] "cerializeBasicVec")
        ]
    | t <- take 6 $ drop 2 $ iterate
            (\t -> TApp (tgName ["V"] "Vector") [t])
            (TLName typeName)
    ]

ifaceToDecls :: Word64 -> P.Interface -> [Decl]
ifaceToDecls thisMod iface =
    [ ifaceClientDecl thisMod iface
    , ifaceClassDecl thisMod iface
    , ifaceExportFn thisMod iface
    ]
    ++
    ifaceInstances thisMod iface

-- | Declare the newtype wrapper for clients of this interface.
ifaceClientDecl :: Word64 -> P.Interface -> Decl
ifaceClientDecl _thisMod P.IFace{ name=Name.CapnpQ{local=name} } =
    DcData Data
        { dataName = Name.localToUnQ name
        , dataNewtype = True
        , dataVariants =
            [ DataVariant
                { dvCtorName = Name.localToUnQ name
                , dvArgs = APos [tgName ["Message"] "Client"]
                }
            ]
        , typeArgs = []
        , derives =
            [ "Std_.Show"
            , "Std_.Eq"
            , "Generics.Generic"
            ]
        }

-- | define the *'server_ class for the interface.
ifaceClassDecl :: Word64 -> P.Interface -> Decl
ifaceClassDecl thisMod P.IFace { name=Name.CapnpQ{local=name}, methods, supers } =
    DcClass
        { ctx =
            TApp (tgName ["MonadIO"] "MonadIO") [tuName "m"]
            :
            -- Add class constraints for superclasses:
            [ let superClass = pureTName thisMod fileId (Name.mkSub local "server_")
              in TApp superClass [tuName "m", tuName "cap"]
            | P.IFace{name=Name.CapnpQ{local, fileId}} <- supers
            ]
        , name = Name.mkSub name "server_"
        , params = ["m", "cap"]
        , decls =
            let mkName = mkMethodName name in
            CdMinimal [ mkName name | P.Method{name} <- methods ]
            : concat
                [ [ CdValueDecl
                        (mkName methodName)
                        (TFn
                            [ tuName "cap"
                            , TApp
                                (tgName ["Server"] "MethodHandler")
                                [ tuName "m"
                                , typeToType thisMod $ C.CompositeType $ C.StructType paramType
                                , typeToType thisMod $ C.CompositeType $ C.StructType resultType
                                ]
                            ]
                        )
                    , CdValueDef DfValue
                        { name = mkName methodName
                        , params = [PVar "_"]
                        , value = egName ["Server"] "methodUnimplemented"
                        }
                    ]
                | P.Method
                    { name=methodName
                    , paramType
                    , resultType
                    }
                <- methods
                ]
        }


-- | Define the export_Foo function for the interface.
ifaceExportFn :: Word64 -> P.Interface -> Decl
ifaceExportFn thisMod iface@P.IFace { name=Name.CapnpQ{ local }, ancestors } =
    DcValue
        { typ = TCtx
            [ TApp (TLName (Name.mkSub local "server_")) [tStd_ "IO", tuName "a"]
            , TApp (tgName ["STM"] "MonadSTM") [tuName "m"]
            ]
            (TFn
                [ tgName ["Supervisors"] "Supervisor"
                , tuName "a"
                , TApp (tuName "m") [TLName local]
                ]
            )
        , def = DfValue
            { name = Name.UnQ $ "export_" <> Name.renderLocalQ local
            , params = [PVar "sup_", PVar "server_"]
            , value = EApp (egName ["STM"] "liftSTM") $ pure $ EFApp (ELName local)
                [ EApp (egName ["Rpc"] "export")
                    [ euName "sup_"
                    , ERecord (egName ["Server"] "ServerOps")
                        [ ( "handleStop"
                          , ePureUnit
                          ) -- TODO
                        , ( "handleCall"
                          , ELambda [PVar "interfaceId_", PVar "methodId_"] $
                                ECase (euName "interfaceId_") $
                                    [ ( PInt (fromIntegral interfaceId)
                                      , ECase (euName "methodId_") $
                                            [ ( PInt i
                                              , EApp
                                                    (egName ["Server"] "toUntypedHandler")
                                                    [ let methodName = mkMethodName ifaceName mname in
                                                      EApp
                                                        (if thisMod == ifaceMod
                                                            then euName methodName
                                                            else egName (pureModule ifaceMod) (Name.unQToLocal methodName)
                                                        )
                                                        [euName "server_"]
                                                    ]
                                              )
                                            | (i, P.Method{name=mname}) <- zip [0..] methods
                                            ]
                                            ++
                                            [ ( PVar "_"
                                              , egName ["Server"] "methodUnimplemented"
                                              )
                                            ]
                                      )
                                    | P.IFace
                                        { name = Name.CapnpQ{local=ifaceName, fileId=ifaceMod}
                                        , interfaceId
                                        , methods
                                        }
                                    <- iface:ancestors
                                    ]
                                    ++
                                    [ ( PVar "_"
                                      , egName ["Server"] "methodUnimplemented"
                                      )
                                    ]
                          )
                        ]
                    ]
                ]
            }
        }

-- | Declare instances for clients for this interface.
ifaceInstances :: Word64 -> P.Interface -> [Decl]
ifaceInstances thisMod iface@P.IFace{ name=Name.CapnpQ{local=name} } =
    [ instance_ [] ["Rpc"] "IsClient" [TLName name]
        [ iValue "fromClient" [] (ELName name)
        , iValue "toClient" [PLCtor name [PVar "client"]] (euName "client")
        ]
    , instance_ [] ["Classes"] "FromPtr" [tuName "msg", TLName name]
        [ iValue "fromPtr" [] (egName ["RpcHelpers"] "isClientFromPtr")
        ]
    , instance_ [] ["Classes"] "ToPtr" [tuName "s", TLName name]
        [ iValue "toPtr" [] (egName ["RpcHelpers"] "isClientToPtr")
        ]
    , instance_ [] ["Classes"] "Decerialize" [TLName name]
        [ iType "Cerial" [tuName "msg", TLName name] $
            TApp (tgName (rawModule thisMod) name) [tuName "msg"]
        , iValue "decerialize"
            [ pgName (rawModule thisMod) (Name.mkSub name "newtype_") [PVar "maybeCap"]
            ]
            (ECase (euName "maybeCap")
                [ (PGCtor (std_ "Nothing") []
                  , EApp
                        (eStd_ "pure")
                        [ EApp (ELName name) [egName ["Message"] "nullClient"]
                        ]
                  )
                , (PGCtor (std_ "Just") [PVar "cap"]
                  , EFApp
                        (ELName name)
                        [ EApp (egName ["Untyped"] "getClient") [euName "cap"]]
                  )
                ]
            )
        ]
    , instance_ [] ["Classes"] "Cerialize" [TLName name]
        [ iValue "cerialize" [PVar "msg", PLCtor name [PVar "client"]] $
            EFApp
                (egName (rawModule thisMod) (Name.mkSub name "newtype_"))
                [ EFApp
                    (eStd_ "Just")
                    [EApp (egName ["Untyped"] "appendCap") [euName "msg", euName "client"]]
                ]
        ]
    ]
    ++
    ifaceServerInstances thisMod iface

-- | Generate a reference to a type defined in a pure module given:
--
-- * The id of the module we're in (thisMod)
-- * The id of the module in which the type is defined (targetMod)
-- * The local name of the type.
pureTName :: Word64 -> Word64 -> Name.LocalQ -> Type
pureTName thisMod targetMod local
    | thisMod == targetMod = TLName local
    | otherwise = tgName (pureModule targetMod) local

-- | Instance declarations for this interface's client for its *'server_ class
-- and those of its ancestors.
ifaceServerInstances :: Word64 -> P.Interface -> [Decl]
ifaceServerInstances thisMod iface@P.IFace{ name=Name.CapnpQ{local=name}, ancestors } =
    map go (iface:ancestors)
  where
    go P.IFace { name=Name.CapnpQ{local, fileId}, interfaceId, methods } =
        let className = Name.mkSub local "server_"
            classType = pureTName thisMod fileId className
        in
        -- Can't use 'instance_' here, because we don't know ahead of time what
        -- module the class is in.
        DcInstance
            { ctx = []
            , typ =
                TApp classType [tStd_ "IO", TLName name]
            , defs =
                [ let methodName = mkMethodName local mname in
                  iValue methodName [PLCtor name [PVar "client"]] $
                    EApp
                        (egName ["Rpc"] "clientMethodHandler")
                        [ EInt $ fromIntegral interfaceId
                        , EInt i
                        , euName "client"
                        ]
                | (i, P.Method{name=mname}) <- zip [0..] methods
                ]
            }

-- | Generate declarations for a constant.
constToDecls :: Word64 -> P.Constant -> [Decl]
constToDecls thisMod P.Constant { name, value } = case value of
    C.PtrValue ty _ ->
        -- Generated code parses the corresponding constant from the raw
        -- module.
        [ DcValue
            { typ = typeToType thisMod (C.PtrType ty)
            , def = DfValue
                { name = Name.localToUnQ name
                , params = []
                , value = EApp
                    (egName ["GenHelpersPure"] "toPurePtrConst")
                    [egName (rawModule thisMod) name]
                }
            }
        ]
    -- For these two we just re-export the constant from the raw module, so no
    -- need to do anything here:
    C.WordValue _ _ -> []
    C.VoidValue -> []

mkMethodName :: Name.LocalQ -> Name.UnQ -> Name.UnQ
mkMethodName typeName methodName = Name.valueName $ Name.mkSub typeName methodName

-- | Arguments to 'marshalField'
data MarshalField = MarshalField
    { thisMod     :: !Word64
    -- ^ The id for the module we're generating
    , into        :: Name.UnQ
    -- ^ An variable holding the destination for the marshalled data.
    , localQField :: Name.LocalQ
    -- ^ The name of the field to marshal, qualified within the current module.
    , from        :: Name.UnQ
    -- ^ A variable holding the value to marshal
    , type_       :: C.Type Name.CapnpQ
    , inUnion     :: !Bool
    -- ^ whether the parent of this field is a union
    }

marshalField :: MarshalField -> Exp
marshalField MarshalField{thisMod, into, localQField, from, type_, inUnion} =
    let setter = egName (rawModule thisMod) $ Name.unQToLocal (Name.setterName localQField)
        getter = egName (rawModule thisMod) $ Name.unQToLocal (Name.getterName localQField)
    in case type_ of
        C.PtrType _ ->
            EBind
                (EApp
                    (egName ["Classes"] "cerialize")
                    [ EApp (egName ["Untyped"] "message") [euName "raw_"]
                    , euName from
                    ]
                )
                (EApp setter [euName into])
        C.VoidType ->
            ePureUnit
        C.WordType _ ->
            EApp setter [euName into, euName from]
        C.CompositeType _ -> EDo
            (if Name.getUnQ localQField == "union'" then
                -- Anonymous union; has the same Cerial as us. Just delegate.
                []
            else if inUnion then
                [ DoBind into $ EApp setter [euName into] ]
            else
                [ DoBind into $ EApp getter [euName into] ]
            )
            (EApp (egName ["Classes"] "marshalInto") [euName into, euName from])

fieldToField :: Word64 -> P.Field -> (Name.UnQ, Type)
fieldToField thisMod P.Field{name, type_} = (name, typeToType thisMod type_)

typeToType :: Word64 -> C.Type Name.CapnpQ -> Type
typeToType _thisMod C.VoidType =
    TUnit
typeToType _thisMod (C.WordType (C.PrimWord ty)) =
    TPrim ty
typeToType _thisMod (C.WordType (C.EnumType Name.CapnpQ{local, fileId})) =
    -- Enums are just re-exported from the raw module, we should still
    -- refer to them qualified even if they're in the file we're generated
    -- from:
    tgName (rawModule fileId) local
typeToType thisMod (C.CompositeType (C.StructType n)) =
    nameToType thisMod n
typeToType thisMod (C.PtrType (C.PtrComposite (C.StructType n))) =
    nameToType thisMod n
typeToType thisMod (C.PtrType (C.ListOf ty)) =
    TApp (tgName ["V"] "Vector") [typeToType thisMod ty]
typeToType _thisMod (C.PtrType (C.PrimPtr C.PrimText)) =
    tgName ["T"] "Text"
typeToType _thisMod (C.PtrType (C.PrimPtr C.PrimData)) =
    tgName ["BS"] "ByteString"
typeToType _thisMod (C.PtrType (C.PrimPtr (C.PrimAnyPtr _))) =
    -- TODO: distinguish different pointer types.
    TApp (tStd_ "Maybe") [tgName ["UntypedPure"] "Ptr"]
typeToType thisMod (C.PtrType (C.PtrInterface n)) =
    nameToType thisMod n

nameToType :: Word64 -> Name.CapnpQ -> Type
nameToType thisMod Name.CapnpQ{local, fileId}
    | thisMod == fileId = TLName local
    | otherwise = tgName (pureModule fileId) local

rawModule :: Word64 -> [T.Text]
rawModule modId =
    ["Capnp", "Gen", "ById", T.pack $ printf "X%x" modId]

pureModule :: Word64 -> [T.Text]
pureModule modId =
    rawModule modId ++ ["Pure"]
