{-# LANGUAGE CPP #-}

-- | Types used by the runtime interpreter
module GHC.Runtime.Interpreter.Types
   ( Interp(..)
   , InterpInstance(..)
   , InterpProcess (..)
   , ExtInterp (..)
   , ExtInterpStatusVar
   , ExtInterpInstance (..)
   , ExtInterpState (..)
   , InterpStatus(..)
   -- * IServ
   , IServ
   , IServConfig(..)
   -- * JSInterp
   , JSInterp
   , JSInterpExtra (..)
   , JSInterpConfig (..)
   , JSState (..)
   , NodeJsSettings (..)
   , defaultNodeJsSettings
   , WasmInterp
   , WasmInterpConfig (..)
   )
where

import GHC.Prelude
import GHC.Linker.Types

import GHCi.RemoteTypes
import GHCi.Message         ( Pipe )
import GHC.Types.Unique.FM
import GHC.Data.FastString ( FastString )
import Foreign

import GHC.Platform
import GHC.Utils.TmpFs
import GHC.Utils.Logger
import GHC.Unit.Env
import GHC.Unit.State
import GHC.Unit.Types
import GHC.StgToJS.Types
import GHC.StgToJS.Linker.Types

import Control.Concurrent
import System.Process   ( ProcessHandle, CreateProcess )
import System.IO
import GHC.Unit.Finder.Types (FinderCache, FinderOpts)

-- | Interpreter
data Interp = Interp
  { Interp -> InterpInstance
interpInstance :: !InterpInstance
      -- ^ Interpreter instance (internal, external)

  , Interp -> Loader
interpLoader   :: !Loader
      -- ^ Interpreter loader

  , Interp -> MVar (UniqFM FastString (Ptr ()))
interpLookupSymbolCache :: !(MVar (UniqFM FastString (Ptr ())))
      -- ^ LookupSymbol cache
  }

data InterpInstance
   = ExternalInterp !ExtInterp -- ^ External interpreter
#if defined(HAVE_INTERNAL_INTERPRETER)
   | InternalInterp            -- ^ Internal interpreter
#endif

data ExtInterp
  = ExtIServ !IServ
  | ExtJS !JSInterp
  | ExtWasm !WasmInterp

-- | External interpreter
--
-- The external interpreter is spawned lazily (on first use) to avoid slowing
-- down sessions that don't require it. The contents of the MVar reflects the
-- state of the interpreter (running or not).
data ExtInterpState cfg details = ExtInterpState
  { forall cfg details. ExtInterpState cfg details -> cfg
interpConfig :: !cfg
  , forall cfg details.
ExtInterpState cfg details -> ExtInterpStatusVar details
interpStatus :: !(ExtInterpStatusVar details)
  }

type ExtInterpStatusVar d = MVar (InterpStatus (ExtInterpInstance d))

type IServ    = ExtInterpState IServConfig    ()
type JSInterp = ExtInterpState JSInterpConfig JSInterpExtra
type WasmInterp = ExtInterpState WasmInterpConfig ()

data InterpProcess = InterpProcess
  { InterpProcess -> Pipe
interpPipe   :: !Pipe           -- ^ Pipe to communicate with the server
  , InterpProcess -> ProcessHandle
interpHandle :: !ProcessHandle  -- ^ Process handle of the server
  , InterpProcess -> MVar ()
interpLock   :: !(MVar ())      -- ^ Lock to prevent concurrent access to the stream
  }

-- | Status of an external interpreter
data InterpStatus inst
   = InterpPending       -- ^ Not spawned yet
   | InterpRunning !inst -- ^ Running

-- | Configuration needed to spawn an external interpreter
data IServConfig = IServConfig
  { IServConfig -> String
iservConfProgram  :: !String   -- ^ External program to run
  , IServConfig -> [String]
iservConfOpts     :: ![String] -- ^ Command-line options
  , IServConfig -> Bool
iservConfProfiled :: !Bool     -- ^ Use Profiling way
  , IServConfig -> Bool
iservConfDynamic  :: !Bool     -- ^ Use Dynamic way
  , IServConfig -> Maybe (CreateProcess -> IO ProcessHandle)
iservConfHook     :: !(Maybe (CreateProcess -> IO ProcessHandle)) -- ^ Hook
  , IServConfig -> IO ()
iservConfTrace    :: IO ()     -- ^ Trace action executed after spawn
  }

-- | Common field between native external interpreter and the JS one
data ExtInterpInstance c = ExtInterpInstance
  { forall c. ExtInterpInstance c -> InterpProcess
instProcess       :: {-# UNPACK #-} !InterpProcess
      -- ^ External interpreter process and its pipe (communication channel)

  , forall c. ExtInterpInstance c -> MVar [HValueRef]
instPendingFrees  :: !(MVar [HValueRef])
      -- ^ Values that need to be freed before the next command is sent.
      -- Finalizers for ForeignRefs can append values to this list
      -- asynchronously.

  , forall c. ExtInterpInstance c -> c
instExtra             :: !c
      -- ^ Instance specific extra fields
  }

------------------------
-- JS Stuff
------------------------

data JSInterpExtra = JSInterpExtra
  { JSInterpExtra -> Handle
instStdIn       :: !Handle         -- ^ Stdin for the process
  , JSInterpExtra -> FinderCache
instFinderCache :: !FinderCache
  , JSInterpExtra -> FinderOpts
instFinderOpts  :: !FinderOpts
  , JSInterpExtra -> MVar JSState
instJSState     :: !(MVar JSState) -- ^ Mutable state
  , JSInterpExtra -> UnitId
instGhciUnitId  :: !UnitId         -- ^ GHCi unit-id
  }

data JSState = JSState
  { JSState -> LinkPlan
jsLinkState     :: !LinkPlan -- ^ Linker state of the interpreter
  , JSState -> Bool
jsServerStarted :: !Bool     -- ^ Is the Haskell server started?
  }

-- | NodeJs configuration
data NodeJsSettings = NodeJsSettings
  { NodeJsSettings -> String
nodeProgram         :: FilePath        -- ^ location of node.js program
  , NodeJsSettings -> Maybe String
nodePath            :: Maybe FilePath  -- ^ value of NODE_PATH environment variable (search path for Node modules; GHCJS used to provide some)
  , NodeJsSettings -> [String]
nodeExtraArgs       :: [String]        -- ^ extra arguments to pass to node.js
  , NodeJsSettings -> Integer
nodeKeepAliveMaxMem :: Integer         -- ^ keep node.js (TH, GHCJSi) processes alive if they don't use more than this
  }

defaultNodeJsSettings :: NodeJsSettings
defaultNodeJsSettings :: NodeJsSettings
defaultNodeJsSettings = NodeJsSettings
  { nodeProgram :: String
nodeProgram         = String
"node"
  , nodePath :: Maybe String
nodePath            = Maybe String
forall a. Maybe a
Nothing
  , nodeExtraArgs :: [String]
nodeExtraArgs       = []
  , nodeKeepAliveMaxMem :: Integer
nodeKeepAliveMaxMem = Integer
536870912
  }


data JSInterpConfig = JSInterpConfig
  { JSInterpConfig -> NodeJsSettings
jsInterpNodeConfig  :: !NodeJsSettings  -- ^ NodeJS settings
  , JSInterpConfig -> String
jsInterpScript      :: !FilePath        -- ^ Path to "ghc-interp.js" script
  , JSInterpConfig -> TmpFs
jsInterpTmpFs       :: !TmpFs
  , JSInterpConfig -> TempDir
jsInterpTmpDir      :: !TempDir
  , JSInterpConfig -> Logger
jsInterpLogger      :: !Logger
  , JSInterpConfig -> StgToJSConfig
jsInterpCodegenCfg  :: !StgToJSConfig
  , JSInterpConfig -> UnitEnv
jsInterpUnitEnv     :: !UnitEnv
  , JSInterpConfig -> FinderOpts
jsInterpFinderOpts  :: !FinderOpts
  , JSInterpConfig -> FinderCache
jsInterpFinderCache :: !FinderCache
  }

------------------------
-- Wasm Stuff
------------------------

data WasmInterpConfig = WasmInterpConfig
  { WasmInterpConfig -> String
wasmInterpDyLD           :: !FilePath  -- ^ Location of dyld.mjs script
  , WasmInterpConfig -> String
wasmInterpLibDir         ::  FilePath  -- ^ wasi-sdk sysroot libdir containing libc.so, etc
  , WasmInterpConfig -> [String]
wasmInterpOpts           :: ![String]  -- ^ Additional command line arguments for iserv
  , WasmInterpConfig -> Platform
wasmInterpTargetPlatform :: !Platform
  , WasmInterpConfig -> Bool
wasmInterpProfiled       :: !Bool      -- ^ Are we profiling yet?
  , WasmInterpConfig -> String
wasmInterpHsSoSuffix     :: !String    -- ^ Shared lib filename common suffix sans .so, e.g. p-ghc9.13.20241001
  , WasmInterpConfig -> UnitState
wasmInterpUnitState      :: !UnitState
  }