module Debian.GenBuildDeps
    ( DepInfo(..)
    , sourceName'
    , relations'
    , binaryNames'
    
    , buildDependencies
    , RelaxInfo
    , relaxDeps
    
    , BuildableInfo(..)
    , ReadyTarget(..)
    , buildable
    , compareSource
    
    , orderSource
    , genDeps
    , failPackage
    , getSourceOrder
    ) where
#if !MIN_VERSION_base(4,8,0)
import           Control.Applicative ((<$>))
#endif
import           Control.Exception (throw)
import           Control.Monad (filterM, foldM)
import           Control.Monad.State (evalState, get, modify, State)
import           Data.Graph (Graph, Edge, Vertex, buildG, topSort, reachable, transposeG, edges, scc)
import           Data.List as List (elemIndex, find, map, nub, partition, tails)
import           Data.Map as Map (empty, findWithDefault, fromList, insert, Map, lookup)
import           Data.Maybe
import           Data.Set as Set (fromList, intersection, null, Set)
import           Data.Tree as Tree (Tree(Node, rootLabel, subForest))
import           Debian.Control (parseControlFromFile)
import           Debian.Control.Policy (HasDebianControl, DebianControl, ControlFileError(..), validateDebianControl, debianSourcePackageName, debianBinaryPackageNames, debianBuildDeps, debianBuildDepsIndep)
import           Debian.Loc (__LOC__)
import           Debian.Relation
import           Debian.Relation.Text ()
import           System.Directory (getDirectoryContents, doesFileExist)
data DepInfo = DepInfo {
      sourceName :: SrcPkgName          
    , relations :: Relations            
    , binaryNames :: [BinPkgName]       
    , depSet :: Set.Set BinPkgName          
    , binSet :: Set.Set BinPkgName          
    } deriving Show
instance Eq DepInfo where
    a == b = (sourceName a == sourceName b) &&
             Set.fromList (map Set.fromList (relations a)) == Set.fromList (map Set.fromList (relations b)) &&
             Set.fromList (binaryNames a) == Set.fromList (binaryNames b)
buildDependencies :: HasDebianControl control => control -> DepInfo
buildDependencies control = do
  let rels = concat [fromMaybe [] (debianBuildDeps control),
                     fromMaybe [] (debianBuildDepsIndep control)]
      bins = debianBinaryPackageNames control
  DepInfo { sourceName = debianSourcePackageName control
          , relations = rels
          , binaryNames = bins
          , depSet = Set.fromList (List.map (\(Rel x _ _) -> x) (concat rels))
          , binSet = Set.fromList bins }
sourceName' :: HasDebianControl control => control -> SrcPkgName
sourceName' control = debianSourcePackageName control
relations' :: HasDebianControl control => control -> Relations
relations' control = concat [fromMaybe [] (debianBuildDeps control),
                            fromMaybe [] (debianBuildDepsIndep control)]
binaryNames' :: HasDebianControl control => control -> [BinPkgName]
binaryNames' control = debianBinaryPackageNames control
newtype OldRelaxInfo = RelaxInfo [(BinPkgName, Maybe SrcPkgName)] deriving Show
type RelaxInfo = SrcPkgName -> BinPkgName -> Bool
relaxDeps :: RelaxInfo -> [DepInfo] -> [DepInfo]
relaxDeps relaxInfo deps =
    List.map relaxDep deps
    where
      relaxDep :: DepInfo -> DepInfo
      relaxDep info = info {relations = filteredDependencies}
          where
            
            
            
            filteredDependencies :: Relations
            filteredDependencies = filter (/= []) (List.map (filter keepDep) (relations info))
            keepDep :: Relation -> Bool
            keepDep (Rel name _ _) = not (relaxInfo (sourceName info) name)
data ReadyTarget a
    = ReadyTarget { ready :: a
                  
                  , waiting :: [a]
                  
                  , other :: [a]
                  
                  }
data BuildableInfo a
    = BuildableInfo
      { readyTargets :: [ReadyTarget a]
      , allBlocked :: [a] }
    | CycleInfo
      { depPairs :: [(a, a)] }
buildable :: forall a. (a -> DepInfo) -> [a] -> BuildableInfo a
buildable relax packages =
    
    
    
    case partition (\ x -> reachable hasDep x == [x]) verts of
      
      
      ([], _) -> CycleInfo {depPairs = List.map ofEdge $ head $ (allCycles hasDep)}
      
      
      (allReady, blocked) ->
          BuildableInfo { readyTargets = List.map (makeReady blocked allReady) allReady
                        , allBlocked = List.map ofVertex blocked }
    where
      makeReady :: [Vertex] -> [Vertex] -> Vertex -> ReadyTarget a
      makeReady blocked ready thisReady =
          let otherReady = filter (/= thisReady) ready
              (directlyBlocked, otherBlocked) = partition (\ x -> elem x (reachable isDep thisReady)) blocked in
          ReadyTarget { ready = ofVertex thisReady
                      , waiting = List.map ofVertex directlyBlocked
                      , other = List.map ofVertex (otherReady ++ otherBlocked) }
      
      isDep :: Graph
      isDep = transposeG hasDep
      hasDep :: Graph
      hasDep = buildG (0, length packages  1) hasDepEdges
      hasDepEdges :: [(Int, Int)]
      hasDepEdges =
#if 0
          nub (foldr f [] (tails vertPairs))
          where f :: [(Int, DepInfo)] -> [(Int, Int)] -> [(Int, Int)]
                f [] es = es
                f (x : xs) es = catMaybes (List.map (toEdge x) xs) ++ es
                toEdge :: (Int, DepInfo) -> (Int, DepInfo) -> Maybe Edge
                toEdge (xv, xa) (yv, ya) =
                    case compareSource xa ya of
                      EQ -> Nothing
                      LT -> Just (yv, xv)
                      GT -> Just (xv, yv)
#else
          nub (evalState (foldM f [] (tails vertPairs)) Map.empty)
          where f :: [(Int, Int)] -> [(Int, DepInfo)] -> State (Map.Map (Int, Int) Ordering) [(Int, Int)]
                f es [] = return es
                f es (x : xs) = mapM (toEdge x) xs >>= \es' -> return (catMaybes es' ++ es)
                toEdge :: (Int, DepInfo) -> (Int, DepInfo) -> State (Map.Map (Int, Int) Ordering) (Maybe Edge)
                toEdge (xv, xa) (yv, ya) = do
                  mp <- get
                  r <- case Map.lookup (xv, yv) mp of
                         Just r' -> return r'
                         Nothing -> do
                           let r' = compareSource xa ya
                           
                           modify (Map.insert (xv, yv) r')
                           return r'
                  case r of
                    EQ -> return Nothing
                    LT -> return $ Just (yv, xv)
                    GT -> return $ Just (xv, yv)
#endif
      ofEdge :: Edge -> (a, a)
      ofEdge (a, b) = (ofVertex a, ofVertex b)
      ofVertex :: Int -> a
      ofVertex n = fromJust (Map.findWithDefault Nothing n (Map.fromList (zip [0..] (map Just packages))))
      verts :: [Int]
      verts = map fst vertPairs
      vertPairs :: [(Int, DepInfo)]
      vertPairs = zip [0..] $ map relax packages
allCycles :: Graph -> [[Edge]]
allCycles g =
    
    
    concatMap sccCycles (scc g)
    where
      
      sccCycles :: Tree Vertex -> [[Edge]]
      sccCycles t = mapMaybe addBackEdge (treePaths t)
      addBackEdge :: [Vertex] -> Maybe [Edge]
      addBackEdge path@(root : _) =
          let back = (last path, root) in
          if elem back (edges g) then Just (pathEdges (path ++ [root])) else Nothing
treePaths :: Tree a -> [[a]]
treePaths (Node {rootLabel = r, subForest = []}) = [[r]]
treePaths (Node {rootLabel = r, subForest = ts}) = map (r :) (concatMap treePaths ts)
pathEdges :: [a] -> [(a, a)]
pathEdges (v1 : v2 : vs) = (v1, v2) : pathEdges (v2 : vs)
pathEdges _ = []
failPackage :: Eq a => (a -> a -> Ordering) -> a -> [a] -> ([a], [a])
failPackage cmp failed packages =
    let graph = buildGraph cmp packages in
    let root = elemIndex failed packages in
    let victims = maybe [] (map (fromJust . vertex) . reachable graph) root in
    partition (\ x -> not . elem x $ victims) packages
    where
      vertex n = Map.findWithDefault Nothing n vertexMap
      vertexMap = Map.fromList (zip [0..] (map Just packages))
orderSource :: (a -> a -> Ordering) -> [a] -> [a]
orderSource cmp packages =
    map (fromJust . vertex) (topSort graph)
    where
      graph = buildGraph cmp packages
      vertex n = Map.findWithDefault Nothing n vertexMap
      vertexMap = Map.fromList (zip [0..] (map Just packages))
buildGraph :: (a -> a -> Ordering) -> [a] -> Graph
buildGraph cmp packages =
    let es = someEdges (zip packages [0..]) in
    buildG (0, length packages  1) es
    where
      someEdges [] = []
      someEdges (a : etc) = aEdges a etc ++ someEdges etc
      aEdges (ap, an) etc =
          concat (map (\ (bp, bn) ->
                           case cmp ap bp of
                             LT -> [(an, bn)]
                             GT -> [(bn, an)]
                             EQ -> []) etc)
compareSource :: DepInfo -> DepInfo -> Ordering
compareSource p1 p2
#if 0
    | any (\rel -> isJust (find (checkPackageNameReq rel) (binaryNames p2))) (concat (relations p1)) = GT
    | any (\rel -> isJust (find (checkPackageNameReq rel) (binaryNames p1))) (concat (relations p2)) = LT
    | otherwise = EQ
    where
      checkPackageNameReq :: Relation -> BinPkgName -> Bool
      checkPackageNameReq (Rel rPkgName _ _) bPkgName = rPkgName == bPkgName
#else
    | not (Set.null (Set.intersection (depSet p1) (binSet p2))) = GT
    | not (Set.null (Set.intersection (depSet p2) (binSet p1))) = LT
    | otherwise = EQ
#endif
compareSource' :: HasDebianControl control => control -> control -> Ordering
compareSource' control1 control2
    | any (\rel -> isJust (find (checkPackageNameReq rel) bins2)) (concat depends1) = GT
    | any (\rel -> isJust (find (checkPackageNameReq rel) bins1)) (concat depends2) = LT
    | otherwise = EQ
    where
      bins1 = binaryNames' control1
      bins2 = binaryNames' control2
      depends1 = relations' control1
      depends2 = relations' control2
      checkPackageNameReq :: Relation -> BinPkgName -> Bool
      checkPackageNameReq (Rel rPkgName _ _) bPkgName = rPkgName == bPkgName
genDeps :: [FilePath] -> IO [DebianControl]
genDeps controlFiles = do
  orderSource compareSource' <$> mapM genDep' controlFiles
    where
      
      genDep' controlPath = parseControlFromFile controlPath >>=
                            either (\ x -> throw (ParseRelationsError [$__LOC__] x))
                                   (\ x -> validateDebianControl x  >>= either throw return)
getSourceOrder :: FilePath -> IO [SrcPkgName]
getSourceOrder fp =
    findControlFiles fp >>= genDeps >>= return . map sourceName'
    where
      
      findControlFiles :: FilePath -> IO [FilePath]
      findControlFiles root =
          getDirectoryContents root >>=
          mapM (\ x -> return $ root ++ "/" ++ x ++ "/debian/control") >>=
          filterM doesFileExist