
module Agda.Compiler.MAlonzo.Compiler where

import Control.Arrow (second)
import Control.DeepSeq
import Control.Monad
import Control.Monad.Except   ( throwError )
import Control.Monad.IO.Class ( MonadIO(..) )
import Control.Monad.Reader   ( MonadReader(..), asks, ReaderT, runReaderT, withReaderT)
import Control.Monad.Trans    ( lift )
import Control.Monad.Writer   ( MonadWriter(..), WriterT, runWriterT )

import qualified Data.HashSet as HashSet
import qualified Data.List as List
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Monoid (Monoid, mempty, mappend)
import Data.Semigroup ((<>))

import GHC.Generics (Generic)

import qualified Agda.Utils.Haskell.Syntax as HS

import System.Directory (createDirectoryIfMissing)
import System.Environment (setEnv)
import System.FilePath hiding (normalise)
import System.IO (utf8)

import Agda.Compiler.CallCompiler
import Agda.Compiler.Common
import Agda.Compiler.MAlonzo.Coerce
import Agda.Compiler.MAlonzo.Misc
import Agda.Compiler.MAlonzo.Pretty
import Agda.Compiler.MAlonzo.Primitives
import Agda.Compiler.MAlonzo.HaskellTypes
import Agda.Compiler.MAlonzo.Pragmas
import Agda.Compiler.MAlonzo.Strict
import Agda.Compiler.ToTreeless
import Agda.Compiler.Treeless.Unused
import Agda.Compiler.Treeless.Erase
import Agda.Compiler.Backend

import Agda.Interaction.Imports
import Agda.Interaction.Options

import Agda.Syntax.Common
import qualified Agda.Syntax.Abstract.Name as A
import Agda.Syntax.Internal as I
import Agda.Syntax.Internal.Names (namesIn)
import qualified Agda.Syntax.Treeless as T
import Agda.Syntax.Literal

import Agda.TypeChecking.Datatypes
import Agda.TypeChecking.Primitive (getBuiltinName)
import Agda.TypeChecking.Reduce
import Agda.TypeChecking.Pretty hiding ((<>))
import Agda.TypeChecking.Substitute
import Agda.TypeChecking.Telescope
import Agda.TypeChecking.Warnings

import Agda.Utils.FileName (isNewerThan)
import Agda.Utils.Function
import Agda.Utils.Functor
import Agda.Utils.Float
import Agda.Utils.IO.Directory
import Agda.Utils.Lens
import Agda.Utils.List
import Agda.Utils.Maybe
import Agda.Utils.Monad
import Agda.Utils.Pretty (prettyShow, render)
import Agda.Utils.Singleton
import qualified Agda.Utils.IO.UTF8 as UTF8
import Agda.Utils.String

import Paths_Agda

import Agda.Utils.Impossible

-- The backend callbacks --------------------------------------------------

ghcBackend :: Backend
ghcBackend = Backend ghcBackend'

ghcBackend' :: Backend' GHCFlags GHCEnv GHCModuleEnv GHCModule GHCDefinition
ghcBackend' = Backend'
  { backendName           = "GHC"
  , backendVersion        = Nothing
  , options               = defaultGHCFlags
  , commandLineFlags      = ghcCommandLineFlags
  , isEnabled             = flagGhcCompile
  , preCompile            = ghcPreCompile
  , postCompile           = ghcPostCompile
  , preModule             = ghcPreModule
  , postModule            = ghcPostModule
  , compileDef            = ghcCompileDef
  , scopeCheckingSuffices = False
  , mayEraseType          = ghcMayEraseType
  }

--- Command-line flags ---

data GHCFlags = GHCFlags
  { flagGhcCompile :: Bool
  , flagGhcCallGhc :: Bool
  , flagGhcBin     :: Maybe FilePath
    -- ^ Use the compiler at PATH instead of "ghc"
  , flagGhcFlags      :: [String]
  , flagGhcStrictData :: Bool
    -- ^ Make inductive constructors strict?
  , flagGhcStrict :: Bool
    -- ^ Make functions strict?
  }
  deriving Generic

instance NFData GHCFlags

defaultGHCFlags :: GHCFlags
defaultGHCFlags = GHCFlags
  { flagGhcCompile    = False
  , flagGhcCallGhc    = True
  , flagGhcBin        = Nothing
  , flagGhcFlags      = []
  , flagGhcStrictData = False
  , flagGhcStrict     = False
  }

ghcCommandLineFlags :: [OptDescr (Flag GHCFlags)]
ghcCommandLineFlags =
    [ Option ['c']  ["compile", "ghc"] (NoArg enable)
                    "compile program using the GHC backend"
    , Option []     ["ghc-dont-call-ghc"] (NoArg dontCallGHC)
                    "don't call GHC, just write the GHC Haskell files."
    , Option []     ["ghc-flag"] (ReqArg ghcFlag "GHC-FLAG")
                    "give the flag GHC-FLAG to GHC"
    , Option []     ["with-compiler"] (ReqArg withCompilerFlag "PATH")
                    "use the compiler available at PATH"
    , Option []     ["ghc-strict-data"] (NoArg strictData)
                    "make inductive constructors strict"
    , Option []     ["ghc-strict"] (NoArg strict)
                    "make functions strict"
    ]
  where
    enable      o = pure o{ flagGhcCompile    = True }
    dontCallGHC o = pure o{ flagGhcCallGhc    = False }
    ghcFlag f   o = pure o{ flagGhcFlags      = flagGhcFlags o ++ [f] }
    strictData  o = pure o{ flagGhcStrictData = True }
    strict      o = pure o{ flagGhcStrictData = True
                          , flagGhcStrict     = True
                          }

withCompilerFlag :: FilePath -> Flag GHCFlags
withCompilerFlag fp o = case flagGhcBin o of
 Nothing -> pure o { flagGhcBin = Just fp }
 Just{}  -> throwError "only one compiler path allowed"

--- Context types ---

-- | Monads that can read @GHCOptions@
class Monad m => ReadGHCOpts m where
  askGhcOpts :: m GHCOptions

instance Monad m => ReadGHCOpts (ReaderT GHCOptions m) where
  askGhcOpts = ask

instance Monad m => ReadGHCOpts (ReaderT GHCEnv m) where
  askGhcOpts = withReaderT ghcEnvOpts askGhcOpts

instance Monad m => ReadGHCOpts (ReaderT GHCModuleEnv m) where
  askGhcOpts = withReaderT ghcModEnv askGhcOpts

data GHCModule = GHCModule
  { ghcModModuleEnv :: GHCModuleEnv
  , ghcModMainFuncs :: [MainFunctionDef]
  -- ^ The `main` function definition(s), if both the module is
  --   the @IsMain@ module (root/focused) and a suitable `main`
  --   function was defined.
  }

instance Monad m => ReadGHCOpts (ReaderT GHCModule m) where
  askGhcOpts = withReaderT ghcModModuleEnv askGhcOpts

instance Monad m => ReadGHCModuleEnv (ReaderT GHCModule m) where
  askGHCModuleEnv = withReaderT ghcModModuleEnv askGHCModuleEnv

data GHCDefinition = GHCDefinition
  { ghcDefUsesFloat  :: UsesFloat
  , ghcDefDecls      :: [HS.Decl]
  , ghcDefDefinition :: Definition
  , ghcDefMainDef    :: Maybe MainFunctionDef
  , ghcDefImports    :: Set ModuleName
  }

--- Top-level compilation ---

ghcPreCompile :: GHCFlags -> TCM GHCEnv
ghcPreCompile flags = do
  cubical <- optCubical <$> pragmaOptions
  let notSupported s =
        typeError $ GenericError $
          "Compilation of code that uses " ++ s ++ " is not supported."
  case cubical of
    Nothing      -> return ()
    Just CErased -> return ()
    Just CFull   -> notSupported "--cubical"

  outDir <- compileDir
  let ghcOpts = GHCOptions
                { optGhcCallGhc    = flagGhcCallGhc flags
                , optGhcBin        = fromMaybe "ghc" (flagGhcBin flags)
                , optGhcFlags      = flagGhcFlags flags
                , optGhcCompileDir = outDir
                , optGhcStrictData = flagGhcStrictData flags
                , optGhcStrict     = flagGhcStrict flags
                }

  mbool       <- getBuiltinName builtinBool
  mtrue       <- getBuiltinName builtinTrue
  mfalse      <- getBuiltinName builtinFalse
  mlist       <- getBuiltinName builtinList
  mnil        <- getBuiltinName builtinNil
  mcons       <- getBuiltinName builtinCons
  mmaybe      <- getBuiltinName builtinMaybe
  mnothing    <- getBuiltinName builtinNothing
  mjust       <- getBuiltinName builtinJust
  mnat        <- getBuiltinName builtinNat
  minteger    <- getBuiltinName builtinInteger
  mword64     <- getBuiltinName builtinWord64
  minf        <- getBuiltinName builtinInf
  msharp      <- getBuiltinName builtinSharp
  mflat       <- getBuiltinName builtinFlat
  minterval   <- getBuiltinName builtinInterval
  mizero      <- getBuiltinName builtinIZero
  mione       <- getBuiltinName builtinIOne
  misone      <- getBuiltinName builtinIsOne
  mitisone    <- getBuiltinName builtinItIsOne
  misone1     <- getBuiltinName builtinIsOne1
  misone2     <- getBuiltinName builtinIsOne2
  misoneempty <- getBuiltinName builtinIsOneEmpty
  mpathp      <- getBuiltinName builtinPathP
  msub        <- getBuiltinName builtinSub
  msubin      <- getBuiltinName builtinSubIn
  mid         <- getBuiltinName builtinId
  mconid      <- getPrimitiveName' builtinConId

  istcbuiltin <- do
    builtins <- mapM getBuiltinName
      [ builtinAgdaTCMReturn
      , builtinAgdaTCMBind
      , builtinAgdaTCMUnify
      , builtinAgdaTCMTypeError
      , builtinAgdaTCMInferType
      , builtinAgdaTCMCheckType
      , builtinAgdaTCMNormalise
      , builtinAgdaTCMReduce
      , builtinAgdaTCMCatchError
      , builtinAgdaTCMQuoteTerm
      , builtinAgdaTCMUnquoteTerm
      , builtinAgdaTCMQuoteOmegaTerm
      , builtinAgdaTCMGetContext
      , builtinAgdaTCMExtendContext
      , builtinAgdaTCMInContext
      , builtinAgdaTCMFreshName
      , builtinAgdaTCMDeclareDef
      , builtinAgdaTCMDeclarePostulate
      , builtinAgdaTCMDefineFun
      , builtinAgdaTCMGetType
      , builtinAgdaTCMGetDefinition
      , builtinAgdaTCMBlockOnMeta
      , builtinAgdaTCMCommit
      , builtinAgdaTCMIsMacro
      , builtinAgdaTCMWithNormalisation
      , builtinAgdaTCMWithReconsParams
      , builtinAgdaTCMFormatErrorParts
      , builtinAgdaTCMDebugPrint
      , builtinAgdaTCMOnlyReduceDefs
      , builtinAgdaTCMDontReduceDefs
      , builtinAgdaTCMNoConstraints
      , builtinAgdaTCMRunSpeculative
      , builtinAgdaTCMExec
      , builtinAgdaTCMGetInstances
      ]
    return $
      flip HashSet.member $
      HashSet.fromList $
      catMaybes builtins

  return $ GHCEnv
    { ghcEnvOpts        = ghcOpts
    , ghcEnvBool        = mbool
    , ghcEnvTrue        = mtrue
    , ghcEnvFalse       = mfalse
    , ghcEnvMaybe       = mmaybe
    , ghcEnvNothing     = mnothing
    , ghcEnvJust        = mjust
    , ghcEnvList        = mlist
    , ghcEnvNil         = mnil
    , ghcEnvCons        = mcons
    , ghcEnvNat         = mnat
    , ghcEnvInteger     = minteger
    , ghcEnvWord64      = mword64
    , ghcEnvInf         = minf
    , ghcEnvSharp       = msharp
    , ghcEnvFlat        = mflat
    , ghcEnvInterval    = minterval
    , ghcEnvIZero       = mizero
    , ghcEnvIOne        = mione
    , ghcEnvIsOne       = misone
    , ghcEnvItIsOne     = mitisone
    , ghcEnvIsOne1      = misone1
    , ghcEnvIsOne2      = misone2
    , ghcEnvIsOneEmpty  = misoneempty
    , ghcEnvPathP       = mpathp
    , ghcEnvSub         = msub
    , ghcEnvSubIn       = msubin
    , ghcEnvId          = mid
    , ghcEnvConId       = mconid
    , ghcEnvIsTCBuiltin = istcbuiltin
    }

ghcPostCompile :: GHCEnv -> IsMain -> Map ModuleName GHCModule -> TCM ()
ghcPostCompile _cenv _isMain mods = do
  -- FIXME: @curMName@ and @curIF@ are evil TCM state, but there does not appear to be
  --------- another way to retrieve the compilation root ("main" module or interaction focused).
  rootModuleName <- curMName
  rootModule <- ifJust (Map.lookup rootModuleName mods) pure
                $ genericError $ "Module " <> prettyShow rootModuleName <> " was not compiled!"
  flip runReaderT rootModule $ do
    copyRTEModules
    callGHC

--- Module compilation ---

ghcPreModule
  :: GHCEnv
  -> IsMain      -- ^ Are we looking at the main module?
  -> ModuleName
  -> Maybe FilePath    -- ^ Path to the @.agdai@ file.
  -> TCM (Recompile GHCModuleEnv GHCModule)
                 -- ^ Could we confirm the existence of a main function?
ghcPreModule cenv isMain m mifile =
  (do let check = ifM uptodate noComp yesComp
      cubical <- optCubical <$> pragmaOptions
      case cubical of
        -- Code that uses --cubical is not compiled.
        Just CFull   -> noComp
        Just CErased -> check
        Nothing      -> check)
    `runReaderT` GHCModuleEnv cenv (HsModuleEnv m (isMain == IsMain))
  where
    uptodate = case mifile of
      Nothing -> pure False
      Just ifile -> liftIO =<< isNewerThan <$> curOutFile <*> pure ifile
    ifileDesc = fromMaybe "(memory)" mifile

    noComp = do
      reportSLn "compile.ghc" 2 . (++ " : no compilation is needed.") . prettyShow . A.mnameToConcrete =<< curMName
      menv <- ask
      mainDefs <- ifM curIsMainModule
                         (mainFunctionDefs <$> curIF)
                         (pure [])
      return . Skip $ GHCModule menv mainDefs

    yesComp = do
      m   <- prettyShow . A.mnameToConcrete <$> curMName
      out <- curOutFile
      reportSLn "compile.ghc" 1 $ repl [m, ifileDesc, out] "Compiling <<0>> in <<1>> to <<2>>"
      asks Recompile

ghcPostModule
  :: GHCEnv
  -> GHCModuleEnv
  -> IsMain        -- ^ Are we looking at the main module?
  -> ModuleName
  -> [GHCDefinition]   -- ^ Compiled module content.
  -> TCM GHCModule
ghcPostModule _cenv menv _isMain _moduleName ghcDefs = do
  builtinThings <- getsTC stBuiltinThings

  -- Accumulate all of the modules, definitions, declarations, etc.
  let (usedFloat, decls, defs, mainDefs, usedModules) = mconcat $
        (\(GHCDefinition useFloat' decls' def' md' imps')
         -> (useFloat', decls', [def'], maybeToList md', imps'))
        <$> ghcDefs

  let imps = mazRTEFloatImport usedFloat ++ imports builtinThings usedModules defs

  i <- curIF

  -- Get content of FOREIGN pragmas.
  let (headerPragmas, hsImps, code) = foreignHaskell i

  flip runReaderT menv $ do
    hsModuleName <- curHsMod
    writeModule $ HS.Module
      hsModuleName
      (map HS.OtherPragma headerPragmas)
      imps
      (map fakeDecl (hsImps ++ code) ++ decls)

  return $ GHCModule menv mainDefs

ghcCompileDef :: GHCEnv -> GHCModuleEnv -> IsMain -> Definition -> TCM GHCDefinition
ghcCompileDef _cenv menv _isMain def = do
  ((usesFloat, decls, mainFuncDef), (HsCompileState imps)) <-
    definition def `runHsCompileT` menv
  return $ GHCDefinition usesFloat decls def (checkedMainDef <$> mainFuncDef) imps

-- | We do not erase types that have a 'HsData' pragma.
--   This is to ensure a stable interface to third-party code.
ghcMayEraseType :: QName -> TCM Bool
ghcMayEraseType q = getHaskellPragma q <&> \case
  -- Andreas, 2019-05-09, issue #3732.
  -- We restrict this to 'HsData' since types like @Size@, @Level@
  -- should be erased although they have a 'HsType' binding to the
  -- Haskell unit type.
  Just HsData{} -> False
  _ -> True

-- Compilation ------------------------------------------------------------

imports :: BuiltinThings PrimFun -> Set ModuleName -> [Definition] -> [HS.ImportDecl]
imports builtinThings usedModules defs = hsImps ++ imps where
  hsImps :: [HS.ImportDecl]
  hsImps = [unqualRTE, decl mazRTE]

  unqualRTE :: HS.ImportDecl
  unqualRTE = HS.ImportDecl mazRTE False $ Just $
              (False, [ HS.IVar $ HS.Ident x
                      | x <- [mazCoerceName, mazErasedName, mazAnyTypeName] ++
                             map treelessPrimName rtePrims ])

  rtePrims = [T.PAdd, T.PSub, T.PMul, T.PQuot, T.PRem, T.PGeq, T.PLt, T.PEqI,
              T.PAdd64, T.PSub64, T.PMul64, T.PQuot64, T.PRem64, T.PLt64, T.PEq64,
              T.PITo64, T.P64ToI] -- Excludes T.PEqF, which is defined in MAlonzo.RTE.Float

  imps :: [HS.ImportDecl]
  imps = map decl $ uniq $ importsForPrim builtinThings defs ++ map mazMod mnames

  decl :: HS.ModuleName -> HS.ImportDecl
  decl m = HS.ImportDecl m True Nothing

  mnames :: [ModuleName]
  mnames = Set.elems usedModules

  uniq :: [HS.ModuleName] -> [HS.ModuleName]
  uniq = List.map head . List.group . List.sort

-- Should we import MAlonzo.RTE.Float
newtype UsesFloat = UsesFloat Bool deriving (Eq, Show)

pattern YesFloat :: UsesFloat
pattern YesFloat = UsesFloat True

pattern NoFloat :: UsesFloat
pattern NoFloat = UsesFloat False

instance Semigroup UsesFloat where
  UsesFloat a <> UsesFloat b = UsesFloat (a || b)

instance Monoid UsesFloat where
  mempty  = NoFloat
  mappend = (<>)

mazRTEFloatImport :: UsesFloat -> [HS.ImportDecl]
mazRTEFloatImport (UsesFloat b) = [ HS.ImportDecl mazRTEFloat True Nothing | b ]

--------------------------------------------------
-- Main compiling clauses
--------------------------------------------------

definition :: Definition -> HsCompileM (UsesFloat, [HS.Decl], Maybe CheckedMainFunctionDef)
-- ignore irrelevant definitions
{- Andreas, 2012-10-02: Invariant no longer holds
definition kit (Defn NonStrict _ _  _ _ _ _ _ _) = __IMPOSSIBLE__
-}
definition Defn{defArgInfo = info, defName = q} | not $ usableModality info = do
  reportSDoc "compile.ghc.definition" 10 $
    ("Not compiling" <+> prettyTCM q) <> "."
  return (mempty, mempty, Nothing)
definition def@Defn{defName = q, defType = ty, theDef = d} = do
  reportSDoc "compile.ghc.definition" 10 $ vcat
    [ ("Compiling" <+> prettyTCM q) <> ":"
    , nest 2 $ pretty d
    ]
  pragma <- liftTCM $ getHaskellPragma q
  env <- askGHCEnv
  let is p = Just q == p env
  typeCheckedMainDef <- checkTypeOfMain def
  let mainDecl = maybeToList $ checkedMainDecl <$> typeCheckedMainDef
  let retDecls ds = return (mempty, ds)
  (uncurry (,,typeCheckedMainDef)) . second ((mainDecl ++) . infodecl q) <$>
    case d of

      _ | Just (HsDefn r hs) <- pragma -> setCurrentRange r $
          if is ghcEnvFlat
          then genericError
                "\"COMPILE GHC\" pragmas are not allowed for the FLAT builtin."
          else do
            -- Make sure we have imports for all names mentioned in the type.
            hsty <- haskellType q
            mapM_ (`xqual` HS.Ident "_") (namesIn ty :: Set QName)

          -- Check that the function isn't INLINE (since that will make this
          -- definition pointless).
            inline <- (^. funInline) . theDef <$> getConstInfo q
            when inline $ warning $ UselessInline q

            retDecls $ fbWithType hsty (fakeExp hs)

      -- Compiling Bool
      Datatype{} | is ghcEnvBool -> do
        _ <- sequence_ [primTrue, primFalse] -- Just to get the proper error for missing TRUE/FALSE
        let d = dname q
        Just true  <- getBuiltinName builtinTrue
        Just false <- getBuiltinName builtinFalse
        cs <- mapM (compiledcondecl Nothing) [false, true]
        retDecls $ [ compiledTypeSynonym q "Bool" 0
                   , HS.FunBind [HS.Match d [] (HS.UnGuardedRhs HS.unit_con) emptyBinds] ] ++
                   cs

      -- Compiling List
      Datatype{ dataPars = np } | is ghcEnvList -> do
        _ <- sequence_ [primNil, primCons] -- Just to get the proper error for missing NIL/CONS
        caseMaybe pragma (return ()) $ \ p -> setCurrentRange p $ warning . GenericWarning =<< do
          fsep $ pwords "Ignoring GHC pragma for builtin lists; they always compile to Haskell lists."
        let d = dname q
            t = unqhname TypeK q
        Just nil  <- getBuiltinName builtinNil
        Just cons <- getBuiltinName builtinCons
        let vars f n = map (f . ihname A) [0 .. n - 1]
        cs <- mapM (compiledcondecl Nothing) [nil, cons]
        retDecls $ [ HS.TypeDecl t (vars HS.UnkindedVar (np - 1)) (HS.FakeType "[]")
                   , HS.FunBind [HS.Match d (vars HS.PVar np) (HS.UnGuardedRhs HS.unit_con) emptyBinds] ] ++
                   cs

      -- Compiling Maybe
      Datatype{ dataPars = np } | is ghcEnvMaybe -> do
        _ <- sequence_ [primNothing, primJust] -- Just to get the proper error for missing NOTHING/JUST
        caseMaybe pragma (return ()) $ \ p -> setCurrentRange p $ warning . GenericWarning =<< do
          fsep $ pwords "Ignoring GHC pragma for builtin maybe; they always compile to Haskell lists."
        let d = dname q
            t = unqhname TypeK q
        Just nothing <- getBuiltinName builtinNothing
        Just just    <- getBuiltinName builtinJust
        let vars f n = map (f . ihname A) [0 .. n - 1]
        cs <- mapM (compiledcondecl Nothing) [nothing, just]
        retDecls $ [ HS.TypeDecl t (vars HS.UnkindedVar (np - 1)) (HS.FakeType "Maybe")
                   , HS.FunBind [HS.Match d (vars HS.PVar np) (HS.UnGuardedRhs HS.unit_con) emptyBinds] ] ++
                   cs

      -- Compiling Inf
      _ | is ghcEnvInf -> do
        _ <- primSharp -- To get a proper error for missing SHARP.
        Just sharp <- getBuiltinName builtinSharp
        sharpC     <- (compiledcondecl Nothing) sharp
        let d   = dname q
            err = "No term-level implementation of the INFINITY builtin."
        retDecls $ [ compiledTypeSynonym q "MAlonzo.RTE.Infinity" 2
                   , HS.FunBind [HS.Match d [HS.PVar (ihname A 0)]
                       (HS.UnGuardedRhs (HS.FakeExp ("error " ++ show err)))
                       emptyBinds]
                   , sharpC
                   ]

      -- The interval is compiled as the type of booleans: 0 is
      -- compiled as False and 1 as True.
      Axiom{} | is ghcEnvInterval -> do
        _       <- sequence_ [primIZero, primIOne]
        Just i0 <- getBuiltinName builtinIZero
        Just i1 <- getBuiltinName builtinIOne
        cs      <- mapM (compiledcondecl (Just 0)) [i0, i1]
        retDecls $
          [ compiledTypeSynonym q "Bool" 0
          , HS.FunBind
              [HS.Match (dname q) []
                 (HS.UnGuardedRhs HS.unit_con) emptyBinds]
          ] ++
          cs

      -- IsOne is compiled as the constant function to the unit type.
      -- Partial/PartialP are compiled as functions from the unit type
      -- to the underlying type.
      Axiom{} | is ghcEnvIsOne -> do
        retDecls $
          [ HS.TypeDecl (unqhname TypeK q) [HS.UnkindedVar (ihname A 0)]
              (HS.FakeType "()")
          , HS.FunBind
              [HS.Match (dname q) []
                 (HS.UnGuardedRhs HS.unit_con) emptyBinds]
          ]

      -- itIsOne.
      Axiom{} | is ghcEnvItIsOne -> do
        retDecls $
          [ HS.FunBind
              [HS.Match (dname q) []
                 (HS.UnGuardedRhs HS.unit_con) emptyBinds]
          ]

      -- IsOne1/IsOne2.
      Axiom{} | is ghcEnvIsOne1 || is ghcEnvIsOne2 -> do
        retDecls $
          [ HS.FunBind
              [HS.Match (dname q) []
                 (HS.UnGuardedRhs (HS.FakeExp "\\_ _ _ -> ()"))
                 emptyBinds]
          ]

      -- isOneEmpty.
      Axiom{} | is ghcEnvIsOneEmpty -> do
        retDecls $
          [ HS.FunBind
              [HS.Match (dname q) []
                 (HS.UnGuardedRhs (HS.FakeExp "\\_ x _ -> x ()"))
                 emptyBinds]
          ]

      -- PathP is compiled as a function from the interval (booleans)
      -- to the underlying type.
      Axiom{} | is ghcEnvPathP -> do
        _        <- sequence_ [primInterval]
        Just int <- getBuiltinName builtinInterval
        int      <- xhqn TypeK int
        retDecls $
          [ HS.TypeDecl (unqhname TypeK q)
              [HS.UnkindedVar (ihname A i) | i <- [0..3]]
              (HS.TyFun (HS.TyCon int) mazAnyType)
          , HS.FunBind
              [HS.Match (dname q) []
                 (HS.UnGuardedRhs (HS.FakeExp "\\_ _ _ _ -> ()"))
                 emptyBinds]
          ]

      -- Sub is compiled as the underlying type.
      Axiom{} | is ghcEnvSub -> do
        retDecls $
          [ HS.TypeDecl (unqhname TypeK q)
              [HS.UnkindedVar (ihname A i) | i <- [0..3]]
              (HS.TyVar (ihname A 1))
          , HS.FunBind
              [HS.Match (dname q) []
                 (HS.UnGuardedRhs (HS.FakeExp "\\_ _ _ _ -> ()"))
                 emptyBinds]
          ]

      -- subIn.
      Axiom{} | is ghcEnvSubIn -> do
        retDecls $
          [ HS.FunBind
              [HS.Match (dname q) []
                 (HS.UnGuardedRhs (HS.FakeExp "\\_ _ _ x -> x"))
                 emptyBinds]
          ]

      -- Id x y is compiled as a pair of a boolean and whatever
      -- Path x y is compiled to.
      Datatype{} | is ghcEnvId -> do
        _        <- sequence_ [primInterval]
        Just int <- getBuiltinName builtinInterval
        int      <- xhqn TypeK int
        -- re  #3733: implement reflId
        retDecls $
          [ HS.TypeDecl (unqhname TypeK q)
              [] -- [HS.UnkindedVar (ihname A i) | i <- [0..3]]
              (HS.TyApp (HS.FakeType "(,) Bool")
                 (HS.TyFun (HS.TyCon int) mazAnyType))
          , HS.FunBind
              [HS.Match (dname q) []
                 (HS.UnGuardedRhs (HS.FakeExp "\\_ _ _ _ -> ()"))
                 emptyBinds]
          ]

      -- conid.
      Primitive{} | is ghcEnvConId -> do
        strict <- optGhcStrictData <$> askGhcOpts
        let var = (if strict then HS.PBangPat else id) . HS.PVar
        retDecls $
          [ HS.FunBind
              [HS.Match (dname q)
                 [ var (ihname A i) | i <- [0..1] ]
                 (HS.UnGuardedRhs $
                  HS.App (HS.App (HS.FakeExp "(,)")
                            (HS.Var (HS.UnQual (ihname A 0))))
                    (HS.Var (HS.UnQual (ihname A 1))))
                 emptyBinds]
          ]

      -- TC builtins are compiled to erased, which is an ∞-ary
      -- function.
      Axiom{} | ghcEnvIsTCBuiltin env q -> do
        retDecls $
          [ HS.FunBind
              [HS.Match (dname q) []
                 (HS.UnGuardedRhs (HS.FakeExp mazErasedName))
                 emptyBinds]
          ]

      DataOrRecSig{} -> __IMPOSSIBLE__

      Axiom{} -> do
        ar <- liftTCM $ typeArity ty
        retDecls $ [ compiledTypeSynonym q ty ar | Just (HsType r ty) <- [pragma] ] ++
                   fb axiomErr
      Primitive{ primName = s } -> (mempty,) . fb <$> (liftTCM . primBody) s

      PrimitiveSort{ primName = s } -> retDecls []

      Function{} -> function pragma $ functionViaTreeless q

      Datatype{ dataPars = np, dataIxs = ni, dataClause = cl
              , dataPathCons = pcs
              } | Just hsdata@(HsData r ty hsCons) <- pragma ->
        setCurrentRange r $ do
        reportSDoc "compile.ghc.definition" 40 $ hsep $
          [ "Compiling data type with COMPILE pragma ...", pretty hsdata ]
        liftTCM $ computeErasedConstructorArgs q
        cs <- liftTCM $ getNotErasedConstructors q
        ccscov <- constructorCoverageCode q (np + ni) cs ty hsCons
        cds <- mapM (compiledcondecl Nothing) cs
        let result = concat $
              [ tvaldecl q Inductive (np + ni) [] (Just __IMPOSSIBLE__)
              , [ compiledTypeSynonym q ty np ]
              , cds
              , ccscov
              ]
        retDecls result
      Datatype{ dataPars = np, dataIxs = ni, dataClause = cl
              , dataPathCons = pcs
              } -> do
        liftTCM $ computeErasedConstructorArgs q
        cs <- liftTCM $ getNotErasedConstructors q
        cds <- mapM (flip condecl Inductive) cs
        retDecls $ tvaldecl q Inductive (np + ni) cds cl
      Constructor{} -> retDecls []
      GeneralizableVar{} -> retDecls []
      Record{ recPars = np, recClause = cl, recConHead = con,
              recInduction = ind } ->
        let -- Non-recursive record types are treated as being
            -- inductive.
            inductionKind = fromMaybe Inductive ind
        in case pragma of
          Just (HsData r ty hsCons) -> setCurrentRange r $ do
            let cs = [conName con]
            liftTCM $ computeErasedConstructorArgs q
            ccscov <- constructorCoverageCode q np cs ty hsCons
            cds <- mapM (compiledcondecl Nothing) cs
            retDecls $
              tvaldecl q inductionKind np [] (Just __IMPOSSIBLE__) ++
              [compiledTypeSynonym q ty np] ++ cds ++ ccscov
          _ -> do
            liftTCM $ computeErasedConstructorArgs q
            cd <- condecl (conName con) inductionKind
            retDecls $ tvaldecl q inductionKind (I.arity ty) [cd] cl
      AbstractDefn{} -> __IMPOSSIBLE__
  where
  function :: Maybe HaskellPragma -> HsCompileM (UsesFloat, [HS.Decl]) -> HsCompileM (UsesFloat, [HS.Decl])
  function mhe fun = do
    (imp, ccls) <- fun
    case mhe of
      Just (HsExport r name) -> setCurrentRange r $ do
        env <- askGHCEnv
        if Just q == ghcEnvFlat env
        then genericError
              "\"COMPILE GHC as\" pragmas are not allowed for the FLAT builtin."
        else do
          t <- setCurrentRange r $ haskellType q
          let tsig :: HS.Decl
              tsig = HS.TypeSig [HS.Ident name] t

              def :: HS.Decl
              def = HS.FunBind [HS.Match (HS.Ident name) [] (HS.UnGuardedRhs (hsCoerce $ hsVarUQ $ dname q)) emptyBinds]
          return (imp, [tsig,def] ++ ccls)
      _ -> return (imp, ccls)

  functionViaTreeless :: QName -> HsCompileM (UsesFloat, [HS.Decl])
  functionViaTreeless q = do
    strict <- optGhcStrict <$> askGhcOpts
    let eval = if strict then EagerEvaluation else LazyEvaluation
    caseMaybeM (liftTCM $ toTreeless eval q) (pure mempty) $ \ treeless -> do

      used <- fromMaybe [] <$> getCompiledArgUse q
      let dostrip = ArgUnused `elem` used

      -- Compute the type approximation
      def <- getConstInfo q
      (argTypes0, resType) <- hsTelApproximation $ defType def
      let pars = case theDef def of
                   Function{ funProjection = Just Projection{ projIndex = i } } | i > 0 -> i - 1
                   _ -> 0
          argTypes  = drop pars argTypes0
          argTypesS = filterUsed used argTypes

      (e, useFloat) <- if dostrip then closedTerm (stripUnusedArguments used treeless)
                                  else closedTerm treeless
      let (ps, b) = lamView e
          lamView e =
            case e of
              HS.Lambda ps b -> (ps, b)
              b              -> ([], b)

          tydecl  f ts t = HS.TypeSig [f] (foldr HS.TyFun t ts)
          funbind f ps b = HS.FunBind [HS.Match f ps (HS.UnGuardedRhs b) emptyBinds]
          tyfunbind f ts t ps b =
            let ts' = ts ++ (replicate (length ps - length ts) mazAnyType)
            in [tydecl f ts' t, funbind f ps b]

      -- The definition of the non-stripped function
      (ps0, _) <- lamView <$> closedTerm_ (foldr ($) T.TErased $ replicate (length used) T.TLam)
      let b0 = foldl HS.App (hsVarUQ $ duname q) [ hsVarUQ x | (~(HS.PVar x), ArgUsed) <- zip ps0 used ]
          ps0' = zipWith (\p u -> case u of
                                    ArgUsed   -> p
                                    ArgUnused -> HS.PIrrPat p)
                   ps0 used

      return (useFloat,
              if dostrip
                then tyfunbind (dname q) argTypes resType ps0' b0 ++
                     tyfunbind (duname q) argTypesS resType ps b
                else tyfunbind (dname q) argTypes resType ps b)

  fbWithType :: HS.Type -> HS.Exp -> [HS.Decl]
  fbWithType ty e =
    HS.TypeSig [dname q] ty : fb e

  fb :: HS.Exp -> [HS.Decl]
  fb e  = [HS.FunBind [HS.Match (dname q) []
                                (HS.UnGuardedRhs e) emptyBinds]]

  axiomErr :: HS.Exp
  axiomErr = rtmError $ Text.pack $ "postulate evaluated: " ++ prettyShow q

constructorCoverageCode :: QName -> Int -> [QName] -> HaskellType -> [HaskellCode] -> HsCompileM [HS.Decl]
constructorCoverageCode q np cs hsTy hsCons = do
  liftTCM $ checkConstructorCount q cs hsCons
  ifM (liftTCM $ noCheckCover q) (return []) $ do
    ccs <- List.concat <$> zipWithM checkConstructorType cs hsCons
    cov <- liftTCM $ checkCover q hsTy np cs hsCons
    return $ ccs ++ cov

-- | Environment for naming of local variables.
--   Invariant: @reverse ccCxt ++ ccNameSupply@
data CCEnv = CCEnv
  { _ccNameSupply :: NameSupply  -- ^ Supply of fresh names
  , _ccContext    :: CCContext   -- ^ Names currently in scope
  }

type NameSupply = [HS.Name]
type CCContext  = [HS.Name]

ccNameSupply :: Lens' NameSupply CCEnv
ccNameSupply f e =  (\ ns' -> e { _ccNameSupply = ns' }) <$> f (_ccNameSupply e)

ccContext :: Lens' CCContext CCEnv
ccContext f e = (\ cxt -> e { _ccContext = cxt }) <$> f (_ccContext e)

-- | Initial environment for expression generation.
initCCEnv :: CCEnv
initCCEnv = CCEnv
  { _ccNameSupply = map (ihname V) [0..]  -- DON'T CHANGE THESE NAMES!
  , _ccContext    = []
  }

-- | Term variables are de Bruijn indices.
lookupIndex :: Int -> CCContext -> HS.Name
lookupIndex i xs = fromMaybe __IMPOSSIBLE__ $ xs !!! i

-- | Constructor coverage monad transformer
type CCT m = ReaderT CCEnv (WriterT UsesFloat (HsCompileT m))

-- | Constructor coverage monad
type CC = CCT TCM

liftCC :: Monad m => HsCompileT m a -> CCT m a
liftCC = lift . lift

freshNames :: Monad m => Int -> ([HS.Name] -> CCT m a) -> CCT m a
freshNames n _ | n < 0 = __IMPOSSIBLE__
freshNames n cont = do
  (xs, rest) <- splitAt n <$> view ccNameSupply
  local (over ccNameSupply (const rest)) $ cont xs

-- | Introduce n variables into the context.
intros :: Monad m => Int -> ([HS.Name] -> CCT m a) -> CCT m a
intros n cont = freshNames n $ \xs ->
  local (over ccContext (reverse xs ++)) $ cont xs

checkConstructorType :: QName -> HaskellCode -> HsCompileM [HS.Decl]
checkConstructorType q hs = do
  ty <- haskellType q
  return [ HS.TypeSig [unqhname CheckK q] ty
         , HS.FunBind [HS.Match (unqhname CheckK q) []
                                (HS.UnGuardedRhs $ fakeExp hs) emptyBinds]
         ]

checkCover :: HasConstInfo m => QName -> HaskellType -> Nat -> [QName] -> [HaskellCode] -> m [HS.Decl]
checkCover q ty n cs hsCons = do
  let tvs = [ "a" ++ show i | i <- [1..n] ]
      makeClause c hsc = do
        a <- erasedArity c
        let pat = HS.PApp (HS.UnQual $ HS.Ident hsc) $ replicate a HS.PWildCard
        return $ HS.Alt pat (HS.UnGuardedRhs $ HS.unit_con) emptyBinds

  cs <- zipWithM makeClause cs hsCons
  let rhs = HS.Case (HS.Var $ HS.UnQual $ HS.Ident "x") cs

  return [ HS.TypeSig [unqhname CoverK q] $ fakeType $ unwords (ty : tvs) ++ " -> ()"
         , HS.FunBind [HS.Match (unqhname CoverK q) [HS.PVar $ HS.Ident "x"]
                                (HS.UnGuardedRhs rhs) emptyBinds]
         ]

closedTerm_ :: T.TTerm -> HsCompileM HS.Exp
closedTerm_ t = fst <$> closedTerm t

closedTerm :: T.TTerm -> HsCompileM (HS.Exp, UsesFloat)
closedTerm v = do
  v <- liftTCM $ addCoercions v
  runWriterT (term v `runReaderT` initCCEnv)

-- Translate case on bool to if
mkIf :: T.TTerm -> CC T.TTerm
mkIf t@(TCase e _ d [TACon c1 0 b1, TACon c2 0 b2]) | T.isUnreachable d = do
  env <- liftCC askGHCEnv
  let isTrue  c = Just c == ghcEnvTrue  env
      isFalse c = Just c == ghcEnvFalse env
  if | isTrue c1, isFalse c2 -> return $ T.tIfThenElse (TCoerce $ TVar e) b1 b2
     | isTrue c2, isFalse c1 -> return $ T.tIfThenElse (TCoerce $ TVar e) b2 b1
     | otherwise             -> return t
mkIf t = return t

-- | Extract Agda term to Haskell expression.
--   Erased arguments are extracted as @()@.
--   Types are extracted as @()@.
term :: T.TTerm -> CC HS.Exp
term tm0 = mkIf tm0 >>= \ tm0 -> do
  let ((hasCoerce, t), ts) = coerceAppView tm0
  -- let (t0, ts)       = tAppView tm0
  -- let (hasCoerce, t) = coerceView t0
  let coe            = applyWhen hasCoerce hsCoerce
  case (t, ts) of
    (T.TPrim T.PIf, [c, x, y]) -> coe <$> do HS.If <$> term c <*> term x <*> term y

    (T.TDef f, ts) -> do
      used <- liftCC $ fromMaybe [] <$> getCompiledArgUse f
      -- #2248: no unused argument pruning for COMPILE'd functions
      isCompiled <- liftTCM $ isJust <$> getHaskellPragma f
      let given   = length ts
          needed  = length used
          missing = drop given used
      if not isCompiled && ArgUnused `elem` used
        then if ArgUnused `elem` missing then term (etaExpand (needed - given) tm0) else do
          f <- liftCC $ HS.Var <$> xhqn (FunK NoUnused) f
                                   -- use stripped function
          -- Andreas, 2019-11-07, issue #4169.
          -- Insert coercion unconditionally as erasure of arguments
          -- that are matched upon might remove the unfolding of codomain types.
          -- (Hard to explain, see test/Compiler/simple/Issue4169.)
          hsCoerce f `apps` filterUsed used ts
        else do
          f <- liftCC $ HS.Var <$> xhqn (FunK PossiblyUnused) f
                                   -- use original (non-stripped) function
          coe f `apps` ts

    (T.TCon c, ts) -> do
      erased  <- liftCC $ getErasedConArgs c
      let missing = drop (length ts) erased
          notErased = not
      if all notErased missing
        then do
                f <- liftCC $ HS.Con <$> conhqn c
                hsCoerce f `apps` [ t | (t, False) <- zip ts erased ]
        else do
                let n = length missing
                unless (n >= 1) __IMPOSSIBLE__  -- We will add at least on TLam, not getting a busy loop here.
                term $ etaExpand (length missing) tm0

    -- Other kind of application: fall back to apps.
    (t, ts) -> noApplication t >>= \ t' -> coe t' `apps` ts
  where
  apps = foldM (\ h a -> HS.App h <$> term a)
  etaExpand n t = mkTLam n $ raise n t `T.mkTApp` map T.TVar (downFrom n)

-- | Translate a non-application, non-coercion, non-constructor, non-definition term.
noApplication :: T.TTerm -> CC HS.Exp
noApplication = \case
  T.TApp{}    -> __IMPOSSIBLE__
  T.TCoerce{} -> __IMPOSSIBLE__
  T.TCon{}    -> __IMPOSSIBLE__
  T.TDef{}    -> __IMPOSSIBLE__

  T.TVar i    -> hsVarUQ . lookupIndex i <$> view ccContext
  T.TLam t    -> intros 1 $ \ [x] -> hsLambda [HS.PVar x] <$> term t

  T.TLet t1 t2 -> do
    t1' <- term t1
    intros 1 $ \[x] -> do
      hsLet x t1' <$> term t2

  T.TCase sc ct def alts -> do
    sc'   <- term $ T.TVar sc
    alts' <- traverse (alt sc) alts
    def'  <- term def
    let defAlt = HS.Alt HS.PWildCard (HS.UnGuardedRhs def') emptyBinds
    return $ HS.Case (hsCoerce sc') (alts' ++ [defAlt])

  T.TLit l    -> literal l
  T.TPrim p   -> return $ compilePrim p
  T.TUnit     -> return $ HS.unit_con
  T.TSort     -> return $ HS.unit_con
  T.TErased   -> return $ hsVarUQ $ HS.Ident mazErasedName
  T.TError e  -> return $ case e of
    T.TUnreachable -> rtmUnreachableError
    T.TMeta s      -> rtmHole s

hsCoerce :: HS.Exp -> HS.Exp
hsCoerce t = HS.App mazCoerce t

compilePrim :: T.TPrim -> HS.Exp
compilePrim s = HS.Var $ hsName $ treelessPrimName s

alt :: Int -> T.TAlt -> CC HS.Alt
alt sc a = do
  case a of
    T.TACon {T.aCon = c} -> do
      intros (T.aArity a) $ \ xs -> do
        erased <- liftCC $ getErasedConArgs c
        env    <- liftCC askGHCEnv
        hConNm <-
          if | Just c == ghcEnvNil env ->
               return $ HS.UnQual $ HS.Ident "[]"
             | Just c == ghcEnvCons env ->
               return $ HS.UnQual $ HS.Symbol ":"
             | otherwise -> liftCC $ conhqn c
        mkAlt (HS.PApp hConNm $ [HS.PVar x | (x, False) <- zip xs erased])
    T.TAGuard g b -> do
      g <- term g
      b <- term b
      return $ HS.Alt HS.PWildCard
                      (HS.GuardedRhss [HS.GuardedRhs [HS.Qualifier g] b])
                      emptyBinds
    T.TALit { T.aLit = LitQName q } -> mkAlt (litqnamepat q)
    T.TALit { T.aLit = l@LitFloat{}, T.aBody = b } -> do
      tell YesFloat
      l <- literal l
      mkGuarded (treelessPrimName T.PEqF) l b
    T.TALit { T.aLit = LitString s , T.aBody = b } -> mkGuarded "(==)" (litString s) b
    T.TALit {} -> mkAlt (HS.PLit $ hslit $ T.aLit a)
  where
    mkGuarded eq lit b = do
      b  <- term b
      let varName = HS.Ident "l" -- only used locally in the guard
          pv = HS.PVar varName
          v  = hsVarUQ varName
          guard =
            HS.Var (HS.UnQual (HS.Ident eq)) `HS.App`
              v `HS.App` lit
      return $ HS.Alt pv
                      (HS.GuardedRhss [HS.GuardedRhs [HS.Qualifier guard] b])
                      emptyBinds

    mkAlt :: HS.Pat -> CC HS.Alt
    mkAlt pat = do
        body' <- term $ T.aBody a
        return $ HS.Alt pat (HS.UnGuardedRhs body') emptyBinds

literal :: forall m. Monad m => Literal -> CCT m HS.Exp
literal l = case l of
  LitNat    _   -> return $ typed "Integer"
  LitWord64 _   -> return $ typed "MAlonzo.RTE.Word64"
  LitFloat  x   -> floatExp x "Double"
  LitQName  x   -> return $ litqname x
  LitString s   -> return $ litString s
  _             -> return $ l'
  where
    l'    = HS.Lit $ hslit l
    typed = HS.ExpTypeSig l' . HS.TyCon . rtmQual

    -- ASR (2016-09-14): See Issue #2169.
    -- Ulf, 2016-09-28: and #2218.
    floatExp :: Double -> String -> CCT m HS.Exp
    floatExp x s
      | isPosInf  x = rte "positiveInfinity"
      | isNegInf  x = rte "negativeInfinity"
      | isNegZero x = rte "negativeZero"
      | isNaN     x = rte "nan"
      | otherwise   = return $ typed s
      where
        rte s = do tell YesFloat; return $ HS.Var $ HS.Qual mazRTEFloat $ HS.Ident s

hslit :: Literal -> HS.Literal
hslit = \case
  LitNat    x -> HS.Int    x
  LitWord64 x -> HS.Int    (fromIntegral x)
  LitFloat  x -> HS.Frac   (toRational x)
  LitChar   x -> HS.Char   x
  LitQName  x -> __IMPOSSIBLE__
  LitString _ -> __IMPOSSIBLE__
  LitMeta{}   -> __IMPOSSIBLE__

litString :: Text -> HS.Exp
litString s = HS.Ann (HS.Lit (HS.String s))
                     (HS.TyCon (HS.Qual (HS.ModuleName "Data.Text") (HS.Ident "Text")))

litqname :: QName -> HS.Exp
litqname x =
  rteCon "QName" `apps`
  [ hsTypedInt n
  , hsTypedInt m
  , HS.Lit $ HS.String $ Text.pack $ prettyShow x
  , rteCon "Fixity" `apps`
    [ litAssoc (fixityAssoc fx)
    , litPrec  (fixityLevel fx)
    ]
  ]
  where
    apps = foldl HS.App
    rteCon name = HS.Con $ HS.Qual mazRTE $ HS.Ident name
    NameId n (ModuleNameHash m) = nameId $ qnameName x
    fx = theFixity $ nameFixity $ qnameName x

    litAssoc NonAssoc   = rteCon "NonAssoc"
    litAssoc LeftAssoc  = rteCon "LeftAssoc"
    litAssoc RightAssoc = rteCon "RightAssoc"

    litPrec Unrelated   = rteCon "Unrelated"
    litPrec (Related l) = rteCon "Related" `HS.App` hsTypedDouble l

litqnamepat :: QName -> HS.Pat
litqnamepat x =
  HS.PApp (HS.Qual mazRTE $ HS.Ident "QName")
          [ HS.PLit (HS.Int $ fromIntegral n)
          , HS.PLit (HS.Int $ fromIntegral m)
          , HS.PWildCard, HS.PWildCard ]
  where
    NameId n (ModuleNameHash m) = nameId $ qnameName x

condecl :: QName -> Induction -> HsCompileM HS.ConDecl
condecl q _ind = do
  opts <- askGhcOpts
  def <- getConstInfo q
  let Constructor{ conPars = np, conErased = erased } = theDef def
  (argTypes0, _) <- hsTelApproximation (defType def)
  let strict     = if conInd (theDef def) == Inductive &&
                      optGhcStrictData opts
                   then HS.Strict
                   else HS.Lazy
      argTypes   = [ (Just strict, t)
                   | (t, False) <- zip (drop np argTypes0)
                                       (fromMaybe [] erased ++ repeat False)
                   ]
  return $ HS.ConDecl (unqhname ConK q) argTypes

compiledcondecl
  :: Maybe Nat  -- ^ The constructor's arity (after erasure).
  -> QName -> HsCompileM HS.Decl
compiledcondecl mar q = do
  ar <- case mar of
    Nothing -> liftTCM $ erasedArity q
    Just ar -> return ar
  hsCon <- fromMaybe __IMPOSSIBLE__ <$> getHaskellConstructor q
  let patVars = map (HS.PVar . ihname A) [0 .. ar - 1]
  return $ HS.PatSyn (HS.PApp (HS.UnQual $ unqhname ConK q) patVars)
             (HS.PApp (hsName hsCon) patVars)

compiledTypeSynonym :: QName -> String -> Nat -> HS.Decl
compiledTypeSynonym q hsT arity =
  HS.TypeDecl (unqhname TypeK q) (map HS.UnkindedVar vs)
              (foldl HS.TyApp (HS.FakeType hsT) $ map HS.TyVar vs)
  where
    vs = [ ihname A i | i <- [0 .. arity - 1]]

tvaldecl :: QName
         -> Induction
            -- ^ Is the type inductive or coinductive?
         -> Nat -> [HS.ConDecl] -> Maybe Clause -> [HS.Decl]
tvaldecl q ind npar cds cl =
  HS.FunBind [HS.Match vn pvs (HS.UnGuardedRhs HS.unit_con) emptyBinds] :
  maybe [HS.DataDecl kind tn [] cds' []]
        (const []) cl
  where
  (tn, vn) = (unqhname TypeK q, dname q)
  pvs = [ HS.PVar $ ihname A i | i <- [0 .. npar - 1]]

  -- Inductive data types consisting of a single constructor with a
  -- single argument are translated into newtypes.
  (kind, cds') = case (ind, cds) of
    (Inductive, [HS.ConDecl c [(_, t)]]) ->
      (HS.NewType, [HS.ConDecl c [(Nothing, t)]])
      -- The strictness annotations are removed for newtype
      -- constructors.
    _ -> (HS.DataType, cds)

infodecl :: QName -> [HS.Decl] -> [HS.Decl]
infodecl _ [] = []
infodecl q ds = HS.Comment (prettyShow q) : ds

--------------------------------------------------
-- Writing out a haskell module
--------------------------------------------------

type MonadGHCIO m = (MonadIO m, ReadGHCOpts m)

copyRTEModules :: MonadGHCIO m => m ()
copyRTEModules = do
  dataDir <- liftIO getDataDir
  let srcDir = dataDir </> "MAlonzo" </> "src"
  dstDir <- optGhcCompileDir <$> askGhcOpts
  liftIO $ copyDirContent srcDir dstDir

writeModule :: MonadGHCIO m => HS.Module -> m ()
writeModule (HS.Module m ps imp ds) = do
  -- Note that GHC assumes that sources use ASCII or UTF-8.
  out <- snd <$> outFileAndDir m
  strict <- optGhcStrict <$> askGhcOpts
  let languagePragmas =
        List.map (HS.LanguagePragma . singleton . HS.Ident) $
          List.sort $
            [ "QualifiedDo" | strict ] ++
                -- If --ghc-strict is used, then the language extension
                -- QualifiedDo is activated. At the time of writing no
                -- code is generated that depends on this extension
                -- (except for the pragmas), but --ghc-strict is broken
                -- with at least some versions of GHC prior to version 9,
                -- and QualifiedDo was introduced with GHC 9.
            [ "BangPatterns"
            , "EmptyDataDecls"
            , "EmptyCase"
            , "ExistentialQuantification"
            , "ScopedTypeVariables"
            , "NoMonomorphismRestriction"
            , "RankNTypes"
            , "PatternSynonyms"
            , "OverloadedStrings"
            ]
  let ghcOptions =
        List.map HS.OtherPragma
          [ ""  -- to separate from LANGUAGE pragmas
          , "{-# OPTIONS_GHC -Wno-overlapping-patterns #-}"
              -- Andreas, 2022-01-26, issue #5758:
              -- Place this in generated file rather than
              -- passing it only when calling GHC from within Agda.
              -- This will silence the warning for the Agda-generated .hs
              -- files while it can be on for other .hs files in the same
              -- project.  (E.g., when using cabal/stack to compile.)
          ]
  liftIO $ UTF8.writeFile out $ (++ "\n") $ prettyPrint $
    -- TODO: It might make sense to skip bang patterns for the unused
    -- arguments of the "non-stripped" functions.
    applyWhen strict makeStrict $
    HS.Module m (concat [languagePragmas, ghcOptions, ps]) imp ds

outFileAndDir :: MonadGHCIO m => HS.ModuleName -> m (FilePath, FilePath)
outFileAndDir m = do
  mdir <- optGhcCompileDir <$> askGhcOpts
  let (fdir, fn) = splitFileName $ repldot pathSeparator $
                   prettyPrint m
  let dir = mdir </> fdir
      fp  = dir </> replaceExtension fn "hs"
  liftIO $ createDirectoryIfMissing True dir
  return (mdir, fp)
  where
  repldot c = List.map $ \ c' -> if c' == '.' then c else c'

curOutFileAndDir :: (MonadGHCIO m, ReadGHCModuleEnv m) => m (FilePath, FilePath)
curOutFileAndDir = outFileAndDir =<< curHsMod

curOutFile :: (MonadGHCIO m, ReadGHCModuleEnv m) => m FilePath
curOutFile = snd <$> curOutFileAndDir

callGHC :: ReaderT GHCModule TCM ()
callGHC = do
  opts    <- askGhcOpts
  hsmod   <- prettyPrint <$> curHsMod
  agdaMod <- curAgdaMod
  let outputName = lastWithDefault __IMPOSSIBLE__ $ mnameToList agdaMod
  (mdir, fp) <- curOutFileAndDir
  let ghcopts = optGhcFlags opts

  modIsMain <- curIsMainModule
  modHasMainFunc <- asks (not . null . ghcModMainFuncs)
  let isMain = modIsMain && modHasMainFunc  -- both need to be IsMain

  -- Warn if no main function and not --no-main
  when (modIsMain /= isMain) $
    genericWarning =<< fsep (pwords "No main function defined in" ++ [prettyTCM agdaMod <> "."] ++
                             pwords "Use --no-main to suppress this warning.")

  let overridableArgs =
        [ "-O"] ++
        (if isMain then ["-o", mdir </> prettyShow (nameConcrete outputName)] else []) ++
        [ "-Werror"]
      otherArgs       =
        [ "-i" ++ mdir] ++
        (if isMain then ["-main-is", hsmod] else []) ++
        [ fp
        , "--make"
        , "-fwarn-incomplete-patterns"
        ]
      args     = overridableArgs ++ ghcopts ++ otherArgs

  let ghcBin = optGhcBin opts

  -- Make GHC use UTF-8 when writing to stdout and stderr.
  liftIO $ setEnv "GHC_CHARENC" "UTF-8"
  -- Note: Some versions of GHC use stderr for progress reports. For
  -- those versions of GHC we don't print any progress information
  -- unless an error is encountered.
  let doCall = optGhcCallGhc opts
  liftTCM $ callCompiler doCall ghcBin args (Just utf8)
