{-# LANGUAGE DeriveGeneric #-}

module Distribution.Types.ComponentRequestedSpec
  ( -- $buildable_vs_enabled_components
    ComponentRequestedSpec (..)
  , ComponentDisabledReason (..)
  , defaultComponentRequestedSpec
  , componentNameRequested
  , componentEnabled
  , componentDisabledReason
  ) where

import Distribution.Compat.Prelude
import Prelude ()

import Distribution.Types.Component -- TODO: maybe remove me?
import Distribution.Types.ComponentName

import Distribution.Pretty (prettyShow)

-- $buildable_vs_enabled_components
-- #buildable_vs_enabled_components#
--
-- = Note: Buildable versus requested versus enabled components
-- What's the difference between a buildable component (ala
-- 'componentBuildable'), a requested component
-- (ala 'componentNameRequested'), and an enabled component (ala
-- 'componentEnabled')?
--
-- A component is __buildable__ if, after resolving flags and
-- conditionals, there is no @buildable: False@ property in it.
-- This is a /static/ property that arises from the
-- Cabal file and the package description flattening; once we have
-- a 'PackageDescription' buildability is known.
--
-- A component is __requested__ if a user specified, via a
-- the flags and arguments passed to configure, that it should be
-- built.  E.g., @--enable-tests@ or @--enable-benchmarks@ request
-- all tests and benchmarks, if they are provided.  What is requested
-- can be read off directly from 'ComponentRequestedSpec'.  A requested
-- component is not always buildable; e.g., a user may @--enable-tests@
-- but one of the test suites may have @buildable: False@.
--
-- A component is __enabled__ if it is BOTH buildable
-- and requested.  Once we have a 'LocalBuildInfo', whether or not a
-- component is enabled is known.
--
-- Generally speaking, most Cabal API code cares if a component
-- is enabled. (For example, if you want to run a preprocessor on each
-- component prior to building them, you want to run this on each
-- /enabled/ component.)
--
-- Note that post-configuration, you will generally not see a
-- non-buildable 'Component'.  This is because 'flattenPD' will drop
-- any such components from 'PackageDescription'.  See #3858 for
-- an example where this causes problems.

-- | Describes what components are enabled by user-interaction.
-- See also this note in
-- "Distribution.Types.ComponentRequestedSpec#buildable_vs_enabled_components".
--
-- @since 2.0.0.2
data ComponentRequestedSpec
  = ComponentRequestedSpec
      { testsRequested :: Bool
      , benchmarksRequested :: Bool
      }
  | OneComponentRequestedSpec ComponentName
  deriving (Generic, Read, Show, Eq)

instance Binary ComponentRequestedSpec
instance Structured ComponentRequestedSpec

-- | The default set of enabled components.  Historically tests and
-- benchmarks are NOT enabled by default.
--
-- @since 2.0.0.2
defaultComponentRequestedSpec :: ComponentRequestedSpec
defaultComponentRequestedSpec = ComponentRequestedSpec False False

-- | Is this component enabled?  See also this note in
-- "Distribution.Types.ComponentRequestedSpec#buildable_vs_enabled_components".
--
-- @since 2.0.0.2
componentEnabled :: ComponentRequestedSpec -> Component -> Bool
componentEnabled enabled = isNothing . componentDisabledReason enabled

-- | Is this component name enabled?  See also this note in
-- "Distribution.Types.ComponentRequestedSpec#buildable_vs_enabled_components".
--
-- @since 2.0.0.2
componentNameRequested :: ComponentRequestedSpec -> ComponentName -> Bool
componentNameRequested enabled = isNothing . componentNameNotRequestedReason enabled

-- | Is this component disabled, and if so, why?
--
-- @since 2.0.0.2
componentDisabledReason
  :: ComponentRequestedSpec
  -> Component
  -> Maybe ComponentDisabledReason
componentDisabledReason enabled comp
  | not (componentBuildable comp) = Just DisabledComponent
  | otherwise = componentNameNotRequestedReason enabled (componentName comp)

-- | Is this component name disabled, and if so, why?
--
-- @since 2.0.0.2
componentNameNotRequestedReason
  :: ComponentRequestedSpec
  -> ComponentName
  -> Maybe ComponentDisabledReason
componentNameNotRequestedReason
  ComponentRequestedSpec{testsRequested = False}
  (CTestName _) =
    Just DisabledAllTests
componentNameNotRequestedReason
  ComponentRequestedSpec{benchmarksRequested = False}
  (CBenchName _) =
    Just DisabledAllBenchmarks
componentNameNotRequestedReason ComponentRequestedSpec{} _ = Nothing
componentNameNotRequestedReason (OneComponentRequestedSpec cname) c
  | c == cname = Nothing
  | otherwise = Just (DisabledAllButOne (prettyShow cname))

-- | A reason explaining why a component is disabled.
--
-- @since 2.0.0.2
data ComponentDisabledReason
  = DisabledComponent
  | DisabledAllTests
  | DisabledAllBenchmarks
  | DisabledAllButOne String
