--------------------------------------
-- Precompiled Cache
--
-- Idea is simple: cache information about packages built in other snapshots,
-- and then for identical matches (same flags, config options, dependencies)
-- just copy over the executables and reregister the libraries.
--------------------------------------

-- | The file containing information on the given package/configuration
-- combination. The filename contains a hash of the non-directory configure
-- options for quick lookup if there's a match.
--
-- It also returns an action yielding the location of the precompiled
-- path based on the old binary encoding.
--
-- We only pay attention to non-directory options. We don't want to avoid a
-- cache hit just because it was installed in a different directory.
precompiledCacheFile :: (MonadThrow m, MonadReader env m, HasEnvConfig env, MonadLogger m)
                     => PackageIdentifier
                     -> ConfigureOpts
                     -> Set GhcPkgId -- ^ dependencies
                     -> m (Path Abs File, m (Path Abs File))
precompiledCacheFile pkgident copts installedPackageIDs = do
    ec <- asks getEnvConfig

    compiler <- parseRelDir $ compilerVersionString $ envConfigCompilerVersion ec
    cabal <- parseRelDir $ versionString $ envConfigCabalVersion ec
    pkg <- parseRelDir $ packageIdentifierString pkgident
    platformRelDir <- platformGhcRelDir

    let input = (coNoDirs copts, installedPackageIDs)

    -- In Cabal versions 1.22 and later, the configure options contain the
    -- installed package IDs, which is what we need for a unique hash.
    -- Unfortunately, earlier Cabals don't have the information, so we must
    -- supplement it with the installed package IDs directly.
    -- See issue: https://github.com/commercialhaskell/stack/issues/1103
    let oldHash = B16.encode $ SHA256.hash $ LBS.toStrict $
            if envConfigCabalVersion ec >= $(mkVersion "1.22")
                then Binary.encode (coNoDirs copts)
                else Binary.encode input
        hashToPath hash = do
            hashPath <- parseRelFile $ S8.unpack hash
            return $ getStackRoot ec
                 </> $(mkRelDir "precompiled")
                 </> platformRelDir
                 </> compiler
                 </> cabal
                 </> pkg
                 </> hashPath

    $logDebug $ "Precompiled cache input = " <> T.pack (show input)
    newPath <- hashToPath $ B64URL.encode $ SHA256.hash $ Store.encode input
    return (newPath, hashToPath oldHash)

-- | Write out information about a newly built package
precompiledCacheFile :: (MonadThrow m, MonadReader env m, HasEnvConfig env, MonadLogger m)
                     => PackageIdentifier
                     -> ConfigureOpts
                     -> Set GhcPkgId -- ^ dependencies
                     -> m (Path Abs File, m (Path Abs File))
precompiledCacheFile pkgident copts installedPackageIDs = do
    ec <- asks getEnvConfig

    compiler <- parseRelDir $ compilerVersionString $ envConfigCompilerVersion ec
    cabal <- parseRelDir $ versionString $ envConfigCabalVersion ec
    pkg <- parseRelDir $ packageIdentifierString pkgident
    platformRelDir <- platformGhcRelDir

    let input = (coNoDirs copts, installedPackageIDs)

    -- In Cabal versions 1.22 and later, the configure options contain the
    -- installed package IDs, which is what we need for a unique hash.
    -- Unfortunately, earlier Cabals don't have the information, so we must
    -- supplement it with the installed package IDs directly.
    -- See issue: https://github.com/commercialhaskell/stack/issues/1103
    let oldHash = B16.encode $ SHA256.hash $ LBS.toStrict $
            if envConfigCabalVersion ec >= $(mkVersion "1.22")
                then Binary.encode (coNoDirs copts)
                else Binary.encode input
        hashToPath hash = do
            hashPath <- parseRelFile $ S8.unpack hash
            return $ getStackRoot ec
                 </> $(mkRelDir "precompiled")
                 </> platformRelDir
                 </> compiler
                 </> cabal
                 </> pkg
                 </> hashPath

    $logDebug $ "Precompiled cache input = " <> T.pack (show input)
    newPath <- hashToPath $ B64URL.encode $ SHA256.hash $ Store.encode input
    return (newPath, hashToPath oldHash)

-- | Write out information about a newly built package
precompiledCacheFile pkgident copts installedPackageIDs = do
    ec <- asks getEnvConfig

    compiler <- parseRelDir $ compilerVersionString $ envConfigCompilerVersion ec
    cabal <- parseRelDir $ versionString $ envConfigCabalVersion ec
    pkg <- parseRelDir $ packageIdentifierString pkgident
    platformRelDir <- platformGhcRelDir

    let input = (coNoDirs copts, installedPackageIDs)

    -- In Cabal versions 1.22 and later, the configure options contain the
    -- installed package IDs, which is what we need for a unique hash.
    -- Unfortunately, earlier Cabals don't have the information, so we must
    -- supplement it with the installed package IDs directly.
    -- See issue: https://github.com/commercialhaskell/stack/issues/1103
    let oldHash = B16.encode $ SHA256.hash $ LBS.toStrict $
            if envConfigCabalVersion ec >= $(mkVersion "1.22")
                then Binary.encode (coNoDirs copts)
                else Binary.encode input
        hashToPath hash = do
            hashPath <- parseRelFile $ S8.unpack hash
            return $ getStackRoot ec
                 </> $(mkRelDir "precompiled")
                 </> platformRelDir
                 </> compiler
                 </> cabal
                 </> pkg
                 </> hashPath

    $logDebug $ "Precompiled cache input = " <> T.pack (show input)
    newPath <- hashToPath $ B64URL.encode $ SHA256.hash $ Store.encode input
    return (newPath, hashToPath oldHash)

-- | Write out information about a newly built package