getCabalLbs :: (StackM env m, HasEnvConfig env)
            => PvpBoundsType
            -> Maybe Int -- ^ optional revision
            -> FilePath
            -> m (PackageIdentifier, L.ByteString)
getCabalLbs pvpBounds mrev fp = do
    bs <- liftIO $ S.readFile fp
    (_warnings, gpd) <- readPackageUnresolvedBS Nothing bs
    (_, sourceMap) <- loadSourceMap AllowNoTargets defaultBuildOptsCLI
    menv <- getMinimalEnvOverride
    (installedMap, _, _, _) <- getInstalled menv GetInstalledOpts
                                { getInstalledProfiling = False
                                , getInstalledHaddock = False
                                , getInstalledSymbols = False
                                }
                                sourceMap
    let gpd' = gtraverseT (addBounds sourceMap installedMap) gpd
        gpd'' =
          case mrev of
            Nothing -> gpd'
            Just rev -> gpd'
              { Cabal.packageDescription
               = (Cabal.packageDescription gpd')
                  { Cabal.customFieldsPD
                  = (("x-revision", show rev):)
                  $ filter (\(x, _) -> map toLower x /= "x-revision")
                  $ Cabal.customFieldsPD
                  $ Cabal.packageDescription gpd'
                  }
              }
    ident <- parsePackageIdentifierFromString $ display $ Cabal.package $ Cabal.packageDescription gpd''
    return
      ( ident
      , TLE.encodeUtf8 $ TL.pack $ showGenericPackageDescription gpd''
      )
  where
    addBounds :: SourceMap -> InstalledMap -> Dependency -> Dependency
    addBounds sourceMap installedMap dep@(Dependency cname range) =
      case lookupVersion (fromCabalPackageName cname) of
        Nothing -> dep
        Just version -> Dependency cname $ simplifyVersionRange
          $ (if toAddUpper && not (hasUpper range) then addUpper version else id)
          $ (if toAddLower && not (hasLower range) then addLower version else id)
            range
      where
        lookupVersion name =
          case Map.lookup name sourceMap of
              Just (PSLocal lp) -> Just $ packageVersion $ lpPackage lp
              Just (PSUpstream version _ _ _ _) -> Just version
              Nothing ->
                  case Map.lookup name installedMap of
                      Just (_, installed) -> Just (installedVersion installed)
                      Nothing -> Nothing

    addUpper version = intersectVersionRanges
        (earlierVersion $ toCabalVersion $ nextMajorVersion version)
    addLower version = intersectVersionRanges
        (orLaterVersion (toCabalVersion version))

    (toAddLower, toAddUpper) =
      case pvpBounds of
        PvpBoundsNone  -> (False, False)
        PvpBoundsUpper -> (False, True)
        PvpBoundsLower -> (True,  False)
        PvpBoundsBoth  -> (True,  True)

-- | Traverse a data type.
getCabalLbs :: (StackM env m, HasEnvConfig env)
            => PvpBoundsType
            -> Maybe Int -- ^ optional revision
            -> FilePath
            -> m (PackageIdentifier, L.ByteString)
getCabalLbs pvpBounds mrev fp = do
    bs <- liftIO $ S.readFile fp
    (_warnings, gpd) <- readPackageUnresolvedBS Nothing bs
    (_, sourceMap) <- loadSourceMap AllowNoTargets defaultBuildOptsCLI
    menv <- getMinimalEnvOverride
    (installedMap, _, _, _) <- getInstalled menv GetInstalledOpts
                                { getInstalledProfiling = False
                                , getInstalledHaddock = False
                                , getInstalledSymbols = False
                                }
                                sourceMap
    let gpd' = gtraverseT (addBounds sourceMap installedMap) gpd
        gpd'' =
          case mrev of
            Nothing -> gpd'
            Just rev -> gpd'
              { Cabal.packageDescription
               = (Cabal.packageDescription gpd')
                  { Cabal.customFieldsPD
                  = (("x-revision", show rev):)
                  $ filter (\(x, _) -> map toLower x /= "x-revision")
                  $ Cabal.customFieldsPD
                  $ Cabal.packageDescription gpd'
                  }
              }
    ident <- parsePackageIdentifierFromString $ display $ Cabal.package $ Cabal.packageDescription gpd''
    return
      ( ident
      , TLE.encodeUtf8 $ TL.pack $ showGenericPackageDescription gpd''
      )
  where
    addBounds :: SourceMap -> InstalledMap -> Dependency -> Dependency
    addBounds sourceMap installedMap dep@(Dependency cname range) =
      case lookupVersion (fromCabalPackageName cname) of
        Nothing -> dep
        Just version -> Dependency cname $ simplifyVersionRange
          $ (if toAddUpper && not (hasUpper range) then addUpper version else id)
          $ (if toAddLower && not (hasLower range) then addLower version else id)
            range
      where
        lookupVersion name =
          case Map.lookup name sourceMap of
              Just (PSLocal lp) -> Just $ packageVersion $ lpPackage lp
              Just (PSUpstream version _ _ _ _) -> Just version
              Nothing ->
                  case Map.lookup name installedMap of
                      Just (_, installed) -> Just (installedVersion installed)
                      Nothing -> Nothing

    addUpper version = intersectVersionRanges
        (earlierVersion $ toCabalVersion $ nextMajorVersion version)
    addLower version = intersectVersionRanges
        (orLaterVersion (toCabalVersion version))

    (toAddLower, toAddUpper) =
      case pvpBounds of
        PvpBoundsNone  -> (False, False)
        PvpBoundsUpper -> (False, True)
        PvpBoundsLower -> (True,  False)
        PvpBoundsBoth  -> (True,  True)

-- | Traverse a data type.
getCabalLbs pvpBounds mrev fp = do
    bs <- liftIO $ S.readFile fp
    (_warnings, gpd) <- readPackageUnresolvedBS Nothing bs
    (_, sourceMap) <- loadSourceMap AllowNoTargets defaultBuildOptsCLI
    menv <- getMinimalEnvOverride
    (installedMap, _, _, _) <- getInstalled menv GetInstalledOpts
                                { getInstalledProfiling = False
                                , getInstalledHaddock = False
                                , getInstalledSymbols = False
                                }
                                sourceMap
    let gpd' = gtraverseT (addBounds sourceMap installedMap) gpd
        gpd'' =
          case mrev of
            Nothing -> gpd'
            Just rev -> gpd'
              { Cabal.packageDescription
               = (Cabal.packageDescription gpd')
                  { Cabal.customFieldsPD
                  = (("x-revision", show rev):)
                  $ filter (\(x, _) -> map toLower x /= "x-revision")
                  $ Cabal.customFieldsPD
                  $ Cabal.packageDescription gpd'
                  }
              }
    ident <- parsePackageIdentifierFromString $ display $ Cabal.package $ Cabal.packageDescription gpd''
    return
      ( ident
      , TLE.encodeUtf8 $ TL.pack $ showGenericPackageDescription gpd''
      )
  where
    addBounds :: SourceMap -> InstalledMap -> Dependency -> Dependency
    addBounds sourceMap installedMap dep@(Dependency cname range) =
      case lookupVersion (fromCabalPackageName cname) of
        Nothing -> dep
        Just version -> Dependency cname $ simplifyVersionRange
          $ (if toAddUpper && not (hasUpper range) then addUpper version else id)
          $ (if toAddLower && not (hasLower range) then addLower version else id)
            range
      where
        lookupVersion name =
          case Map.lookup name sourceMap of
              Just (PSLocal lp) -> Just $ packageVersion $ lpPackage lp
              Just (PSUpstream version _ _ _ _) -> Just version
              Nothing ->
                  case Map.lookup name installedMap of
                      Just (_, installed) -> Just (installedVersion installed)
                      Nothing -> Nothing

    addUpper version = intersectVersionRanges
        (earlierVersion $ toCabalVersion $ nextMajorVersion version)
    addLower version = intersectVersionRanges
        (orLaterVersion (toCabalVersion version))

    (toAddLower, toAddUpper) =
      case pvpBounds of
        PvpBoundsNone  -> (False, False)
        PvpBoundsUpper -> (False, True)
        PvpBoundsLower -> (True,  False)
        PvpBoundsBoth  -> (True,  True)

-- | Traverse a data type.