{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
module Distribution.Types.Dependency
  ( Dependency(..)
  , depPkgName
  , depVerRange
  , depLibraries
  , thisPackageVersion
  , notThisPackageVersion
  , simplifyDependency
  ) where

import Prelude ()
import Distribution.Compat.Prelude

import Distribution.Version ( VersionRange, thisVersion
                            , notThisVersion, anyVersion
                            , simplifyVersionRange )

import Distribution.CabalSpecVersion
import Distribution.Pretty
import qualified Text.PrettyPrint as PP
import Distribution.Parsec
import Distribution.Compat.CharParsing (char, spaces)
import Distribution.Compat.Parsing (between, option)
import Distribution.Types.PackageId
import Distribution.Types.PackageName
import Distribution.Types.LibraryName
import Distribution.Types.UnqualComponentName

import Text.PrettyPrint ((<+>))
import qualified Data.Set as Set

-- | Describes a dependency on a source package (API)
--
data Dependency = Dependency
                    PackageName
                    VersionRange
                    (Set LibraryName)
                    -- ^ The set of libraries required from the package.
                    -- Only the selected libraries will be built.
                    -- It does not affect the cabal-install solver yet.
                  deriving (Generic, Read, Show, Eq, Typeable, Data)

depPkgName :: Dependency -> PackageName
depPkgName (Dependency pn _ _) = pn

depVerRange :: Dependency -> VersionRange
depVerRange (Dependency _ vr _) = vr

depLibraries :: Dependency -> Set LibraryName
depLibraries (Dependency _ _ cs) = cs

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

instance Pretty Dependency where
    pretty (Dependency name ver sublibs) = pretty name
                                       <<>> optionalMonoid
                                            (sublibs /= Set.singleton LMainLibName)
                                            (PP.colon <<>> PP.braces prettySublibs)
                                       <+> pretty ver
      where
        optionalMonoid True x = x
        optionalMonoid False _ = mempty
        prettySublibs = PP.hsep $ PP.punctuate PP.comma $ prettySublib <$> Set.toList sublibs
        prettySublib LMainLibName = PP.text $ unPackageName name
        prettySublib (LSubLibName un) = PP.text $ unUnqualComponentName un

versionGuardMultilibs :: (Monad m, CabalParsing m) => m a -> m a
versionGuardMultilibs expr = do
  csv <- askCabalSpecVersion
  if csv < CabalSpecV3_0
  then fail $ unwords
    [ "Sublibrary dependency syntax used."
    , "To use this syntax the package needs to specify at least 'cabal-version: 3.0'."
    , "Alternatively, if you are depending on an internal library, you can write"
    , "directly the library name as it were a package."
    ]
  else
    expr

-- |
--
-- >>> simpleParsec "mylib:sub" :: Maybe Dependency
-- Just (Dependency (PackageName "mylib") AnyVersion (fromList [LSubLibName (UnqualComponentName "sub")]))
--
-- >>> simpleParsec "mylib:{sub1,sub2}" :: Maybe Dependency
-- Just (Dependency (PackageName "mylib") AnyVersion (fromList [LSubLibName (UnqualComponentName "sub1"),LSubLibName (UnqualComponentName "sub2")]))
--
-- >>> simpleParsec "mylib:{ sub1 , sub2 }" :: Maybe Dependency
-- Just (Dependency (PackageName "mylib") AnyVersion (fromList [LSubLibName (UnqualComponentName "sub1"),LSubLibName (UnqualComponentName "sub2")]))
--
-- >>> simpleParsec "mylib:{ sub1 , sub2 } ^>= 42" :: Maybe Dependency
-- Just (Dependency (PackageName "mylib") (MajorBoundVersion (mkVersion [42])) (fromList [LSubLibName (UnqualComponentName "sub1"),LSubLibName (UnqualComponentName "sub2")]))
--
-- Spaces around colon are not allowed:
--
-- >>> simpleParsec "mylib: sub" :: Maybe Dependency
-- Nothing
--
-- >>> simpleParsec "mylib :sub" :: Maybe Dependency
-- Nothing
--
-- >>> simpleParsec "mylib: {sub1,sub2}" :: Maybe Dependency
-- Nothing
--
-- >>> simpleParsec "mylib :{sub1,sub2}" :: Maybe Dependency
-- Nothing
--
instance Parsec Dependency where
    parsec = do
        name <- parsec

        libs <- option [LMainLibName]
              $ (char ':' *>)
              $ versionGuardMultilibs
              $ pure <$> parseLib name <|> parseMultipleLibs name

        spaces -- https://github.com/haskell/cabal/issues/5846

        ver  <- parsec <|> pure anyVersion
        return $ Dependency name ver $ Set.fromList libs
      where makeLib pn ln | unPackageName pn == ln = LMainLibName
                          | otherwise = LSubLibName $ mkUnqualComponentName ln
            parseLib pn = makeLib pn <$> parsecUnqualComponentName
            parseMultipleLibs pn = between (char '{' *> spaces)
                                           (spaces <* char '}')
                                           $ parsecCommaList $ parseLib pn

-- mempty should never be in a Dependency-as-dependency.
-- This is only here until the Dependency-as-constraint problem is solved #5570.
-- Same for below.
thisPackageVersion :: PackageIdentifier -> Dependency
thisPackageVersion (PackageIdentifier n v) =
  Dependency n (thisVersion v) Set.empty

notThisPackageVersion :: PackageIdentifier -> Dependency
notThisPackageVersion (PackageIdentifier n v) =
  Dependency n (notThisVersion v) Set.empty

-- | Simplify the 'VersionRange' expression in a 'Dependency'.
-- See 'simplifyVersionRange'.
--
simplifyDependency :: Dependency -> Dependency
simplifyDependency (Dependency name range comps) =
  Dependency name (simplifyVersionRange range) comps