{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Distribution.Types.UnitId
  ( UnitId
  , unUnitId
  , mkUnitId
  , DefUnitId
  , unsafeMkDefUnitId
  , unDefUnitId
  , newSimpleUnitId
  , mkLegacyUnitId
  , getHSLibraryName
  ) where

import Distribution.Compat.Prelude
import Distribution.Utils.ShortText
import Prelude ()

import qualified Distribution.Compat.CharParsing as P
import Distribution.Parsec
import Distribution.Pretty
import Distribution.Types.ComponentId
import Distribution.Types.PackageId

import Text.PrettyPrint (text)

-- | A unit identifier identifies a (possibly instantiated)
-- package/component that can be installed the installed package
-- database.  There are several types of components that can be
-- installed:
--
--  * A traditional library with no holes, so that 'unitIdHash'
--    is @Nothing@.  In the absence of Backpack, 'UnitId'
--    is the same as a 'ComponentId'.
--
--  * An indefinite, Backpack library with holes.  In this case,
--    'unitIdHash' is still @Nothing@, but in the install,
--    there are only interfaces, no compiled objects.
--
--  * An instantiated Backpack library with all the holes
--    filled in.  'unitIdHash' is a @Just@ a hash of the
--    instantiating mapping.
--
-- A unit is a component plus the additional information on how the
-- holes are filled in. Thus there is a one to many relationship: for a
-- particular component there are many different ways of filling in the
-- holes, and each different combination is a unit (and has a separate
-- 'UnitId').
--
-- 'UnitId' is distinct from 'OpenUnitId', in that it is always
-- installed, whereas 'OpenUnitId' are intermediate unit identities
-- that arise during mixin linking, and don't necessarily correspond
-- to any actually installed unit.  Since the mapping is not actually
-- recorded in a 'UnitId', you can't actually substitute over them
-- (but you can substitute over 'OpenUnitId').  See also
-- "Distribution.Backpack.FullUnitId" for a mechanism for expanding an
-- instantiated 'UnitId' to retrieve its mapping.
--
-- Backwards compatibility note: if you need to get the string
-- representation of a UnitId to pass, e.g., as a @-package-id@
-- flag, use the 'display' function, which will work on all
-- versions of Cabal.
newtype UnitId = UnitId ShortText
  deriving (Generic, Read, Show, Eq, Ord, Data, NFData)

instance Binary UnitId
instance Structured UnitId

-- | The textual format for 'UnitId' coincides with the format
-- GHC accepts for @-package-id@.
instance Pretty UnitId where
  pretty = text . unUnitId

-- | The textual format for 'UnitId' coincides with the format
-- GHC accepts for @-package-id@.
instance Parsec UnitId where
  parsec = mkUnitId <$> P.munch1 isUnitChar
    where
      -- https://gitlab.haskell.org/ghc/ghc/issues/17752
      isUnitChar '-' = True
      isUnitChar '_' = True
      isUnitChar '.' = True
      isUnitChar '+' = True
      isUnitChar c = isAlphaNum c

-- | If you need backwards compatibility, consider using 'display'
-- instead, which is supported by all versions of Cabal.
unUnitId :: UnitId -> String
unUnitId (UnitId s) = fromShortText s

mkUnitId :: String -> UnitId
mkUnitId = UnitId . toShortText

-- | 'mkUnitId'
--
-- @since 2.0.0.2
instance IsString UnitId where
  fromString = mkUnitId

-- | Create a unit identity with no associated hash directly
-- from a 'ComponentId'.
newSimpleUnitId :: ComponentId -> UnitId
newSimpleUnitId = mkUnitId . unComponentId

-- | Make an old-style UnitId from a package identifier.
-- Assumed to be for the public library
mkLegacyUnitId :: PackageId -> UnitId
mkLegacyUnitId = newSimpleUnitId . mkComponentId . prettyShow

-- | Returns library name prefixed with HS, suitable for filenames
getHSLibraryName :: UnitId -> String
getHSLibraryName uid = "HS" ++ prettyShow uid

-- | A 'UnitId' for a definite package.  The 'DefUnitId' invariant says
-- that a 'UnitId' identified this way is definite; i.e., it has no
-- unfilled holes.
newtype DefUnitId = DefUnitId {unDefUnitId :: UnitId}
  deriving (Generic, Read, Show, Eq, Ord, Data, Binary, NFData, Pretty)

instance Structured DefUnitId

-- Workaround for a GHC 8.0.1 bug, see
-- https://github.com/haskell/cabal/issues/4793#issuecomment-334258288
instance Parsec DefUnitId where
  parsec = DefUnitId <$> parsec

-- | Unsafely create a 'DefUnitId' from a 'UnitId'.  Your responsibility
-- is to ensure that the 'DefUnitId' invariant holds.
unsafeMkDefUnitId :: UnitId -> DefUnitId
unsafeMkDefUnitId = DefUnitId
