    {-# LANGUAGE DeriveGeneric #-}

    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Distribution.Simple.Program.Types
    -- Copyright   :  Isaac Jones 2006, Duncan Coutts 2007-2009
    --
    -- Maintainer  :  cabal-devel@haskell.org
    -- Portability :  portable
    --
    -- This provides an abstraction which deals with configuring and running
    -- programs. A 'Program' is a static notion of a known program. A
    -- 'ConfiguredProgram' is a 'Program' that has been found on the current
    -- machine and is ready to be run (possibly with some user-supplied default
    -- args). Configuring a program involves finding its location and if necessary
    -- finding its version. There's reasonable default behavior for trying to find
    -- \"foo\" in PATH, being able to override its location, etc.
    --
    module Distribution.Simple.Program.Types (
        -- * Program and functions for constructing them
        Program(..),
        ProgramSearchPath,
        ProgramSearchPathEntry(..),
        simpleProgram,

        -- * Configured program and related functions
        ConfiguredProgram(..),
        programPath,
        suppressOverrideArgs,
        ProgArg,
        ProgramLocation(..),
        simpleConfiguredProgram,
      ) where

    import Distribution.Simple.Program.Find
             ( ProgramSearchPath, ProgramSearchPathEntry(..)
             , findProgramOnSearchPath )
    import Distribution.Version
             ( Version )
    import Distribution.Verbosity
             ( Verbosity )

    import Distribution.Compat.Binary (Binary)
    import qualified Data.Map as Map
    import GHC.Generics (Generic)

    -- | Represents a program which can be configured.
    --
    -- Note: rather than constructing this directly, start with 'simpleProgram' and
    -- override any extra fields.
    --
    data Program = Program {
           -- | The simple name of the program, eg. ghc
           programName :: String,

           -- | A function to search for the program if its location was not
           -- specified by the user. Usually this will just be a call to
           -- 'findProgramOnSearchPath'.
           --
           -- It is supplied with the prevailing search path which will typically
           -- just be used as-is, but can be extended or ignored as needed.
           programFindLocation :: Verbosity -> ProgramSearchPath
                                  -> IO (Maybe FilePath),

           -- | Try to find the version of the program. For many programs this is
           -- not possible or is not necessary so it's OK to return Nothing.
           programFindVersion :: Verbosity -> FilePath -> IO (Maybe Version),

           -- | A function to do any additional configuration after we have
           -- located the program (and perhaps identified its version). For example
           -- it could add args, or environment vars.
           programPostConf :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram
         }

    type ProgArg = String

    -- | Represents a program which has been configured and is thus ready to be run.
    --
    -- These are usually made by configuring a 'Program', but if you have to
    -- construct one directly then start with 'simpleConfiguredProgram' and
    -- override any extra fields.
    --
    data ConfiguredProgram = ConfiguredProgram {
           -- | Just the name again
           programId :: String,

           -- | The version of this program, if it is known.
           programVersion :: Maybe Version,

           -- | Default command-line args for this program.
           -- These flags will appear first on the command line, so they can be
           -- overridden by subsequent flags.
           programDefaultArgs :: [String],

           -- | Override command-line args for this program.
           -- These flags will appear last on the command line, so they override
           -- all earlier flags.
           programOverrideArgs :: [String],

           -- | Override environment variables for this program.
           -- These env vars will extend\/override the prevailing environment of
           -- the current to form the environment for the new process.
           programOverrideEnv :: [(String, Maybe String)],

           -- | A key-value map listing various properties of the program, useful
           -- for feature detection. Populated during the configuration step, key
           -- names depend on the specific program.
           programProperties :: Map.Map String String,

           -- | Location of the program. eg. @\/usr\/bin\/ghc-6.4@
           programLocation :: ProgramLocation
         } deriving (Eq, Generic, Read, Show)

    instance Binary ConfiguredProgram

    -- | Where a program was found. Also tells us whether it's specified by user or
    -- not.  This includes not just the path, but the program as well.
    data ProgramLocation
        = UserSpecified { locationPath :: FilePath }
          -- ^The user gave the path to this program,
          -- eg. --ghc-path=\/usr\/bin\/ghc-6.6
        | FoundOnSystem { locationPath :: FilePath }
          -- ^The program was found automatically.
          deriving (Eq, Generic, Read, Show)

    instance Binary ProgramLocation

    -- | The full path of a configured program.
    programPath :: ConfiguredProgram -> FilePath
    programPath = locationPath . programLocation

    -- | Suppress any extra arguments added by the user.
    suppressOverrideArgs :: ConfiguredProgram -> ConfiguredProgram
    suppressOverrideArgs prog = prog { programOverrideArgs = [] }

    -- | Make a simple named program.
    --
    -- By default we'll just search for it in the path and not try to find the
    -- version name. You can override these behaviours if necessary, eg:
    --
    -- > simpleProgram "foo" { programFindLocation = ... , programFindVersion ... }
    --
    simpleProgram :: String -> Program
    simpleProgram name = Program {
        programName         = name,
        programFindLocation = \v p -> findProgramOnSearchPath v p name,
        programFindVersion  = \_ _ -> return Nothing,
        programPostConf     = \_ p -> return p
      }

    -- | Make a simple 'ConfiguredProgram'.
    --
    -- > simpleConfiguredProgram "foo" (FoundOnSystem path)
    --
    simpleConfiguredProgram :: String -> ProgramLocation -> ConfiguredProgram
    simpleConfiguredProgram name loc = ConfiguredProgram {
         programId           = name,
         programVersion      = Nothing,
         programDefaultArgs  = [],
         programOverrideArgs = [],
         programOverrideEnv  = [],
         programProperties   = Map.empty,
         programLocation     = loc
      }