module Data.Dependency
    ( -- * Functions
      resolveDependencies
    , isPresent
    , buildSequence
    , asGraph
    -- * Types
    , Dependency (..)
    , PackageSet (..)
    , Version (..)
    ) where

import           Control.Monad
import           Data.Dependency.Sort
import           Data.Dependency.Type
import           Data.List            (groupBy)
import qualified Data.Map             as M
import qualified Data.Set             as S

isPresent :: PackageSet Version -> Dependency -> Bool
isPresent (PackageSet ps) (Dependency ln _ _) = g (M.lookup ln ps)
    where g (Just x) = not (S.null x)
          g Nothing  = False

latest :: (Ord a) => PackageSet a -> Dependency -> Maybe (String, a)
latest (PackageSet ps) (Dependency ln _ _) = do
    s <- M.lookup ln ps
    (,) ln <$> S.lookupMax s

buildSequence :: [Dependency] -> [[Dependency]]
buildSequence = reverse . groupBy independent . sortDeps
    where independent (Dependency ln _ ls) (Dependency ln' _ ls') = ln' `notElem` ls && ln `notElem` ls'

saturateDeps :: PackageSet Dependency -> Dependency -> Maybe [Dependency]
saturateDeps (PackageSet ps) dep = do
    let libDeps = _libDependencies dep
    deps <- sequence [ M.lookup lib ps | lib <- libDeps ]
    (:) dep <$> traverse S.lookupMax deps

-- | Heuristics:
--
-- 1. Always use a newer version when possible
--
-- 2. Obey constraints
--
-- 3. Specify an error for circular dependencies
--
-- 4. Specify an error for overconstrained builds
--
-- 5. Specify an error if a package is not present.
--
-- This doesn't do any package resolution beyond versioning.
resolveDependencies :: PackageSet Dependency -> [[Dependency]] -> Maybe [[Dependency]]
resolveDependencies ps = select . getLatest <=< fmap buildSequence . saturated
    where select = fmap (fmap (fmap snd))
          saturated dep = join . join <$> traverse (traverse (saturateDeps ps)) dep
          getLatest = sequence . fmap (traverse (latest ps))