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

module Distribution.Types.Mixin (
    Mixin(..),
    mkMixin,
    normaliseMixin,
) where

import Distribution.Compat.Prelude
import Prelude ()

import Distribution.CabalSpecVersion
import Distribution.Parsec
import Distribution.Pretty
import Distribution.Types.IncludeRenaming
import Distribution.Types.LibraryName
import Distribution.Types.PackageName
import Distribution.Types.UnqualComponentName

import qualified Distribution.Compat.CharParsing as P
import qualified Text.PrettyPrint                as PP

-- |
--
-- /Invariant:/ if 'mixinLibraryName' is 'LSubLibName', it's not
-- the same as 'mixinPackageName'. In other words,
-- the same invariant as 'Dependency' has.
--
data Mixin = Mixin { mixinPackageName :: PackageName
                   , mixinLibraryName :: LibraryName
                   , mixinIncludeRenaming :: IncludeRenaming }
    deriving (Show, Read, Eq, Ord, Typeable, Data, Generic)

instance Binary Mixin
instance Structured Mixin

instance NFData Mixin where rnf = genericRnf

instance Pretty Mixin where
    pretty (Mixin pn LMainLibName incl)     = pretty pn <+> pretty incl
    pretty (Mixin pn (LSubLibName ln) incl) = pretty pn <<>> PP.colon <<>> pretty ln <+> pretty incl

-- |
--
-- >>>  simpleParsec "mylib" :: Maybe Mixin
-- Just (Mixin {mixinPackageName = PackageName "mylib", mixinLibraryName = LMainLibName, mixinIncludeRenaming = IncludeRenaming {includeProvidesRn = DefaultRenaming, includeRequiresRn = DefaultRenaming}})
--
-- >>>  simpleParsec "thatlib:sublib" :: Maybe Mixin
-- Just (Mixin {mixinPackageName = PackageName "thatlib", mixinLibraryName = LSubLibName (UnqualComponentName "sublib"), mixinIncludeRenaming = IncludeRenaming {includeProvidesRn = DefaultRenaming, includeRequiresRn = DefaultRenaming}})
--
-- >>>  simpleParsec "thatlib:thatlib" :: Maybe Mixin
-- Just (Mixin {mixinPackageName = PackageName "thatlib", mixinLibraryName = LMainLibName, mixinIncludeRenaming = IncludeRenaming {includeProvidesRn = DefaultRenaming, includeRequiresRn = DefaultRenaming}})
--
-- Sublibrary syntax is accepted since @cabal-version: 3.4@.
--
-- >>> map (`simpleParsec'` "mylib:sub") [CabalSpecV3_0, CabalSpecV3_4] :: [Maybe Mixin]
-- [Nothing,Just (Mixin {mixinPackageName = PackageName "mylib", mixinLibraryName = LSubLibName (UnqualComponentName "sub"), mixinIncludeRenaming = IncludeRenaming {includeProvidesRn = DefaultRenaming, includeRequiresRn = DefaultRenaming}})]
--
instance Parsec Mixin where
    parsec = do
        pn <- parsec
        ln <- P.option LMainLibName $ do
            _ <- P.char ':'
            versionGuardMultilibs
            parsecWarning PWTExperimental "colon specifier is experimental feature (issue #5660)"
            LSubLibName <$> parsec
        P.spaces
        incl <- parsec
        return (mkMixin pn ln incl)
      where

versionGuardMultilibs :: CabalParsing m => m ()
versionGuardMultilibs = do
  csv <- askCabalSpecVersion
  when (csv < CabalSpecV3_4) $ fail $ unwords
    [ "Sublibrary mixin syntax used."
    , "To use this syntax the package needs to specify at least 'cabal-version: 3.4'."
    ]

-- | Smart constructor of 'Mixin', enforces invariant.
-- 
-- @since 3.4.0.0
mkMixin :: PackageName -> LibraryName -> IncludeRenaming -> Mixin
mkMixin pn (LSubLibName uqn) incl
    | packageNameToUnqualComponentName pn == uqn
    = Mixin pn LMainLibName incl
mkMixin pn ln incl
    = Mixin pn ln incl

-- | Restore invariant
normaliseMixin :: Mixin -> Mixin
normaliseMixin (Mixin pn ln incl) = mkMixin pn ln incl