{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
   
module Options.Cabal where
import qualified Data.Set as S
import           Control.Monad.M

import qualified Data.List as L
import           Control.Monad
import qualified Distribution.PackageDescription.Parse as C
import qualified Distribution.Package as C
import qualified Distribution.Version as C
import qualified Distribution.PackageDescription as C
import           Data.String.Util
import           Control.Monad.IO.Class
import           Data.Function (on)
import           Data.Maybe

toPkgName :: C.Dependency -> String
toPkgName (C.Dependency (C.PackageName name) _) = name

vintersection :: C.Dependency -> C.Dependency -> Bool
vintersection (C.Dependency _ lv) (C.Dependency _ rv) = 
 not $ C.intersectVersionRanges lv rv == C.noVersion

-- | Post-condition: no version overlap
nub' :: [C.Dependency] -> [C.Dependency]
nub' = L.nubBy (\l r -> toPkgName l == toPkgName r && vintersection l r)

fromExclusions ::  S.Set String -> [C.Dependency] -> M [C.Dependency] 
fromExclusions exclusions deps = do 
  -- Print packages which were intended to be excluded, but
  -- weren't found anyway.  
  unless (S.null unfound_exclusions) $
    warning
      . listing
      . ("packages to exclude were not found:":)
      . map toString
      . S.toList
      $ excluded 
  -- Print version overlapped (removed packages) if any.
  unless (L.null overlapped) $
    warning $ 
      ("removed the following packages from processing due version" 
      ++ " range overlap:\n") ++ (indenting 2 . listing $ overlapped)
  return disjoint 
         
  where
    excluded = S.intersection (S.fromList $ map toPkgName deps) exclusions
    unfound_exclusions = S.difference exclusions excluded
    unexcluded = L.filter (not . flip S.member excluded . toPkgName) deps
    sorted     = L.sortBy (on compare toPkgName) unexcluded -- for readability
    disjoint   = nub' sorted
    -- | Calculate the packages with overlapped ranges 
    overlapped = sorted L.\\ disjoint 

-- | Given the defined exclusion set, return a list with the
-- following properties: 
-- 1 version overlap is not a relation for deps's taken as a set 
-- 2 unversioned packageId's satisfy cabal constraints 
readPackages :: FilePath -> S.Set String -> M [C.Dependency]
readPackages cabal_path exclusions = do 
  parse_result <- liftIO $ C.parsePackageDescription <$> readFile cabal_path
  case parse_result of
    (C.ParseFailed fail_msg) ->
      err . show $ fail_msg
    (C.ParseOk warnings desc) -> do
      unless (L.null warnings) . warning $ 
        preposition 
          "warnings during parse" "of" "cabal file" "warnings"
          (map show warnings)
      msg $ "parsing cabal file: " ++ cabal_path
      fromExclusions exclusions . toDeps $ desc
   where
    toDeps :: C.GenericPackageDescription -> [C.Dependency] 
    toDeps gpd =
      concatMap ($ gpd) [
        concatDeps . maybeToList . C.condLibrary,
        concatDeps . map snd . C.condExecutables,
        concatDeps . map snd . C.condTestSuites,
        concatDeps . map snd . C.condBenchmarks 
        ]
      where
        concatDeps :: [C.CondTree v [C.Dependency] a] -> [C.Dependency]
        concatDeps = concatMap C.condTreeConstraints
