{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}

module Trans.AbstractOpToHaskell
  ( fileToModules,
  )
where

import qualified Capnp.Repr as R
import Control.Monad (guard)
import Data.String (IsString (fromString))
import Data.Word
import qualified IR.AbstractOp as AO
import qualified IR.Common as C
import qualified IR.Haskell as Hs
import qualified IR.Name as Name
import Trans.ToHaskellCommon

-- | Modules imported by all generated modules.
imports :: [Hs.Import]
imports =
  [ Hs.ImportAs {importAs = "R", parts = ["Capnp", "Repr"]},
    Hs.ImportAs {importAs = "RP", parts = ["Capnp", "Repr", "Parsed"]},
    Hs.ImportAs {importAs = "Basics", parts = ["Capnp", "Basics"]},
    Hs.ImportAs {importAs = "OL", parts = ["GHC", "OverloadedLabels"]},
    Hs.ImportAs {importAs = "GH", parts = ["Capnp", "GenHelpers"]},
    Hs.ImportAs {importAs = "C", parts = ["Capnp", "Classes"]},
    Hs.ImportAs {importAs = "Generics", parts = ["GHC", "Generics"]}
  ]

-- | Modules imported by generated modules that use rpc. We separate these out to
-- avoid a circular import when generating code for rpc.capnp -- which does not
-- contain interfaces, so does not need to import the rpc system -- but which
-- must be imported *by* the rpc system.
rpcImports :: [Hs.Import]
rpcImports =
  [ Hs.ImportAs {importAs = "GH", parts = ["Capnp", "GenHelpers", "Rpc"]}
  ]

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

fileToMainModule :: AO.File -> Hs.Module
fileToMainModule file@AO.File {fileName, usesRpc} =
  fixImports $
    Hs.Module
      { modName = ["Capnp", "Gen"] ++ makeModName fileName,
        modLangPragmas =
          [ "TypeFamilies",
            "DataKinds",
            "DeriveGeneric",
            "DuplicateRecordFields",
            "EmptyDataDeriving",
            "FlexibleContexts",
            "FlexibleInstances",
            "MultiParamTypeClasses",
            "UndecidableInstances",
            "UndecidableSuperClasses",
            "OverloadedLabels",
            "OverloadedStrings",
            "StandaloneDeriving",
            "RecordWildCards",
            "TypeApplications",
            "ScopedTypeVariables"
          ],
        modExports = Nothing,
        modImports = imports ++ (guard usesRpc >> rpcImports),
        modDecls = fileToDecls file
      }

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

fileToDecls :: AO.File -> [Hs.Decl]
fileToDecls AO.File {fileId, decls} =
  concatMap (declToDecls fileId) decls

declToDecls :: Word64 -> AO.Decl -> [Hs.Decl]
declToDecls thisMod decl =
  case decl of
    AO.TypeDecl {name, nodeId, params, repr, extraTypeInfo} ->
      let dataName = Name.localToUnQ name
          typeArgs = toTVars params
          typ = case typeArgs of
            [] -> tuName dataName
            _ -> Hs.TApp (tuName dataName) typeArgs
       in [ Hs.DcData
              Hs.Data
                { dataName,
                  typeArgs,
                  dataVariants =
                    case extraTypeInfo of
                      Just (AO.EnumTypeInfo variants) ->
                        [ Hs.DataVariant
                            { dvCtorName = Name.localToUnQ $ Name.mkSub name variantName,
                              dvArgs = Hs.APos []
                            }
                          | variantName <- variants
                        ]
                          ++ [declareUnknownVariant name]
                      _ -> [],
                  derives =
                    case extraTypeInfo of
                      Just AO.EnumTypeInfo {} -> ["Std_.Eq", "Std_.Show", "Generics.Generic"]
                      _ -> [],
                  dataNewtype = False,
                  dataInstance = False
                },
            Hs.DcTypeInstance
              (Hs.TApp (tgName ["R"] "ReprFor") [typ])
              (toType repr),
            Hs.DcInstance
              { ctx = [],
                typ = Hs.TApp (tgName ["C"] "HasTypeId") [typ],
                defs =
                  [ Hs.IdValue
                      Hs.DfValue
                        { name = "typeId",
                          params = [],
                          value = Hs.EInt (fromIntegral nodeId)
                        }
                  ]
              }
          ]
            ++ let ctx = paramsContext typeArgs
                in case extraTypeInfo of
                     Just AO.StructTypeInfo {nWords, nPtrs} ->
                       [ Hs.DcInstance
                           { ctx,
                             typ = Hs.TApp (tgName ["C"] "TypedStruct") [typ],
                             defs =
                               [ Hs.IdValue
                                   Hs.DfValue
                                     { name = "numStructWords",
                                       params = [],
                                       value = Hs.EInt $ fromIntegral nWords
                                     },
                                 Hs.IdValue
                                   Hs.DfValue
                                     { name = "numStructPtrs",
                                       params = [],
                                       value = Hs.EInt $ fromIntegral nPtrs
                                     }
                               ]
                           },
                         Hs.DcInstance
                           { ctx,
                             typ = Hs.TApp (tgName ["C"] "Allocate") [typ],
                             defs =
                               [ Hs.IdType $
                                   Hs.TypeAlias "AllocHint" [typ] Hs.TUnit,
                                 Hs.IdValue
                                   Hs.DfValue
                                     { name = "new",
                                       params = [Hs.PVar "_"],
                                       value = egName ["C"] "newTypedStruct"
                                     }
                               ]
                           },
                         Hs.DcInstance
                           { ctx,
                             typ =
                               Hs.TApp
                                 (tgName ["C"] "EstimateAlloc")
                                 [typ, Hs.TApp (tgName ["C"] "Parsed") [typ]],
                             defs = []
                           },
                         Hs.DcInstance
                           { ctx,
                             typ = Hs.TApp (tgName ["C"] "AllocateList") [typ],
                             defs =
                               [ Hs.IdType $ Hs.TypeAlias "ListAllocHint" [typ] (tStd_ "Int"),
                                 Hs.IdValue
                                   Hs.DfValue
                                     { name = "newList",
                                       params = [],
                                       value = egName ["C"] "newTypedStructList"
                                     }
                               ]
                           },
                         Hs.DcInstance
                           { ctx,
                             typ =
                               Hs.TApp
                                 (tgName ["C"] "EstimateListAlloc")
                                 [typ, Hs.TApp (tgName ["C"] "Parsed") [typ]],
                             defs = []
                           }
                       ]
                     Just (AO.EnumTypeInfo variants) ->
                       [ Hs.DcInstance
                           { ctx,
                             typ = Hs.TApp (tStd_ "Enum") [typ],
                             defs =
                               [ Hs.IdValue
                                   Hs.DfValue
                                     { name = "toEnum",
                                       params = [Hs.PVar "n_"],
                                       value =
                                         Hs.ECase (euName "n_") $
                                           [ ( Hs.PInt i,
                                               Hs.ELName (Name.mkSub name variantName)
                                             )
                                             | (i, variantName) <- zip [0 ..] variants
                                           ]
                                             ++ [ ( Hs.PVar "tag_",
                                                    Hs.EApp
                                                      (Hs.ELName (unknownVariant name))
                                                      [Hs.EApp (eStd_ "fromIntegral") [euName "tag_"]]
                                                  )
                                                ]
                                     },
                                 Hs.IdValue
                                   Hs.DfValue
                                     { name = "fromEnum",
                                       params = [Hs.PVar "value_"],
                                       value =
                                         Hs.ECase (euName "value_") $
                                           [ ( Hs.PLCtor (Name.mkSub name variantName) [],
                                               Hs.EInt i
                                             )
                                             | (i, variantName) <- zip [0 ..] variants
                                           ]
                                             ++ [ ( Hs.PLCtor (unknownVariant name) [Hs.PVar "tag_"],
                                                    Hs.EApp (eStd_ "fromIntegral") [euName "tag_"]
                                                  )
                                                ]
                                     }
                               ]
                           },
                         Hs.DcInstance
                           { ctx,
                             typ = Hs.TApp (tgName ["C"] "IsWord") [typ],
                             defs =
                               [ Hs.IdValue
                                   Hs.DfValue
                                     { name = "fromWord",
                                       params = [Hs.PVar "w_"],
                                       value = Hs.EApp (eStd_ "toEnum") [Hs.EApp (eStd_ "fromIntegral") [Hs.EVar "w_"]]
                                     },
                                 Hs.IdValue
                                   Hs.DfValue
                                     { name = "toWord",
                                       params = [Hs.PVar "v_"],
                                       value = Hs.EApp (eStd_ "fromIntegral") [Hs.EApp (eStd_ "fromEnum") [Hs.EVar "v_"]]
                                     }
                               ]
                           },
                         Hs.DcInstance
                           { ctx,
                             typ = Hs.TApp (tgName ["C"] "Parse") [typ, typ],
                             defs =
                               [ Hs.IdValue
                                   Hs.DfValue
                                     { name = "parse",
                                       params = [],
                                       value = egName ["GH"] "parseEnum"
                                     },
                                 Hs.IdValue
                                   Hs.DfValue
                                     { name = "encode",
                                       params = [],
                                       value = egName ["GH"] "encodeEnum"
                                     }
                               ]
                           },
                         Hs.DcInstance
                           { ctx,
                             typ = Hs.TApp (tgName ["C"] "AllocateList") [typ],
                             defs =
                               [ Hs.IdType $ Hs.TypeAlias "ListAllocHint" [typ] (tStd_ "Int")
                               ]
                           },
                         Hs.DcInstance
                           { ctx,
                             typ = Hs.TApp (tgName ["C"] "EstimateListAlloc") [typ, typ],
                             defs = []
                           }
                       ]
                     Just AO.InterfaceTypeInfo {methods, supers} ->
                       defineInterfaceParse name params
                         ++ defineInterfaceServer thisMod name params methods supers
                     Nothing -> []
    AO.FieldDecl {containerType, typeParams, fieldName, fieldLocType} ->
      let tVars = toTVars typeParams
          ctx = paramsContext tVars
          labelType = Hs.TString (Name.renderUnQ fieldName)
          parentType = Hs.TApp (Hs.TLName containerType) tVars
          childType = fieldLocTypeToType thisMod fieldLocType
          fieldKind = Hs.TGName $ fieldLocTypeToFieldKind fieldLocType
       in [ Hs.DcInstance
              { ctx,
                typ =
                  Hs.TApp
                    (tgName ["GH"] "HasField")
                    [labelType, fieldKind, parentType, childType],
                defs =
                  [ Hs.IdValue
                      Hs.DfValue
                        { name = "fieldByLabel",
                          value = fieldLocTypeToField fieldLocType,
                          params = []
                        }
                  ]
              }
          ]
    AO.UnionDecl {name, typeParams, tagLoc, variants} ->
      let tVars = toTVars typeParams
          typ = Hs.TApp (Hs.TLName name) tVars
       in Hs.DcInstance
            { ctx = paramsContext tVars,
              typ = Hs.TApp (tgName ["GH"] "HasUnion") [typ],
              defs =
                [ Hs.IdValue
                    Hs.DfValue
                      { name = "unionField",
                        params = [],
                        value =
                          fieldLocTypeToField $
                            C.DataField
                              tagLoc
                              (C.PrimWord (C.PrimInt (C.IntType C.Unsigned C.Sz16)))
                      },
                  defineRawData thisMod name tVars variants,
                  defineInternalWhich name variants,
                  Hs.IdData
                    Hs.Data
                      { dataName = "Which",
                        typeArgs = [typ],
                        dataVariants = [],
                        derives = [],
                        dataNewtype = False,
                        dataInstance = False
                      }
                ]
            }
            : concatMap (variantToDecls thisMod name typeParams) variants
    AO.SuperDecl {subName, typeParams, superType} ->
      let tVars = toTVars typeParams
       in [ Hs.DcInstance
              { ctx = paramsContext tVars,
                typ =
                  Hs.TApp
                    (tgName ["C"] "Super")
                    [ typeToType thisMod $ C.PtrType $ C.PtrInterface superType,
                      Hs.TApp (Hs.TLName subName) tVars
                    ],
                defs = []
              }
          ]
    AO.MethodDecl
      { interfaceName,
        interfaceId,
        methodId,
        methodInfo =
          AO.MethodInfo
            { typeParams,
              methodName,
              paramType,
              resultType
            }
      } ->
        let tVars = toTVars typeParams
         in [ Hs.DcInstance
                { ctx = paramsContext tVars,
                  typ =
                    Hs.TApp
                      (tgName ["GH"] "HasMethod")
                      [ Hs.TString (Name.renderUnQ methodName),
                        Hs.TApp (Hs.TLName interfaceName) tVars,
                        compositeTypeToType thisMod paramType,
                        compositeTypeToType thisMod resultType
                      ],
                  defs =
                    [ Hs.IdValue
                        Hs.DfValue
                          { name = "methodByLabel",
                            params = [],
                            value =
                              Hs.EApp
                                (egName ["GH"] "Method")
                                [ Hs.EInt $ fromIntegral interfaceId,
                                  Hs.EInt $ fromIntegral methodId
                                ]
                          }
                    ]
                }
            ]
    AO.ParsedInstanceDecl {typeName, typeParams, parsedInstances} ->
      defineParsedInstances thisMod typeName typeParams parsedInstances
    AO.ConstDecl {name, value} ->
      defineConstant thisMod name value

defineConstant thisMod localName value =
  let name = Name.localToUnQ localName
   in case value of
        C.VoidValue ->
          [ Hs.DcValue
              { typ = Hs.TUnit,
                def =
                  Hs.DfValue
                    { name,
                      params = [],
                      value = Hs.ETup []
                    }
              }
          ]
        C.WordValue t v ->
          [ Hs.DcValue
              { typ = typeToType thisMod (C.WordType t),
                def =
                  Hs.DfValue
                    { name,
                      params = [],
                      value = Hs.EApp (egName ["C"] "fromWord") [Hs.EInt (fromIntegral v)]
                    }
              }
          ]
        C.PtrValue t v ->
          [ Hs.DcValue
              { typ = Hs.TApp (tgName ["R"] "Raw") [typeToType thisMod (C.PtrType t), tgName ["GH"] "Const"],
                def =
                  Hs.DfValue
                    { name,
                      params = [],
                      value =
                        Hs.EApp
                          (egName ["GH"] "getPtrConst")
                          [Hs.ETypeAnno (Hs.EBytes (makePtrBytes v)) (tgName ["GH"] "ByteString")]
                    }
              }
          ]

defineRawData thisMod name tVars variants =
  Hs.IdData
    Hs.Data
      { dataName = "RawWhich",
        typeArgs =
          [ Hs.TApp (Hs.TVar $ Name.renderUnQ $ Name.localToUnQ name) tVars,
            Hs.TVar "mut_"
          ],
        dataNewtype = False,
        dataInstance = False,
        dataVariants =
          [ Hs.DataVariant
              { dvCtorName =
                  "RW_" <> Name.localToUnQ (Name.mkSub name variantName),
                dvArgs =
                  Hs.APos
                    [ Hs.TApp
                        (tReprName "Raw")
                        [ fieldLocTypeToType thisMod fieldLocType,
                          Hs.TVar "mut_"
                        ]
                    ]
              }
            | AO.UnionVariant {variantName, fieldLocType} <- variants
          ]
            ++ [ Hs.DataVariant
                   { dvCtorName = "RW_" <> Name.localToUnQ (unknownVariant name),
                     dvArgs = Hs.APos [tStd_ "Word16"]
                   }
               ],
        -- TODO: derive Show, Read, Eq, Generic, to be feature complete with the code generated by RawToHaskell.
        -- This will require a stand-alone deriving declaration
        derives = []
      }

unknownVariant :: Name.LocalQ -> Name.LocalQ
unknownVariant name = Name.mkSub name "unknown'"

rawCtorName :: Name.LocalQ -> Name.UnQ
rawCtorName local = "RW_" <> Name.localToUnQ local

defineInternalWhich structName variants =
  Hs.IdValue
    Hs.DfValue
      { name = "internalWhich",
        params = [Hs.PVar "tag_", Hs.PVar "struct_"],
        value =
          Hs.ECase (Hs.ELName "tag_") $
            [ ( Hs.PInt $ fromIntegral tagValue,
                Hs.EFApp
                  (euName $ rawCtorName (Name.mkSub structName variantName))
                  [ Hs.EApp
                      (egName ["GH"] "readVariant")
                      [ Hs.ELabel variantName,
                        euName "struct_"
                      ]
                  ]
              )
              | AO.UnionVariant {tagValue, variantName} <- variants
            ]
              ++ [ ( Hs.PVar "_",
                     Hs.EApp
                       (eStd_ "pure")
                       [ Hs.EApp
                           (euName $ rawCtorName (unknownVariant structName))
                           [euName "tag_"]
                       ]
                   )
                 ]
      }

variantToDecls thisMod containerType typeParams AO.UnionVariant {tagValue, variantName, fieldLocType} =
  let tVars = toTVars typeParams
      ctx = paramsContext tVars
      labelType = Hs.TString (Name.renderUnQ variantName)
      parentType = Hs.TApp (Hs.TLName containerType) tVars
      childType = fieldLocTypeToType thisMod fieldLocType
      fieldKind = Hs.TGName $ fieldLocTypeToFieldKind fieldLocType
   in [ Hs.DcInstance
          { ctx,
            typ =
              Hs.TApp
                (tgName ["GH"] "HasVariant")
                [labelType, fieldKind, parentType, childType],
            defs =
              [ Hs.IdValue
                  Hs.DfValue
                    { name = "variantByLabel",
                      params = [],
                      value =
                        Hs.EApp
                          (egName ["GH"] "Variant")
                          [ fieldLocTypeToField fieldLocType,
                            Hs.EInt (fromIntegral tagValue)
                          ]
                    }
              ]
          }
      ]

paramsContext :: [Hs.Type] -> [Hs.Type]
paramsContext = map paramConstraints

-- | Constraints required for a capnproto type parameter. The returned
-- expression has kind 'Constraint'.
paramConstraints :: Hs.Type -> Hs.Type
paramConstraints t =
  Hs.TApp (tgName ["GH"] "TypeParam") [t]

tCapnp :: Word64 -> Name.CapnpQ -> Hs.Type
tCapnp thisMod Name.CapnpQ {local, fileId}
  | thisMod == fileId = Hs.TLName local
  | otherwise = tgName (map Name.renderUnQ $ idToModule fileId) local

fieldLocTypeToType :: Word64 -> C.FieldLocType AO.Brand Name.CapnpQ -> Hs.Type
fieldLocTypeToType thisMod = \case
  C.VoidField -> Hs.TUnit
  C.DataField _ t -> wordTypeToType thisMod t
  C.PtrField _ t -> ptrTypeToType thisMod t
  C.HereField t -> compositeTypeToType thisMod t

fieldLocTypeToFieldKind :: C.FieldLocType b n -> Name.GlobalQ
fieldLocTypeToFieldKind = \case
  C.HereField _ -> gName ["GH"] "Group"
  _ -> gName ["GH"] "Slot"

wordTypeToType thisMod = \case
  C.EnumType t -> tCapnp thisMod t
  C.PrimWord t -> primWordToType t

primWordToType = \case
  C.PrimInt t -> intTypeToType t
  C.PrimFloat32 -> tStd_ "Float"
  C.PrimFloat64 -> tStd_ "Double"
  C.PrimBool -> tStd_ "Bool"

intTypeToType (C.IntType sign size) =
  let prefix = case sign of
        C.Signed -> "Int"
        C.Unsigned -> "Word"
   in tStd_ $ fromString $ prefix ++ show (C.sizeBits size)

wordTypeBits = \case
  C.EnumType _ -> 16
  C.PrimWord (C.PrimInt (C.IntType _ size)) -> C.sizeBits size
  C.PrimWord C.PrimFloat32 -> 32
  C.PrimWord C.PrimFloat64 -> 64
  C.PrimWord C.PrimBool -> 1

ptrTypeToType thisMod = \case
  C.ListOf t -> Hs.TApp (tgName ["R"] "List") [typeToType thisMod t]
  C.PrimPtr t -> primPtrToType t
  C.PtrComposite t -> compositeTypeToType thisMod t
  C.PtrInterface t -> interfaceTypeToType thisMod t
  C.PtrParam t -> typeParamToType t

typeToType thisMod = \case
  C.CompositeType t -> compositeTypeToType thisMod t
  C.VoidType -> Hs.TUnit
  C.WordType t -> wordTypeToType thisMod t
  C.PtrType t -> ptrTypeToType thisMod t

primPtrToType = \case
  C.PrimText -> tgName ["Basics"] "Text"
  C.PrimData -> tgName ["Basics"] "Data"
  C.PrimAnyPtr t -> anyPtrToType t

anyPtrToType :: C.AnyPtr -> Hs.Type
anyPtrToType t = case t of
  C.Struct -> basics "AnyStruct"
  C.List -> basics "AnyList"
  C.Cap -> basics "Capability"
  C.Ptr -> Hs.TApp (tStd_ "Maybe") [basics "AnyPointer"]
  where
    basics = tgName ["Basics"]

compositeTypeToType thisMod (C.StructType name brand) = namedType thisMod name brand

interfaceTypeToType thisMod (C.InterfaceType name brand) = namedType thisMod name brand

typeParamToType = Hs.TVar . Name.typeVarName . C.paramName

namedType :: Word64 -> Name.CapnpQ -> C.ListBrand Name.CapnpQ -> Hs.Type
namedType thisMod name (C.ListBrand []) = tCapnp thisMod name
namedType thisMod name (C.ListBrand args) =
  Hs.TApp
    (tCapnp thisMod name)
    [typeToType thisMod (C.PtrType t) | t <- args]

fieldLocTypeToField = \case
  C.DataField loc wt ->
    let shift = C.dataOff loc
        index = C.dataIdx loc
        nbits = wordTypeBits wt
        defaultValue = C.dataDef loc
     in Hs.EApp
          (egName ["GH"] "dataField")
          [ Hs.EInt $ fromIntegral shift,
            Hs.EInt $ fromIntegral index,
            Hs.EInt $ fromIntegral nbits,
            Hs.EInt $ fromIntegral defaultValue
          ]
  C.PtrField idx _ ->
    Hs.EApp (egName ["GH"] "ptrField") [Hs.EInt $ fromIntegral idx]
  C.VoidField ->
    egName ["GH"] "voidField"
  C.HereField _ ->
    egName ["GH"] "groupField"

class ToType a where
  toType :: a -> Hs.Type

instance ToType R.Repr where
  toType (R.Ptr p) = rApp "Ptr" [toType p]
  toType (R.Data d) = rApp "Data" [toType d]

instance ToType a => ToType (Maybe a) where
  toType Nothing = tStd_ "Nothing"
  toType (Just a) = Hs.TApp (tStd_ "Just") [toType a]

instance ToType R.PtrRepr where
  toType R.Cap = tReprName "Cap"
  toType (R.List r) = rApp "List" [toType r]
  toType R.Struct = tReprName "Struct"

instance ToType R.ListRepr where
  toType (R.ListNormal nl) = rApp "ListNormal" [toType nl]
  toType R.ListComposite = tReprName "ListComposite"

instance ToType R.NormalListRepr where
  toType (R.NormalListData r) = rApp "ListData" [toType r]
  toType R.NormalListPtr = tReprName "ListPtr"

instance ToType R.DataSz where
  toType = tReprName . fromString . show

rApp :: Name.LocalQ -> [Hs.Type] -> Hs.Type
rApp n = Hs.TApp (tReprName n)

tReprName :: Name.LocalQ -> Hs.Type
tReprName = tgName ["R"]

declareUnknownVariant :: Name.LocalQ -> Hs.DataVariant
declareUnknownVariant name =
  Hs.DataVariant
    { dvCtorName = Name.localToUnQ $ Name.mkSub name "unknown'",
      dvArgs = Hs.APos [tStd_ "Word16"]
    }

defineParsedInstances :: Word64 -> Name.LocalQ -> [Name.UnQ] -> AO.ParsedInstances -> [Hs.Decl]
defineParsedInstances thisMod typeName typeParams instanceInfo =
  concatMap
    (\f -> f typeName typeParams instanceInfo)
    [ defineParsed thisMod,
      defineParse,
      defineMarshal
    ]

defineParsed :: Word64 -> Name.LocalQ -> [Name.UnQ] -> AO.ParsedInstances -> [Hs.Decl]
defineParsed thisMod typeName typeParams instanceInfo =
  let tVars = map (Hs.TVar . Name.typeVarName) typeParams
      typ = Hs.TApp (Hs.TLName typeName) tVars
      parsedTy = case instanceInfo of
        AO.ParsedStruct {} -> typ
        AO.ParsedUnion {} -> Hs.TApp (tgName ["GH"] "Which") [typ]
   in Hs.DcData
        Hs.Data
          { dataName = "C.Parsed",
            typeArgs = [parsedTy],
            derives = ["Generics.Generic"],
            dataNewtype = False,
            dataInstance = True,
            dataVariants =
              case instanceInfo of
                AO.ParsedStruct {fields, hasUnion, dataCtorName} ->
                  [ Hs.DataVariant
                      { dvCtorName = Name.localToUnQ dataCtorName,
                        dvArgs =
                          Hs.ARec $
                            [ ( name,
                                Hs.TApp
                                  (tgName ["RP"] "Parsed")
                                  [fieldLocTypeToType thisMod typ]
                              )
                              | (name, typ) <- fields
                            ]
                              ++ [ ( "union'",
                                     Hs.TApp
                                       (tgName ["C"] "Parsed")
                                       [Hs.TApp (tgName ["GH"] "Which") [typ]]
                                   )
                                   | hasUnion
                                 ]
                      }
                  ]
                AO.ParsedUnion {variants} ->
                  [ Hs.DataVariant
                      { dvCtorName = Name.localToUnQ $ Name.mkSub typeName name,
                        dvArgs = case ftype of
                          C.VoidField -> Hs.APos []
                          _ ->
                            Hs.APos
                              [ Hs.TApp
                                  (tgName ["RP"] "Parsed")
                                  [fieldLocTypeToType thisMod ftype]
                              ]
                      }
                    | (name, ftype) <- variants
                  ]
                    ++ [declareUnknownVariant typeName]
          }
        : [ Hs.DcDeriveInstance
              [ Hs.TApp (tStd_ cls) [Hs.TApp (tgName ["RP"] "Parsed") [v]]
                | v <- tVars
              ]
              (Hs.TApp (tStd_ cls) [Hs.TApp (tgName ["C"] "Parsed") [parsedTy]])
            | cls <- ["Show", "Eq"]
          ]

defineParse :: Name.LocalQ -> [Name.UnQ] -> AO.ParsedInstances -> [Hs.Decl]
defineParse typeName typeParams AO.ParsedStruct {fields, hasUnion, dataCtorName} =
  let tVars = toTVars typeParams
      typ = Hs.TApp (Hs.TLName typeName) tVars
   in [ Hs.DcInstance
          { ctx = paramsContext tVars,
            typ = Hs.TApp (tgName ["C"] "Parse") [typ, Hs.TApp (tgName ["C"] "Parsed") [typ]],
            defs =
              [ Hs.IdValue
                  Hs.DfValue
                    { name = "parse",
                      params = [Hs.PVar "raw_"],
                      value =
                        Hs.EFApp (Hs.ELName dataCtorName) $
                          [ Hs.EApp
                              (egName ["GH"] "parseField")
                              [Hs.ELabel field, euName "raw_"]
                            | field <- map fst fields
                          ]
                            ++ if hasUnion
                              then
                                [ Hs.EApp
                                    (egName ["C"] "parse")
                                    [Hs.EApp (egName ["GH"] "structUnion") [euName "raw_"]]
                                ]
                              else []
                    }
              ]
          }
      ]
defineParse typeName typeParams AO.ParsedUnion {variants} =
  let tVars = toTVars typeParams
      typ = Hs.TApp (tgName ["GH"] "Which") [Hs.TApp (Hs.TLName typeName) tVars]
   in [ Hs.DcInstance
          { ctx = paramsContext tVars,
            typ = Hs.TApp (tgName ["C"] "Parse") [typ, Hs.TApp (tgName ["C"] "Parsed") [typ]],
            defs =
              [ Hs.IdValue
                  Hs.DfValue
                    { name = "parse",
                      params = [Hs.PVar "raw_"],
                      value =
                        Hs.EDo
                          [ Hs.DoBind "rawWhich_" $ Hs.EApp (egName ["GH"] "unionWhich") [euName "raw_"]
                          ]
                          ( Hs.ECase (euName "rawWhich_") $
                              [ let ctorName = Name.mkSub typeName variantName
                                 in case fieldLocType of
                                      C.VoidField ->
                                        ( puName (rawCtorName ctorName) [Hs.PVar "_"],
                                          Hs.EApp (eStd_ "pure") [Hs.ELName ctorName]
                                        )
                                      _ ->
                                        ( puName (rawCtorName ctorName) [Hs.PVar "rawArg_"],
                                          Hs.EFApp
                                            (Hs.ELName ctorName)
                                            [Hs.EApp (egName ["C"] "parse") [euName "rawArg_"]]
                                        )
                                | (variantName, fieldLocType) <- variants
                              ]
                                ++ [ let ctorName = unknownVariant typeName
                                      in ( puName (rawCtorName ctorName) [Hs.PVar "tag_"],
                                           Hs.EApp
                                             (eStd_ "pure")
                                             [Hs.EApp (Hs.ELName ctorName) [euName "tag_"]]
                                         )
                                   ]
                          )
                    }
              ]
          }
      ]

defineInterfaceParse typeName typeParams =
  let tVars = toTVars typeParams
      typ = Hs.TApp (Hs.TLName typeName) tVars
   in [ Hs.DcInstance
          { ctx = paramsContext tVars,
            typ = Hs.TApp (tgName ["C"] "Parse") [typ, Hs.TApp (tgName ["GH"] "Client") [typ]],
            defs =
              [ Hs.IdValue
                  Hs.DfValue
                    { name = "parse",
                      params = [],
                      value = egName ["GH"] "parseCap"
                    },
                Hs.IdValue
                  Hs.DfValue
                    { name = "encode",
                      params = [],
                      value = egName ["GH"] "encodeCap"
                    }
              ]
          }
      ]

defineMarshal :: Name.LocalQ -> [Name.UnQ] -> AO.ParsedInstances -> [Hs.Decl]
defineMarshal typeName typeParams AO.ParsedStruct {fields, hasUnion, dataCtorName} =
  let tVars = toTVars typeParams
      typ = Hs.TApp (Hs.TLName typeName) tVars
   in [ Hs.DcInstance
          { ctx = paramsContext tVars,
            typ =
              Hs.TApp
                (tgName ["C"] "Marshal")
                [typ, Hs.TApp (tgName ["C"] "Parsed") [typ]],
            defs =
              [ if null fields && not hasUnion
                  then -- We need to special case this, since otherwise GHC will complain about
                  -- the record wildcard pattern on a ctor with no arguments.

                    Hs.IdValue
                      Hs.DfValue
                        { name = "marshalInto",
                          params = [Hs.PVar "_raw", Hs.PLCtor dataCtorName []],
                          value = Hs.EApp (eStd_ "pure") [Hs.ETup []]
                        }
                  else
                    Hs.IdValue
                      Hs.DfValue
                        { name = "marshalInto",
                          params =
                            [Hs.PVar "raw_", Hs.PLRecordWildCard dataCtorName],
                          value =
                            Hs.EDo
                              (map (Hs.DoE . uncurry emitMarshalField) fields)
                              ( if hasUnion
                                  then
                                    Hs.EApp
                                      (egName ["C"] "marshalInto")
                                      [ Hs.EApp (egName ["GH"] "structUnion") [euName "raw_"],
                                        euName "union'"
                                      ]
                                  else Hs.EApp (eStd_ "pure") [Hs.ETup []]
                              )
                        }
              ]
          }
      ]
defineMarshal typeName typeParams AO.ParsedUnion {variants} =
  let tVars = toTVars typeParams
      typ = Hs.TApp (tgName ["GH"] "Which") [Hs.TApp (Hs.TLName typeName) tVars]
   in [ Hs.DcInstance
          { ctx = paramsContext tVars,
            typ =
              Hs.TApp
                (tgName ["C"] "Marshal")
                [typ, Hs.TApp (tgName ["C"] "Parsed") [typ]],
            defs =
              [ Hs.IdValue
                  Hs.DfValue
                    { name = "marshalInto",
                      params = [Hs.PVar "raw_", Hs.PVar "parsed_"],
                      value =
                        Hs.ECase (Hs.EVar "parsed_") $
                          [ ( Hs.PLCtor (Name.mkSub typeName name) $
                                case fieldLocType of
                                  C.VoidField -> []
                                  _ -> [Hs.PVar "arg_"],
                              emitMarshalVariant name fieldLocType
                            )
                            | (name, fieldLocType) <- variants
                          ]
                            ++ [ ( Hs.PLCtor (unknownVariant typeName) [Hs.PVar "tag_"],
                                   Hs.EApp
                                     (egName ["GH"] "encodeField")
                                     [ egName ["GH"] "unionField",
                                       Hs.EVar "tag_",
                                       unionStruct (euName "raw_")
                                     ]
                                 )
                               ]
                    }
              ]
          }
      ]

defineInterfaceServer thisMod typeName typeParams methods supers =
  let tVars = toTVars typeParams
      typ = Hs.TApp (Hs.TLName typeName) tVars
      clsName = Name.mkSub typeName "server_"
   in [ Hs.DcInstance
          { ctx = paramsContext tVars,
            typ = Hs.TApp (tgName ["GH"] "Export") [typ],
            defs =
              [ Hs.IdType $ Hs.TypeAlias "Server" [typ] $ Hs.TApp (Hs.TLName clsName) tVars,
                Hs.IdValue
                  Hs.DfValue
                    { name = "methodHandlerTree",
                      params = [Hs.PVar "_", Hs.PVar "s_"],
                      value =
                        Hs.EApp
                          (egName ["GH"] "MethodHandlerTree")
                          [ Hs.ETypeApp (egName ["C"] "typeId") [typ],
                            Hs.EList
                              [ Hs.EApp
                                  (egName ["GH"] "toUntypedMethodHandler")
                                  [ Hs.EApp
                                      ( Hs.ETypeApp
                                          (Hs.EVar (Name.renderUnQ (Name.valueName (Name.mkSub typeName methodName))))
                                          tVars
                                      )
                                      [Hs.EVar "s_"]
                                  ]
                                | AO.MethodInfo {methodName} <- methods
                              ],
                            Hs.EList
                              [ Hs.EApp
                                  (egName ["GH"] "methodHandlerTree")
                                  [ Hs.ETypeApp
                                      (egName ["GH"] "Proxy")
                                      [typeToType thisMod $ C.PtrType $ C.PtrInterface super],
                                    Hs.EVar "s_"
                                  ]
                                | super <- supers
                              ]
                          ]
                    }
              ]
          },
        Hs.DcClass
          { ctx =
              [ Hs.TApp
                  (tgName ["GH"] "Server")
                  [ typeToType thisMod $ C.PtrType $ C.PtrInterface super,
                    Hs.TVar "s_"
                  ]
                | super <- supers
              ],
            name = clsName,
            params = map (Name.UnQ . Name.typeVarName) typeParams ++ ["s_"],
            funDeps = [],
            decls =
              Hs.CdMinimal
                [ mkMethodName typeName methodName
                  | AO.MethodInfo {methodName} <- methods
                ]
                : concatMap (defineIfaceClassMethod thisMod typeName) methods
          }
      ]

defineIfaceClassMethod thisMod typeName AO.MethodInfo {methodName, paramType, resultType} =
  let mkType t = typeToType thisMod (C.CompositeType t)
      name = mkMethodName typeName methodName
   in [ Hs.CdValueDecl
          name
          ( Hs.TFn
              [ Hs.TVar "s_",
                Hs.TApp
                  (tgName ["GH"] "MethodHandler")
                  [ mkType paramType,
                    mkType resultType
                  ]
              ]
          ),
        Hs.CdValueDef
          Hs.DfValue
            { name,
              params = [Hs.PVar "_"],
              value = egName ["GH"] "methodUnimplemented"
            }
      ]

mkMethodName typeName methodName =
  Name.valueName (Name.mkSub typeName methodName)

emitMarshalField :: Name.UnQ -> C.FieldLocType AO.Brand Name.CapnpQ -> Hs.Exp
emitMarshalField name (C.HereField _) =
  Hs.EDo
    [ Hs.DoBind "group_" $
        Hs.EApp
          (egName ["GH"] "readField")
          [Hs.ELabel name, Hs.EVar "raw_"]
    ]
    (Hs.EApp (egName ["C"] "marshalInto") [Hs.EVar "group_", euName name])
emitMarshalField name _ =
  Hs.EApp
    (egName ["GH"] "encodeField")
    [ Hs.ELabel name,
      euName name,
      euName "raw_"
    ]

emitMarshalVariant :: Name.UnQ -> C.FieldLocType AO.Brand Name.CapnpQ -> Hs.Exp
emitMarshalVariant name (C.HereField _) =
  Hs.EDo
    [ Hs.DoBind "rawGroup_" $
        Hs.EApp
          (egName ["GH"] "initVariant")
          [ Hs.ELabel name,
            unionStruct (euName "raw_")
          ]
    ]
    (Hs.EApp (egName ["C"] "marshalInto") [euName "rawGroup_", euName "arg_"])
emitMarshalVariant name C.VoidField =
  Hs.EApp
    (egName ["GH"] "encodeVariant")
    [ Hs.ELabel name,
      Hs.ETup [],
      unionStruct (euName "raw_")
    ]
emitMarshalVariant name _ =
  Hs.EApp
    (egName ["GH"] "encodeVariant")
    [ Hs.ELabel name,
      euName "arg_",
      unionStruct (euName "raw_")
    ]

unionStruct :: Hs.Exp -> Hs.Exp
unionStruct e = Hs.EApp (egName ["GH"] "unionStruct") [e]