{-# LANGUAGE NoImplicitPrelude   #-}
{-# LANGUAGE OverloadedRecordDot #-}

-- | This module contains all the types related to the idea of installing a
-- package in the pkg-db or an executable on the file system.
module Stack.Types.Installed
  ( InstallLocation (..)
  , InstalledPackageLocation (..)
  , PackageDatabase (..)
  , PackageDbVariety (..)
  , InstallMap
  , Installed (..)
  , InstalledMap
  , InstalledLibraryInfo (..)
  , toPackageDbVariety
  , installedLibraryInfoFromGhcPkgId
  , simpleInstalledLib
  , installedToPackageIdOpt
  , installedPackageIdentifier
  , installedVersion
  , foldOnGhcPkgId'
  ) where

import qualified Data.Map as M
import qualified Distribution.SPDX.License as SPDX
import           Distribution.License ( License )
import           Stack.Prelude
import           Stack.Types.ComponentUtils ( StackUnqualCompName )
import           Stack.Types.GhcPkgId ( GhcPkgId, ghcPkgIdString )

-- | Type representing user package databases that packages can be installed
-- into.
data InstallLocation
  = Snap
    -- ^ The write-only package database, formerly known as the snapshot
    -- database.
  | Local
    -- ^ The mutable package database, formerly known as the local database.
  deriving (Eq, Show)

instance Semigroup InstallLocation where
  Local <> _ = Local
  _ <> Local = Local
  Snap <> Snap = Snap

instance Monoid InstallLocation where
  mempty = Snap
  mappend = (<>)

-- | Type representing user (non-global) package databases that can provide
-- installed packages.
data InstalledPackageLocation
  = InstalledTo InstallLocation
    -- ^ A package database that a package can be installed into.
  | ExtraPkgDb
    -- ^ An \'extra\' package database, specified by @extra-package-dbs@.
  deriving (Eq, Show)

-- | Type representing package databases that can provide installed packages.
data PackageDatabase
  = GlobalPkgDb
    -- ^ GHC's global package database.
  | UserPkgDb InstalledPackageLocation (Path Abs Dir)
    -- ^ A user package database.
  deriving (Eq, Show)

-- | A function to yield the variety of package database for a given
-- package database that can provide installed packages.
toPackageDbVariety :: PackageDatabase -> PackageDbVariety
toPackageDbVariety GlobalPkgDb = GlobalDb
toPackageDbVariety (UserPkgDb ExtraPkgDb _) = ExtraDb
toPackageDbVariety (UserPkgDb (InstalledTo Snap) _) = WriteOnlyDb
toPackageDbVariety (UserPkgDb (InstalledTo Local) _) = MutableDb

-- | Type representing varieties of package databases that can provide
-- installed packages.
data PackageDbVariety
  = GlobalDb
    -- ^ GHC's global package database.
  | ExtraDb
    -- ^ An \'extra\' package database, specified by @extra-package-dbs@.
  | WriteOnlyDb
    -- ^ The write-only package database, for immutable packages.
  | MutableDb
    -- ^ The mutable package database.
  deriving (Eq, Show)

-- | Type synonym representing dictionaries of package names for a project's
-- packages and dependencies, and pairs of their relevant database (write-only
-- or mutable) and package versions.
type InstallMap = Map PackageName (InstallLocation, Version)

-- | Type synonym representing dictionaries of package names, and a pair of in
-- which package database the package is installed (write-only or mutable) and
-- information about what is installed.
type InstalledMap = Map PackageName (InstallLocation, Installed)

data InstalledLibraryInfo = InstalledLibraryInfo
  { ghcPkgId :: GhcPkgId
  , license :: Maybe (Either SPDX.License License)
  , subLib :: Map StackUnqualCompName GhcPkgId
  }
  deriving (Eq, Show)

-- | Type representing information about what is installed.
data Installed
  = Library PackageIdentifier InstalledLibraryInfo
    -- ^ A library, including its installed package id and, optionally, its
    -- license.
  | Executable PackageIdentifier
    -- ^ An executable.
  deriving (Eq, Show)

installedLibraryInfoFromGhcPkgId :: GhcPkgId -> InstalledLibraryInfo
installedLibraryInfoFromGhcPkgId ghcPkgId =
  InstalledLibraryInfo ghcPkgId Nothing mempty

simpleInstalledLib ::
     PackageIdentifier
  -> GhcPkgId
  -> Map StackUnqualCompName GhcPkgId
  -> Installed
simpleInstalledLib pkgIdentifier ghcPkgId =
  Library pkgIdentifier . InstalledLibraryInfo ghcPkgId Nothing

installedToPackageIdOpt :: InstalledLibraryInfo -> [String]
installedToPackageIdOpt libInfo =
  M.foldr' (iterator (++)) (pure $ toStr libInfo.ghcPkgId) libInfo.subLib
 where
  toStr ghcPkgId = "-package-id=" <> ghcPkgIdString ghcPkgId
  iterator op ghcPkgId acc = pure (toStr ghcPkgId) `op` acc

installedPackageIdentifier :: Installed -> PackageIdentifier
installedPackageIdentifier (Library pid _) = pid
installedPackageIdentifier (Executable pid) = pid

-- | A strict fold over the 'GhcPkgId' of the given installed package. This will
-- iterate on both sub and main libraries, if any.
foldOnGhcPkgId' ::
     (Maybe StackUnqualCompName -> GhcPkgId -> resT -> resT)
  -> Installed
  -> resT
  -> resT
foldOnGhcPkgId' _ Executable{} res = res
foldOnGhcPkgId' fn (Library _ libInfo) res =
  M.foldrWithKey' (fn . Just) (base res) libInfo.subLib
 where
  base = fn Nothing libInfo.ghcPkgId

-- | Get the installed Version.
installedVersion :: Installed -> Version
installedVersion i =
  let PackageIdentifier _ version = installedPackageIdentifier i
  in  version
