{-# 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, Typeable)

-- | 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
