    {-# LANGUAGE ExistentialQuantification #-}
    {-# LANGUAGE DeriveGeneric             #-}
    {-# LANGUAGE MultiWayIf                #-}
    {-# LANGUAGE OverloadedStrings         #-}
    {-# LANGUAGE ScopedTypeVariables       #-}
    -- Copyright 2022 United States Government as represented by the Administrator
    -- of the National Aeronautics and Space Administration. All Rights Reserved.
    --
    -- Disclaimers
    --
    -- Licensed under the Apache License, Version 2.0 (the "License"); you may
    -- not use this file except in compliance with the License. You may obtain a
    -- copy of the License at
    --
    --      https://www.apache.org/licenses/LICENSE-2.0
    --
    -- Unless required by applicable law or agreed to in writing, software
    -- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    -- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    -- License for the specific language governing permissions and limitations
    -- under the License.
    --
    -- | Create <https://github.com/nasa/fprime FPrime> components that subscribe
    -- to obtain data and call Copilot when new values arrive.

    {- HLINT ignore "Functor law" -}
    module Command.FPrimeApp
        ( command
        , CommandOptions(..)
        , ErrorCode
        )
      where

    -- External imports
    import           Control.Applicative    ( liftA2, (<|>) )
    import qualified Control.Exception      as E
    import           Control.Monad.Except   ( ExceptT(..), liftEither )
    import           Data.Aeson             ( ToJSON, toJSON )
    import           Data.Char              ( toUpper )
    import           Data.Maybe             ( fromMaybe, mapMaybe, maybeToList )
    import           GHC.Generics           ( Generic )

    -- External imports: auxiliary
    import System.Directory.Extra ( copyTemplate )

    import qualified Command.Standalone

    -- Internal imports: auxiliary
    import Command.Result (Result (..))

    -- Internal imports
    import Command.Common
    import Command.Errors     (ErrorCode, ErrorTriplet (..))
    import Command.VariableDB (InputDef (..), TypeDef (..), VariableDB, findInput,
                               findType, findTypeByType)
    import Data.Aeson.Extra   (mergeObjects)
    import Data.ExprPair      (ExprPair(..), exprPair)
    import Data.Location      (Location (..))
    import Data.Spec.Parser   (readInputExpr)

    -- | Generate a new FPrime component connected to Copilot.
    command :: CommandOptions -- ^ Options to the ROS backend.
            -> IO (Result ErrorCode)
    command :: CommandOptions -> IO (Result ErrorCode)
    command CommandOptions
    options = ExceptT ErrorTriplet IO () -> IO (Result ErrorCode)
    forall (m :: * -> *) a.
    Monad m =>
    ExceptT ErrorTriplet m a -> m (Result ErrorCode)
    processResult (ExceptT ErrorTriplet IO () -> IO (Result ErrorCode))
    -> ExceptT ErrorTriplet IO () -> IO (Result ErrorCode)
    forall a b. (a -> b) -> a -> b
    $ do
        -- Obtain template dir
        templateDir <- Maybe FilePath -> FilePath -> ExceptT ErrorTriplet IO FilePath
    forall e. Maybe FilePath -> FilePath -> ExceptT e IO FilePath
    locateTemplateDir Maybe FilePath
    mTemplateDir FilePath
    "fprime"

        templateVars <- parseTemplateVarsFile templateVarsF

        appData <- command' options functions

        let subst = Value -> Value -> Value
    mergeObjects (AppData -> Value
    forall a. ToJSON a => a -> Value
    toJSON AppData
    appData) Value
    templateVars

        -- Expand template
        ExceptT $ fmap (makeLeftE cannotCopyTemplate) $ E.try $
          copyTemplate templateDir subst targetDir

      where

        targetDir :: FilePath
    targetDir     = CommandOptions -> FilePath
    commandTargetDir CommandOptions
    options
        mTemplateDir :: Maybe FilePath
    mTemplateDir  = CommandOptions -> Maybe FilePath
    commandTemplateDir CommandOptions
    options
        functions :: ExprPair
    functions     = FilePath -> ExprPair
    exprPair (CommandOptions -> FilePath
    commandPropFormat CommandOptions
    options)
        templateVarsF :: Maybe FilePath
    templateVarsF = CommandOptions -> Maybe FilePath
    commandExtraVars CommandOptions
    options

    command' :: CommandOptions
             -> ExprPair
             -> ExceptT ErrorTriplet IO AppData
    command' :: CommandOptions -> ExprPair -> ExceptT ErrorTriplet IO AppData
    command' CommandOptions
    options (ExprPair ExprPairT a
    exprT) = do
        -- Open files needed to fill in details in the template.
        vs    <- Maybe FilePath -> ExceptT ErrorTriplet IO (Maybe [FilePath])
    parseVariablesFile Maybe FilePath
    varNameFile
        rs    <- parseRequirementsListFile handlersFile
        varDB <- openVarDBFilesWithDefault varDBFile

        specT <- maybe (return Nothing) (\FilePath
    e -> InputFile a -> Maybe (InputFile a)
    forall a. a -> Maybe a
    Just (InputFile a -> Maybe (InputFile a))
    -> (Spec a -> InputFile a) -> Spec a -> Maybe (InputFile a)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Spec a -> InputFile a
    forall a. Spec a -> InputFile a
    InputFileSpec (Spec a -> Maybe (InputFile a))
    -> ExceptT ErrorTriplet IO (Spec a)
    -> ExceptT ErrorTriplet IO (Maybe (InputFile a))
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> ExceptT ErrorTriplet IO (Spec a)
    readInputExpr' FilePath
    e) cExpr
        specF <- if null fpA
                      then return Nothing
                      else do
                        fpA' <- mapM readInputFile' fpA
                        let fpA'' = [InputFile a] -> [InputFile a]
    forall a. [InputFile a] -> [InputFile a]
    combineInputFiles [InputFile a]
    fpA'
                        if length fpA'' > 1
                          then liftEither $ Left commandMultipleInputTypes
                          else pure $ Just $ head fpA''

        let spec = Maybe (InputFile a)
    specT Maybe (InputFile a) -> Maybe (InputFile a) -> Maybe (InputFile a)
    forall a. Maybe a -> Maybe a -> Maybe a
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> Maybe (InputFile a)
    specF

        liftEither $ checkArguments spec vs rs

        copilotM <- sequenceA $ (\InputFile a
    spec' -> InputFile a
    -> Maybe FilePath -> [FilePath] -> ExceptT ErrorTriplet IO AppData
    processSpec InputFile a
    spec' Maybe FilePath
    cExpr [FilePath]
    fpA) <$> spec

        let varNames = [FilePath] -> Maybe [FilePath] -> [FilePath]
    forall a. a -> Maybe a -> a
    fromMaybe (Maybe (InputFile a) -> [FilePath]
    forall {a}. Maybe (InputFile a) -> [FilePath]
    defaultVarNames Maybe (InputFile a)
    spec) Maybe [FilePath]
    vs
            monitors = [(FilePath, Maybe FilePath)]
    -> ([FilePath] -> [(FilePath, Maybe FilePath)])
    -> Maybe [FilePath]
    -> [(FilePath, Maybe FilePath)]
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe (Maybe (InputFile a) -> [(FilePath, Maybe FilePath)]
    forall {a}. Maybe (InputFile a) -> [(FilePath, Maybe FilePath)]
    defaultMonitors Maybe (InputFile a)
    spec) ((FilePath -> (FilePath, Maybe FilePath))
    -> [FilePath] -> [(FilePath, Maybe FilePath)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\FilePath
    x -> (FilePath
    x, Maybe FilePath
    forall a. Maybe a
    Nothing))) Maybe [FilePath]
    rs

        let appData   = [VarDecl] -> [Monitor] -> Maybe AppData -> AppData
    AppData [VarDecl]
    variables [Monitor]
    monitors' Maybe AppData
    copilotM
            variables = (FilePath -> Maybe VarDecl) -> [FilePath] -> [VarDecl]
    forall a b. (a -> Maybe b) -> [a] -> [b]
    mapMaybe (VariableDB -> FilePath -> Maybe VarDecl
    variableMap VariableDB
    varDB) [FilePath]
    varNames
            monitors' = ((FilePath, Maybe FilePath) -> Maybe Monitor)
    -> [(FilePath, Maybe FilePath)] -> [Monitor]
    forall a b. (a -> Maybe b) -> [a] -> [b]
    mapMaybe (VariableDB -> (FilePath, Maybe FilePath) -> Maybe Monitor
    monitorMap VariableDB
    varDB) [(FilePath, Maybe FilePath)]
    monitors

        return appData

      where

        cExpr :: Maybe FilePath
    cExpr          = CommandOptions -> Maybe FilePath
    commandConditionExpr CommandOptions
    options
        fpA :: [FilePath]
    fpA            = CommandOptions -> [FilePath]
    commandInputFiles CommandOptions
    options
        varNameFile :: Maybe FilePath
    varNameFile    = CommandOptions -> Maybe FilePath
    commandVariables CommandOptions
    options
        varDBFile :: [FilePath]
    varDBFile      = Maybe FilePath -> [FilePath]
    forall a. Maybe a -> [a]
    maybeToList (Maybe FilePath -> [FilePath]) -> Maybe FilePath -> [FilePath]
    forall a b. (a -> b) -> a -> b
    $ CommandOptions -> Maybe FilePath
    commandVariableDB CommandOptions
    options
        handlersFile :: Maybe FilePath
    handlersFile   = CommandOptions -> Maybe FilePath
    commandHandlers CommandOptions
    options
        formatName :: FilePath
    formatName     = CommandOptions -> FilePath
    commandFormat CommandOptions
    options
        propFormatName :: FilePath
    propFormatName = CommandOptions -> FilePath
    commandPropFormat CommandOptions
    options
        propVia :: Maybe FilePath
    propVia        = CommandOptions -> Maybe FilePath
    commandPropVia CommandOptions
    options

        readInputExpr' :: FilePath -> ExceptT ErrorTriplet IO (Spec a)
    readInputExpr' FilePath
    e =
          FilePath
    -> FilePath
    -> Maybe FilePath
    -> ExprPairT a
    -> ExceptT ErrorTriplet IO (Spec a)
    forall a.
    FilePath
    -> FilePath
    -> Maybe FilePath
    -> ExprPairT a
    -> ExceptT ErrorTriplet IO (Spec a)
    readInputExpr FilePath
    e FilePath
    propFormatName Maybe FilePath
    propVia ExprPairT a
    exprT

        readInputFile' :: FilePath -> ExceptT ErrorTriplet IO (InputFile a)
    readInputFile' FilePath
    f =
          FilePath
    -> FilePath
    -> FilePath
    -> Maybe FilePath
    -> ExprPairT a
    -> ExceptT ErrorTriplet IO (InputFile a)
    forall a.
    FilePath
    -> FilePath
    -> FilePath
    -> Maybe FilePath
    -> ExprPairT a
    -> ExceptT ErrorTriplet IO (InputFile a)
    parseInputFile FilePath
    f FilePath
    formatName FilePath
    propFormatName Maybe FilePath
    propVia ExprPairT a
    exprT

        processSpec :: InputFile a
    -> Maybe FilePath -> [FilePath] -> ExceptT ErrorTriplet IO AppData
    processSpec InputFile a
    spec' Maybe FilePath
    expr' [FilePath]
    fp' =
          Maybe FilePath
    -> [FilePath]
    -> FilePath
    -> [(FilePath, FilePath)]
    -> ExprPairT a
    -> InputFile a
    -> ExceptT ErrorTriplet IO AppData
    forall a.
    Maybe FilePath
    -> [FilePath]
    -> FilePath
    -> [(FilePath, FilePath)]
    -> ExprPairT a
    -> InputFile a
    -> ExceptT ErrorTriplet IO AppData
    Command.Standalone.commandLogic Maybe FilePath
    expr' [FilePath]
    fp' FilePath
    "copilot" [] ExprPairT a
    exprT InputFile a
    spec'

        defaultVarNames :: Maybe (InputFile a) -> [FilePath]
    defaultVarNames Maybe (InputFile a)
    spec = case Maybe (InputFile a)
    spec of
          Just (InputFileSpec Spec a
    spec') -> Maybe (Spec a) -> [FilePath]
    forall a. Maybe (Spec a) -> [FilePath]
    specExtractExternalVariables (Spec a -> Maybe (Spec a)
    forall a. a -> Maybe a
    Just Spec a
    spec')
          Just (InputFileDiagram Diagram
    _)  -> []
          Maybe (InputFile a)
    Nothing                    -> Maybe (Spec (ZonkAny 1)) -> [FilePath]
    forall a. Maybe (Spec a) -> [FilePath]
    specExtractExternalVariables Maybe (Spec (ZonkAny 1))
    forall a. Maybe a
    Nothing

        defaultMonitors :: Maybe (InputFile a) -> [(FilePath, Maybe FilePath)]
    defaultMonitors Maybe (InputFile a)
    spec = case Maybe (InputFile a)
    spec of
          Just (InputFileSpec Spec a
    spec') -> Maybe (Spec a) -> [(FilePath, Maybe FilePath)]
    forall a. Maybe (Spec a) -> [(FilePath, Maybe FilePath)]
    specExtractHandlers (Spec a -> Maybe (Spec a)
    forall a. a -> Maybe a
    Just Spec a
    spec')
          Just (InputFileDiagram Diagram
    _)  -> [ (FilePath
    "handler", FilePath -> Maybe FilePath
    forall a. a -> Maybe a
    Just FilePath
    "uint8_t" ) ]
          Maybe (InputFile a)
    Nothing                    -> Maybe (Spec (ZonkAny 0)) -> [(FilePath, Maybe FilePath)]
    forall a. Maybe (Spec a) -> [(FilePath, Maybe FilePath)]
    specExtractHandlers Maybe (Spec (ZonkAny 0))
    forall a. Maybe a
    Nothing

    -- ** Argument processing

    -- | Options used to customize the conversion of specifications to F'
    -- applications.
    data CommandOptions = CommandOptions
      { CommandOptions -> Maybe FilePath
    commandConditionExpr :: Maybe String   -- ^ Trigger condition.
      , CommandOptions -> [FilePath]
    commandInputFiles  :: [FilePath]     -- ^ Input specification files.
      , CommandOptions -> FilePath
    commandTargetDir   :: FilePath       -- ^ Target directory where the
                                             -- component should be created.
      , CommandOptions -> Maybe FilePath
    commandTemplateDir :: Maybe FilePath -- ^ Directory where the template is
                                             -- to be found.
      , CommandOptions -> Maybe FilePath
    commandVariables   :: Maybe FilePath -- ^ File containing a list of
                                             -- variables to make available to
                                             -- Copilot.
      , CommandOptions -> Maybe FilePath
    commandVariableDB  :: Maybe FilePath -- ^ File containing a list of known
                                             -- variables with their types and the
                                             -- message IDs they can be obtained
                                             -- from.
      , CommandOptions -> Maybe FilePath
    commandHandlers    :: Maybe FilePath -- ^ File containing a list of
                                             -- handlers used in the Copilot
                                             -- specification. The handlers are
                                             -- assumed to receive no arguments.
      , CommandOptions -> FilePath
    commandFormat      :: String         -- ^ Format of the input file.
      , CommandOptions -> FilePath
    commandPropFormat  :: String         -- ^ Format used for input properties.
      , CommandOptions -> Maybe FilePath
    commandPropVia     :: Maybe String   -- ^ Use external command to
                                             -- pre-process system properties.
      , CommandOptions -> Maybe FilePath
    commandExtraVars   :: Maybe FilePath -- ^ File containing additional
                                             -- variables to make available to the
                                             -- template.
      }

    -- | Return the variable information needed to generate declarations
    -- and subscriptions for a given variable name and variable database.
    variableMap :: VariableDB
                -> String
                -> Maybe VarDecl
    variableMap :: VariableDB -> FilePath -> Maybe VarDecl
    variableMap VariableDB
    varDB FilePath
    varName = do
      inputDef     <- VariableDB -> FilePath -> Maybe InputDef
    findInput VariableDB
    varDB FilePath
    varName
      inputDefType <- inputType inputDef
      let typeDef = VariableDB -> FilePath -> FilePath -> FilePath -> Maybe TypeDef
    findType VariableDB
    varDB FilePath
    varName FilePath
    "fprime/port" FilePath
    "C"

      portType <- maybe (inputType inputDef) (Just . typeFromType) typeDef

      return $ VarDecl varName inputDefType portType

    -- | Return the monitor information needed to generate declarations and
    -- publishers for the given monitor info, and variable database.
    monitorMap :: VariableDB
               -> (String, Maybe String)
               -> Maybe Monitor
    monitorMap :: VariableDB -> (FilePath, Maybe FilePath) -> Maybe Monitor
    monitorMap VariableDB
    varDB (FilePath
    monitorName, Maybe FilePath
    Nothing) =
      Monitor -> Maybe Monitor
    forall a. a -> Maybe a
    Just (Monitor -> Maybe Monitor) -> Monitor -> Maybe Monitor
    forall a b. (a -> b) -> a -> b
    $ FilePath -> FilePath -> Maybe FilePath -> Maybe FilePath -> Monitor
    Monitor FilePath
    monitorName ((Char -> Char) -> FilePath -> FilePath
    forall a b. (a -> b) -> [a] -> [b]
    map Char -> Char
    toUpper FilePath
    monitorName) Maybe FilePath
    forall a. Maybe a
    Nothing Maybe FilePath
    forall a. Maybe a
    Nothing
    monitorMap VariableDB
    varDB (FilePath
    monitorName, Just FilePath
    ty) = do
      let tyPort :: FilePath
    tyPort = FilePath -> (TypeDef -> FilePath) -> Maybe TypeDef -> FilePath
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe FilePath
    ty TypeDef -> FilePath
    typeFromType (Maybe TypeDef -> FilePath) -> Maybe TypeDef -> FilePath
    forall a b. (a -> b) -> a -> b
    $ VariableDB -> FilePath -> FilePath -> FilePath -> Maybe TypeDef
    findTypeByType VariableDB
    varDB FilePath
    "fprime/port" FilePath
    "C" FilePath
    ty
      Monitor -> Maybe Monitor
    forall a. a -> Maybe a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Monitor -> Maybe Monitor) -> Monitor -> Maybe Monitor
    forall a b. (a -> b) -> a -> b
    $ FilePath -> FilePath -> Maybe FilePath -> Maybe FilePath -> Monitor
    Monitor FilePath
    monitorName ((Char -> Char) -> FilePath -> FilePath
    forall a b. (a -> b) -> [a] -> [b]
    map Char -> Char
    toUpper FilePath
    monitorName) (FilePath -> Maybe FilePath
    forall a. a -> Maybe a
    Just FilePath
    ty) (FilePath -> Maybe FilePath
    forall a. a -> Maybe a
    Just FilePath
    tyPort)

    -- | The declaration of a variable in C, with a given type and name.
    data VarDecl = VarDecl
        { VarDecl -> FilePath
    varDeclName       :: String
        , VarDecl -> FilePath
    varDeclType       :: String
        , VarDecl -> FilePath
    varDeclFPrimeType :: String
        }
      deriving (forall x. VarDecl -> Rep VarDecl x)
    -> (forall x. Rep VarDecl x -> VarDecl) -> Generic VarDecl
    forall x. Rep VarDecl x -> VarDecl
    forall x. VarDecl -> Rep VarDecl x
    forall a.
    (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
    $cfrom :: forall x. VarDecl -> Rep VarDecl x
    from :: forall x. VarDecl -> Rep VarDecl x
    $cto :: forall x. Rep VarDecl x -> VarDecl
    to :: forall x. Rep VarDecl x -> VarDecl
    Generic

    instance ToJSON VarDecl

    data Monitor = Monitor
        { Monitor -> FilePath
    monitorName     :: String
        , Monitor -> FilePath
    monitorUC       :: String
        , Monitor -> Maybe FilePath
    monitorType     :: Maybe String
        , Monitor -> Maybe FilePath
    monitorPortType :: Maybe String
        }
      deriving (forall x. Monitor -> Rep Monitor x)
    -> (forall x. Rep Monitor x -> Monitor) -> Generic Monitor
    forall x. Rep Monitor x -> Monitor
    forall x. Monitor -> Rep Monitor x
    forall a.
    (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
    $cfrom :: forall x. Monitor -> Rep Monitor x
    from :: forall x. Monitor -> Rep Monitor x
    $cto :: forall x. Rep Monitor x -> Monitor
    to :: forall x. Rep Monitor x -> Monitor
    Generic

    instance ToJSON Monitor

    -- | Data that may be relevant to generate a ROS application.
    data AppData = AppData
      { AppData -> [VarDecl]
    variables :: [VarDecl]
      , AppData -> [Monitor]
    monitors  :: [Monitor]
      , AppData -> Maybe AppData
    copilot   :: Maybe Command.Standalone.AppData
      }
      deriving ((forall x. AppData -> Rep AppData x)
    -> (forall x. Rep AppData x -> AppData) -> Generic AppData
    forall x. Rep AppData x -> AppData
    forall x. AppData -> Rep AppData x
    forall a.
    (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
    $cfrom :: forall x. AppData -> Rep AppData x
    from :: forall x. AppData -> Rep AppData x
    $cto :: forall x. Rep AppData x -> AppData
    to :: forall x. Rep AppData x -> AppData
    Generic)

    instance ToJSON AppData

    -- | Error message associated to having multiple input files of incompatible
    -- types.
    commandMultipleInputTypes :: ErrorTriplet
    commandMultipleInputTypes :: ErrorTriplet
    commandMultipleInputTypes =
        ErrorCode -> FilePath -> Location -> ErrorTriplet
    ErrorTriplet ErrorCode
    ecMultipleInputTypes FilePath
    msg Location
    LocationNothing
      where
        msg :: FilePath
    msg =
          FilePath
    "Too many inputs provided. Provide one diagram or multiple specs."

    -- | Error: multiple inputs of incompatible types.
    ecMultipleInputTypes :: ErrorCode
    ecMultipleInputTypes :: ErrorCode
    ecMultipleInputTypes = ErrorCode
    1