-- | Copy dependencies' haddocks to documentation directory.  This way, relative @../$pkg-$ver@
-- links work and it's easy to upload docs to a web server or otherwise view them in a
-- non-local-filesystem context. We copy instead of symlink for two reasons: (1) symlinks aren't
-- reliably supported on Windows, and (2) the filesystem containing dependencies' docs may not be
-- available where viewing the docs (e.g. if building in a Docker container).
copyDepHaddocks :: (MonadIO m, MonadLogger m, MonadThrow m, MonadCatch m, MonadBaseControl IO m)
                => EnvOverride
                -> WhichCompiler
                -> BaseConfigOpts
                -> [Path Abs Dir]
                -> PackageIdentifier
                -> Set (Path Abs Dir)
                -> m ()
copyDepHaddocks envOverride wc bco pkgDbs pkgId extraDestDirs = do
    mpkgHtmlDir <- findGhcPkgHaddockHtml envOverride wc pkgDbs pkgId
    case mpkgHtmlDir of
        Nothing -> return ()
        Just pkgHtmlDir -> do
            depGhcIds <- findGhcPkgDepends envOverride wc pkgDbs pkgId
            forM_ (map ghcPkgIdPackageIdentifier depGhcIds) $
                copyDepWhenNeeded pkgHtmlDir
  where
    copyDepWhenNeeded pkgHtmlDir depId = do
        mDepOrigDir <- findGhcPkgHaddockHtml envOverride wc pkgDbs depId
        case mDepOrigDir of
            Nothing -> return ()
            Just depOrigDir -> do
                let extraDestDirs' =
                        -- Parent test ensures we don't try to copy docs to global locations
                        if bcoSnapInstallRoot bco `isParentOf` pkgHtmlDir ||
                           bcoLocalInstallRoot bco `isParentOf` pkgHtmlDir
                            then Set.insert (parent pkgHtmlDir) extraDestDirs
                            else extraDestDirs
                copyWhenNeeded extraDestDirs' depId depOrigDir
    copyWhenNeeded destDirs depId depOrigDir = do
        depRelDir <- parseRelDir (packageIdentifierString depId)
        copied <- forM (Set.toList destDirs) $ \destDir -> do
            let depCopyDir = destDir </> depRelDir
            if depCopyDir == depOrigDir
                then return False
                else do
                    needCopy <- getNeedCopy depOrigDir depCopyDir
                    when needCopy $ doCopy depOrigDir depCopyDir
                    return needCopy
        when (or copied) $
            copyDepHaddocks envOverride wc bco pkgDbs depId destDirs
    getNeedCopy depOrigDir depCopyDir = do
        let depOrigIndex = haddockIndexFile depOrigDir
            depCopyIndex = haddockIndexFile depCopyDir
        depOrigExists <- fileExists depOrigIndex
        depCopyExists <- fileExists depCopyIndex
        case (depOrigExists, depCopyExists) of
            (False, _) -> return False
            (True, False) -> return True
            (True, True) -> do
                copyMod <- liftIO $ getModificationTime (toFilePath depCopyIndex)
                origMod <- liftIO $ getModificationTime (toFilePath depOrigIndex)
                return (copyMod <= origMod)
    doCopy depOrigDir depCopyDir = do
        removeTreeIfExists depCopyDir
        createTree depCopyDir
        copyDirectoryRecursive depOrigDir depCopyDir

-- | Generate Haddock index and contents for local packages.
copyDepHaddocks :: (MonadIO m, MonadLogger m, MonadThrow m, MonadCatch m, MonadBaseControl IO m)
                => EnvOverride
                -> WhichCompiler
                -> BaseConfigOpts
                -> [Path Abs Dir]
                -> PackageIdentifier
                -> Set (Path Abs Dir)
                -> m ()
copyDepHaddocks envOverride wc bco pkgDbs pkgId extraDestDirs = do
    mpkgHtmlDir <- findGhcPkgHaddockHtml envOverride wc pkgDbs pkgId
    case mpkgHtmlDir of
        Nothing -> return ()
        Just pkgHtmlDir -> do
            depGhcIds <- findGhcPkgDepends envOverride wc pkgDbs pkgId
            forM_ (map ghcPkgIdPackageIdentifier depGhcIds) $
                copyDepWhenNeeded pkgHtmlDir
  where
    copyDepWhenNeeded pkgHtmlDir depId = do
        mDepOrigDir <- findGhcPkgHaddockHtml envOverride wc pkgDbs depId
        case mDepOrigDir of
            Nothing -> return ()
            Just depOrigDir -> do
                let extraDestDirs' =
                        -- Parent test ensures we don't try to copy docs to global locations
                        if bcoSnapInstallRoot bco `isParentOf` pkgHtmlDir ||
                           bcoLocalInstallRoot bco `isParentOf` pkgHtmlDir
                            then Set.insert (parent pkgHtmlDir) extraDestDirs
                            else extraDestDirs
                copyWhenNeeded extraDestDirs' depId depOrigDir
    copyWhenNeeded destDirs depId depOrigDir = do
        depRelDir <- parseRelDir (packageIdentifierString depId)
        copied <- forM (Set.toList destDirs) $ \destDir -> do
            let depCopyDir = destDir </> depRelDir
            if depCopyDir == depOrigDir
                then return False
                else do
                    needCopy <- getNeedCopy depOrigDir depCopyDir
                    when needCopy $ doCopy depOrigDir depCopyDir
                    return needCopy
        when (or copied) $
            copyDepHaddocks envOverride wc bco pkgDbs depId destDirs
    getNeedCopy depOrigDir depCopyDir = do
        let depOrigIndex = haddockIndexFile depOrigDir
            depCopyIndex = haddockIndexFile depCopyDir
        depOrigExists <- fileExists depOrigIndex
        depCopyExists <- fileExists depCopyIndex
        case (depOrigExists, depCopyExists) of
            (False, _) -> return False
            (True, False) -> return True
            (True, True) -> do
                copyMod <- liftIO $ getModificationTime (toFilePath depCopyIndex)
                origMod <- liftIO $ getModificationTime (toFilePath depOrigIndex)
                return (copyMod <= origMod)
    doCopy depOrigDir depCopyDir = do
        removeTreeIfExists depCopyDir
        createTree depCopyDir
        copyDirectoryRecursive depOrigDir depCopyDir

-- | Generate Haddock index and contents for local packages.
copyDepHaddocks envOverride wc bco pkgDbs pkgId extraDestDirs = do
    mpkgHtmlDir <- findGhcPkgHaddockHtml envOverride wc pkgDbs pkgId
    case mpkgHtmlDir of
        Nothing -> return ()
        Just pkgHtmlDir -> do
            depGhcIds <- findGhcPkgDepends envOverride wc pkgDbs pkgId
            forM_ (map ghcPkgIdPackageIdentifier depGhcIds) $
                copyDepWhenNeeded pkgHtmlDir
  where
    copyDepWhenNeeded pkgHtmlDir depId = do
        mDepOrigDir <- findGhcPkgHaddockHtml envOverride wc pkgDbs depId
        case mDepOrigDir of
            Nothing -> return ()
            Just depOrigDir -> do
                let extraDestDirs' =
                        -- Parent test ensures we don't try to copy docs to global locations
                        if bcoSnapInstallRoot bco `isParentOf` pkgHtmlDir ||
                           bcoLocalInstallRoot bco `isParentOf` pkgHtmlDir
                            then Set.insert (parent pkgHtmlDir) extraDestDirs
                            else extraDestDirs
                copyWhenNeeded extraDestDirs' depId depOrigDir
    copyWhenNeeded destDirs depId depOrigDir = do
        depRelDir <- parseRelDir (packageIdentifierString depId)
        copied <- forM (Set.toList destDirs) $ \destDir -> do
            let depCopyDir = destDir </> depRelDir
            if depCopyDir == depOrigDir
                then return False
                else do
                    needCopy <- getNeedCopy depOrigDir depCopyDir
                    when needCopy $ doCopy depOrigDir depCopyDir
                    return needCopy
        when (or copied) $
            copyDepHaddocks envOverride wc bco pkgDbs depId destDirs
    getNeedCopy depOrigDir depCopyDir = do
        let depOrigIndex = haddockIndexFile depOrigDir
            depCopyIndex = haddockIndexFile depCopyDir
        depOrigExists <- fileExists depOrigIndex
        depCopyExists <- fileExists depCopyIndex
        case (depOrigExists, depCopyExists) of
            (False, _) -> return False
            (True, False) -> return True
            (True, True) -> do
                copyMod <- liftIO $ getModificationTime (toFilePath depCopyIndex)
                origMod <- liftIO $ getModificationTime (toFilePath depOrigIndex)
                return (copyMod <= origMod)
    doCopy depOrigDir depCopyDir = do
        removeTreeIfExists depCopyDir
        createTree depCopyDir
        copyDirectoryRecursive depOrigDir depCopyDir

-- | Generate Haddock index and contents for local packages.