{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}

module Distribution.Types.BuildInfo (
    BuildInfo(..),

    emptyBuildInfo,
    allLanguages,
    allExtensions,
    usedExtensions,
    usesTemplateHaskellOrQQ,

    hcOptions,
    hcProfOptions,
    hcSharedOptions,
    hcStaticOptions,
) where

import Prelude ()
import Distribution.Compat.Prelude

import Distribution.Types.Mixin
import Distribution.Types.Dependency
import Distribution.Types.ExeDependency
import Distribution.Types.LegacyExeDependency
import Distribution.Types.PkgconfigDependency

import Distribution.ModuleName
import Distribution.Compiler
import Language.Haskell.Extension

-- Consider refactoring into executable and library versions.
data BuildInfo = BuildInfo {
        -- | component is buildable here
        buildable         :: Bool,
        -- | Tools needed to build this bit.
        --
        -- This is a legacy field that 'buildToolDepends' largely supersedes.
        --
        -- Unless use are very sure what you are doing, use the functions in
        -- "Distribution.Simple.BuildToolDepends" rather than accessing this
        -- field directly.
        buildTools        :: [LegacyExeDependency],
        -- | Haskell tools needed to build this bit
        --
        -- This field is better than 'buildTools' because it allows one to
        -- precisely specify an executable in a package.
        --
        -- Unless use are very sure what you are doing, use the functions in
        -- "Distribution.Simple.BuildToolDepends" rather than accessing this
        -- field directly.
        buildToolDepends  :: [ExeDependency],
        cppOptions        :: [String],  -- ^ options for pre-processing Haskell code
        asmOptions        :: [String],  -- ^ options for assmebler
        cmmOptions        :: [String],  -- ^ options for C-- compiler
        ccOptions         :: [String],  -- ^ options for C compiler
        cxxOptions        :: [String],  -- ^ options for C++ compiler
        ldOptions         :: [String],  -- ^ options for linker
        pkgconfigDepends  :: [PkgconfigDependency], -- ^ pkg-config packages that are used
        frameworks        :: [String], -- ^support frameworks for Mac OS X
        extraFrameworkDirs:: [String], -- ^ extra locations to find frameworks.
        asmSources        :: [FilePath], -- ^ Assembly files.
        cmmSources        :: [FilePath], -- ^ C-- files.
        cSources          :: [FilePath],
        cxxSources        :: [FilePath],
        jsSources         :: [FilePath],
        hsSourceDirs      :: [FilePath], -- ^ where to look for the Haskell module hierarchy
        otherModules      :: [ModuleName], -- ^ non-exposed or non-main modules
        virtualModules    :: [ModuleName], -- ^ exposed modules that do not have a source file (e.g. @GHC.Prim@ from @ghc-prim@ package)
        autogenModules    :: [ModuleName], -- ^ not present on sdist, Paths_* or user-generated with a custom Setup.hs

        defaultLanguage   :: Maybe Language,-- ^ language used when not explicitly specified
        otherLanguages    :: [Language],    -- ^ other languages used within the package
        defaultExtensions :: [Extension],   -- ^ language extensions used by all modules
        otherExtensions   :: [Extension],   -- ^ other language extensions used within the package
        oldExtensions     :: [Extension],   -- ^ the old extensions field, treated same as 'defaultExtensions'

        extraLibs         :: [String], -- ^ what libraries to link with when compiling a program that uses your package
        extraGHCiLibs     :: [String], -- ^ if present, overrides extraLibs when package is loaded with GHCi.
        extraBundledLibs  :: [String], -- ^ if present, adds libs to hs-libraries, which become part of the package.
                                       --   Example: the Cffi library shipping with the rts, alognside the HSrts-1.0.a,.o,...
                                       --   Example 2: a library that is being built by a foreing tool (e.g. rust)
                                       --              and copied and registered together with this library.  The
                                       --              logic on how this library is built will have to be encoded in a
                                       --              custom Setup for now.  Otherwise cabal would need to lear how to
                                       --              call arbitrary library builders.
        extraLibFlavours  :: [String], -- ^ Hidden Flag.  This set of strings, will be appended to all libraries when
                                       --   copying. E.g. [libHS<name>_<flavour> | flavour <- extraLibFlavours]. This
                                       --   should only be needed in very specific cases, e.g. the `rts` package, where
                                       --   there are multiple copies of slightly differently built libs.
        extraDynLibFlavours :: [String], -- ^ Hidden Flag. This set of strings will be appended to all /dynamic/
                                         --   libraries when copying. This is particularly useful with the `rts` package,
                                         --   where we want different dynamic flavours of the RTS library to be installed.
        extraLibDirs      :: [String],
        includeDirs       :: [FilePath], -- ^directories to find .h files
        includes          :: [FilePath], -- ^ The .h files to be found in includeDirs
        autogenIncludes   :: [FilePath], -- ^ The .h files to be generated (e.g. by @autoconf@)
        installIncludes   :: [FilePath], -- ^ .h files to install with the package
        options           :: PerCompilerFlavor [String],
        profOptions       :: PerCompilerFlavor [String],
        sharedOptions     :: PerCompilerFlavor [String],
        staticOptions     :: PerCompilerFlavor [String],
        customFieldsBI    :: [(String,String)], -- ^Custom fields starting
                                                -- with x-, stored in a
                                                -- simple assoc-list.
        targetBuildDepends :: [Dependency], -- ^ Dependencies specific to a library or executable target
        mixins :: [Mixin]
    }
    deriving (Generic, Show, Read, Eq, Typeable, Data)

instance Binary BuildInfo
instance Structured BuildInfo
instance NFData BuildInfo where rnf = genericRnf

instance Monoid BuildInfo where
  mempty = BuildInfo {
    buildable           = True,
    buildTools          = [],
    buildToolDepends    = [],
    cppOptions          = [],
    asmOptions          = [],
    cmmOptions          = [],
    ccOptions           = [],
    cxxOptions          = [],
    ldOptions           = [],
    pkgconfigDepends    = [],
    frameworks          = [],
    extraFrameworkDirs  = [],
    asmSources          = [],
    cmmSources          = [],
    cSources            = [],
    cxxSources          = [],
    jsSources           = [],
    hsSourceDirs        = [],
    otherModules        = [],
    virtualModules      = [],
    autogenModules      = [],
    defaultLanguage     = Nothing,
    otherLanguages      = [],
    defaultExtensions   = [],
    otherExtensions     = [],
    oldExtensions       = [],
    extraLibs           = [],
    extraGHCiLibs       = [],
    extraBundledLibs    = [],
    extraLibFlavours    = [],
    extraDynLibFlavours = [],
    extraLibDirs        = [],
    includeDirs         = [],
    includes            = [],
    autogenIncludes     = [],
    installIncludes     = [],
    options             = mempty,
    profOptions         = mempty,
    sharedOptions       = mempty,
    staticOptions       = mempty,
    customFieldsBI      = [],
    targetBuildDepends  = [],
    mixins              = []
  }
  mappend = (<>)

instance Semigroup BuildInfo where
  a <> b = BuildInfo {
    buildable           = buildable a && buildable b,
    buildTools          = combine    buildTools,
    buildToolDepends    = combine    buildToolDepends,
    cppOptions          = combine    cppOptions,
    asmOptions          = combine    asmOptions,
    cmmOptions          = combine    cmmOptions,
    ccOptions           = combine    ccOptions,
    cxxOptions          = combine    cxxOptions,
    ldOptions           = combine    ldOptions,
    pkgconfigDepends    = combine    pkgconfigDepends,
    frameworks          = combineNub frameworks,
    extraFrameworkDirs  = combineNub extraFrameworkDirs,
    asmSources          = combineNub asmSources,
    cmmSources          = combineNub cmmSources,
    cSources            = combineNub cSources,
    cxxSources          = combineNub cxxSources,
    jsSources           = combineNub jsSources,
    hsSourceDirs        = combineNub hsSourceDirs,
    otherModules        = combineNub otherModules,
    virtualModules      = combineNub virtualModules,
    autogenModules      = combineNub autogenModules,
    defaultLanguage     = combineMby defaultLanguage,
    otherLanguages      = combineNub otherLanguages,
    defaultExtensions   = combineNub defaultExtensions,
    otherExtensions     = combineNub otherExtensions,
    oldExtensions       = combineNub oldExtensions,
    extraLibs           = combine    extraLibs,
    extraGHCiLibs       = combine    extraGHCiLibs,
    extraBundledLibs    = combine    extraBundledLibs,
    extraLibFlavours    = combine    extraLibFlavours,
    extraDynLibFlavours = combine    extraDynLibFlavours,
    extraLibDirs        = combineNub extraLibDirs,
    includeDirs         = combineNub includeDirs,
    includes            = combineNub includes,
    autogenIncludes     = combineNub autogenIncludes,
    installIncludes     = combineNub installIncludes,
    options             = combine    options,
    profOptions         = combine    profOptions,
    sharedOptions       = combine    sharedOptions,
    staticOptions       = combine    staticOptions,
    customFieldsBI      = combine    customFieldsBI,
    targetBuildDepends  = combineNub targetBuildDepends,
    mixins              = combine    mixins
  }
    where
      combine    field = field a `mappend` field b
      combineNub field = nub (combine field)
      combineMby field = field b `mplus` field a

emptyBuildInfo :: BuildInfo
emptyBuildInfo = mempty

-- | The 'Language's used by this component
--
allLanguages :: BuildInfo -> [Language]
allLanguages bi = maybeToList (defaultLanguage bi)
               ++ otherLanguages bi

-- | The 'Extension's that are used somewhere by this component
--
allExtensions :: BuildInfo -> [Extension]
allExtensions bi = usedExtensions bi
                ++ otherExtensions bi

-- | The 'Extensions' that are used by all modules in this component
--
usedExtensions :: BuildInfo -> [Extension]
usedExtensions bi = oldExtensions bi
                 ++ defaultExtensions bi

-- | Whether any modules in this component use Template Haskell or
-- Quasi Quotes
usesTemplateHaskellOrQQ :: BuildInfo -> Bool
usesTemplateHaskellOrQQ bi = any p (allExtensions bi)
  where
    p ex = ex `elem`
      [EnableExtension TemplateHaskell, EnableExtension QuasiQuotes]

-- |Select options for a particular Haskell compiler.
hcOptions :: CompilerFlavor -> BuildInfo -> [String]
hcOptions = lookupHcOptions options

hcProfOptions :: CompilerFlavor -> BuildInfo -> [String]
hcProfOptions = lookupHcOptions profOptions

hcSharedOptions :: CompilerFlavor -> BuildInfo -> [String]
hcSharedOptions = lookupHcOptions sharedOptions

hcStaticOptions :: CompilerFlavor -> BuildInfo -> [String]
hcStaticOptions = lookupHcOptions staticOptions

lookupHcOptions :: (BuildInfo -> PerCompilerFlavor [String])
                -> CompilerFlavor -> BuildInfo -> [String]
lookupHcOptions f hc bi = case f bi of
    PerCompilerFlavor ghc ghcjs
        | hc == GHC   -> ghc
        | hc == GHCJS -> ghcjs
        | otherwise   -> mempty