content: {-# LANGUAGE NoImplicitPrelude   #-}
  {-# LANGUAGE NoFieldSelectors    #-}
  {-# LANGUAGE OverloadedRecordDot #-}
  
  {-|
  Module      : Stack.Types.Dependency
  License     : BSD-3-Clause
  -}
  
  module Stack.Types.Dependency
    ( DepValue (..)
    , DepType (..)
    , DepLibrary (..)
    , cabalToStackDep
    , cabalExeToStackDep
    , cabalSetupDepsToStackDep
    , libraryDepFromVersionRange
    , isDepTypeLibrary
    , getDepSublib
    , depValueToTarget
    ) where
  
  import qualified Data.Map as Map
  import qualified Data.Set as Set
  import qualified Distribution.PackageDescription as Cabal
  import           Distribution.Types.VersionRange ( VersionRange )
  import           Stack.Prelude
  import           Stack.Types.ComponentUtils
                     ( StackUnqualCompName (..), fromCabalName )
  import           Stack.Types.NamedComponent ( NamedComponent(..) )
  import           Stack.Types.SourceMap ( PackageType (..), Target (..) )
  
  -- | The value for a map from dependency name. This contains both the version
  -- range and the type of dependency.
  data DepValue = DepValue
    { versionRange :: !VersionRange
    , depType :: !DepType
    }
    deriving Show
  
  -- | Is this package being used as a library, or just as a build tool? If the
  -- former, we need to ensure that a library actually exists. See
  -- <https://github.com/commercialhaskell/stack/issues/2195>
  data DepType
    = AsLibrary !DepLibrary
    | AsBuildTool
    deriving (Eq, Show)
  
  data DepLibrary = DepLibrary
    { main :: !Bool
    , subLib :: Set StackUnqualCompName
    }
    deriving (Eq, Show)
  
  getDepSublib :: DepValue -> Maybe (Set StackUnqualCompName)
  getDepSublib val = case val.depType of
    AsLibrary libVal -> Just libVal.subLib
    _ -> Nothing
  
  defaultDepLibrary :: DepLibrary
  defaultDepLibrary = DepLibrary True mempty
  
  isDepTypeLibrary :: DepType -> Bool
  isDepTypeLibrary AsLibrary{} = True
  isDepTypeLibrary AsBuildTool = False
  
  cabalToStackDep :: Cabal.Dependency -> DepValue
  cabalToStackDep (Cabal.Dependency _ verRange libNameSet) =
    DepValue { versionRange = verRange, depType = AsLibrary depLibrary }
   where
    depLibrary = DepLibrary finalHasMain filteredItems
    (finalHasMain, filteredItems) = foldr' iterator (False, mempty) libNameSet
    iterator LMainLibName (_, newLibNameSet) = (True, newLibNameSet)
    iterator (LSubLibName libName) (hasMain, newLibNameSet) =
      (hasMain, Set.insert (fromCabalName libName) newLibNameSet)
  
  cabalExeToStackDep :: Cabal.ExeDependency -> DepValue
  cabalExeToStackDep (Cabal.ExeDependency _ _name verRange) =
    DepValue { versionRange = verRange, depType = AsBuildTool }
  
  cabalSetupDepsToStackDep :: Cabal.SetupBuildInfo -> Map PackageName DepValue
  cabalSetupDepsToStackDep setupInfo =
    foldr' inserter mempty (Cabal.setupDepends setupInfo)
   where
    inserter d@(Cabal.Dependency packageName _ _) =
      Map.insert packageName (cabalToStackDep d)
  
  libraryDepFromVersionRange :: VersionRange -> DepValue
  libraryDepFromVersionRange range = DepValue
    { versionRange = range
    , depType = AsLibrary defaultDepLibrary
    }
  
  depValueToTarget :: DepValue -> Target
  depValueToTarget dv = case dv.depType of
    AsLibrary dlib -> TargetComps (completeSet dlib)
    AsBuildTool -> TargetAll PTDependency
   where
    completeSet dlib =
      (if dlib.main then Set.insert CLib else id) $ sublibSet dlib
    sublibSet dlib = Set.mapMonotonic CSubLib dlib.subLib
  