module GraphOps (
        addNode,        delNode,        getNode,       lookupNode,     modNode,
        size,
        union,
        addConflict,    delConflict,    addConflicts,
        addCoalesce,    delCoalesce,
        addExclusion,   addExclusions,
        addPreference,
        coalesceNodes,  coalesceGraph,
        freezeNode,     freezeOneInGraph, freezeAllInGraph,
        scanGraph,
        setColor,
        validateGraph,
        slurpNodeConflictCount
)
where
import GhcPrelude
import GraphBase
import Outputable
import Unique
import UniqSet
import UniqFM
import Data.List        hiding (union)
import Data.Maybe
lookupNode
        :: Uniquable k
        => Graph k cls color
        -> k -> Maybe (Node  k cls color)
lookupNode graph k
        = lookupUFM (graphMap graph) k
getNode
        :: Uniquable k
        => Graph k cls color
        -> k -> Node k cls color
getNode graph k
 = case lookupUFM (graphMap graph) k of
        Just node       -> node
        Nothing         -> panic "ColorOps.getNode: not found"
addNode :: Uniquable k
        => k -> Node k cls color
        -> Graph k cls color -> Graph k cls color
addNode k node graph
 = let
        
        map_conflict =
          nonDetFoldUniqSet
            
            
            (adjustUFM_C (\n -> n { nodeConflicts =
                                      addOneToUniqSet (nodeConflicts n) k}))
            (graphMap graph)
            (nodeConflicts node)
        
        map_coalesce =
          nonDetFoldUniqSet
            
            
            (adjustUFM_C (\n -> n { nodeCoalesce =
                                      addOneToUniqSet (nodeCoalesce n) k}))
            map_conflict
            (nodeCoalesce node)
  in    graph
        { graphMap      = addToUFM map_coalesce k node}
delNode :: (Uniquable k)
        => k -> Graph k cls color -> Maybe (Graph k cls color)
delNode k graph
        | Just node     <- lookupNode graph k
        = let   
                graph1  = foldl' (\g k1 -> let Just g' = delConflict k1 k g in g') graph
                        $ nonDetEltsUniqSet (nodeConflicts node)
                
                graph2  = foldl' (\g k1 -> let Just g' = delCoalesce k1 k g in g') graph1
                        $ nonDetEltsUniqSet (nodeCoalesce node)
                        
                
                graph3  = graphMapModify (\fm -> delFromUFM fm k) graph2
          in    Just graph3
        | otherwise
        = Nothing
modNode :: Uniquable k
        => (Node k cls color -> Node k cls color)
        -> k -> Graph k cls color -> Maybe (Graph k cls color)
modNode f k graph
 = case lookupNode graph k of
        Just Node{}
         -> Just
         $  graphMapModify
                 (\fm   -> let  Just node       = lookupUFM fm k
                                node'           = f node
                           in   addToUFM fm k node')
                graph
        Nothing -> Nothing
size    :: Graph k cls color -> Int
size graph
        = sizeUFM $ graphMap graph
union   :: Graph k cls color -> Graph k cls color -> Graph k cls color
union   graph1 graph2
        = Graph
        { graphMap              = plusUFM (graphMap graph1) (graphMap graph2) }
addConflict
        :: Uniquable k
        => (k, cls) -> (k, cls)
        -> Graph k cls color -> Graph k cls color
addConflict (u1, c1) (u2, c2)
 = let  addNeighbor u c u'
                = adjustWithDefaultUFM
                        (\node -> node { nodeConflicts = addOneToUniqSet (nodeConflicts node) u' })
                        (newNode u c)  { nodeConflicts = unitUniqSet u' }
                        u
   in   graphMapModify
        ( addNeighbor u1 c1 u2
        . addNeighbor u2 c2 u1)
delConflict
        :: Uniquable k
        => k -> k
        -> Graph k cls color -> Maybe (Graph k cls color)
delConflict k1 k2
        = modNode
                (\node -> node { nodeConflicts = delOneFromUniqSet (nodeConflicts node) k2 })
                k1
addConflicts
        :: Uniquable k
        => UniqSet k -> (k -> cls)
        -> Graph k cls color -> Graph k cls color
addConflicts conflicts getClass
        
        | (u : [])      <- nonDetEltsUniqSet conflicts
        = graphMapModify
        $ adjustWithDefaultUFM
                id
                (newNode u (getClass u))
                u
        | otherwise
        = graphMapModify
        $ \fm -> foldl' (\g u  -> addConflictSet1 u getClass conflicts g) fm
                $ nonDetEltsUniqSet conflicts
                
addConflictSet1 :: Uniquable k
                => k -> (k -> cls) -> UniqSet k
                -> UniqFM (Node k cls color)
                -> UniqFM (Node k cls color)
addConflictSet1 u getClass set
 = case delOneFromUniqSet set u of
    set' -> adjustWithDefaultUFM
                (\node -> node                  { nodeConflicts = unionUniqSets set' (nodeConflicts node) } )
                (newNode u (getClass u))        { nodeConflicts = set' }
                u
addExclusion
        :: (Uniquable k, Uniquable color)
        => k -> (k -> cls) -> color
        -> Graph k cls color -> Graph k cls color
addExclusion u getClass color
        = graphMapModify
        $ adjustWithDefaultUFM
                (\node -> node                  { nodeExclusions = addOneToUniqSet (nodeExclusions node) color })
                (newNode u (getClass u))        { nodeExclusions = unitUniqSet color }
                u
addExclusions
        :: (Uniquable k, Uniquable color)
        => k -> (k -> cls) -> [color]
        -> Graph k cls color -> Graph k cls color
addExclusions u getClass colors graph
        = foldr (addExclusion u getClass) graph colors
addCoalesce
        :: Uniquable k
        => (k, cls) -> (k, cls)
        -> Graph k cls color -> Graph k cls color
addCoalesce (u1, c1) (u2, c2)
 = let  addCoalesce u c u'
         =      adjustWithDefaultUFM
                        (\node -> node { nodeCoalesce = addOneToUniqSet (nodeCoalesce node) u' })
                        (newNode u c)  { nodeCoalesce = unitUniqSet u' }
                        u
   in   graphMapModify
        ( addCoalesce u1 c1 u2
        . addCoalesce u2 c2 u1)
delCoalesce
        :: Uniquable k
        => k -> k
        -> Graph k cls color    -> Maybe (Graph k cls color)
delCoalesce k1 k2
        = modNode (\node -> node { nodeCoalesce = delOneFromUniqSet (nodeCoalesce node) k2 })
                k1
addPreference
        :: Uniquable k
        => (k, cls) -> color
        -> Graph k cls color -> Graph k cls color
addPreference (u, c) color
        = graphMapModify
        $ adjustWithDefaultUFM
                (\node -> node { nodePreference = color : (nodePreference node) })
                (newNode u c)  { nodePreference = [color] }
                u
coalesceGraph
        :: (Uniquable k, Ord k, Eq cls, Outputable k)
        => Bool                 
                                
        -> Triv k cls color
        -> Graph k cls color
        -> ( Graph k cls color
           , [(k, k)])          
                                
coalesceGraph aggressive triv graph
        = coalesceGraph' aggressive triv graph []
coalesceGraph'
        :: (Uniquable k, Ord k, Eq cls, Outputable k)
        => Bool
        -> Triv k cls color
        -> Graph k cls color
        -> [(k, k)]
        -> ( Graph k cls color
           , [(k, k)])
coalesceGraph' aggressive triv graph kkPairsAcc
 = let
        
        cNodes  = filter (\node -> not $ isEmptyUniqSet (nodeCoalesce node))
                $ nonDetEltsUFM $ graphMap graph
                
        
        
        
        
        
        
        cList   = [ (nodeId node1, k2)
                        | node1 <- cNodes
                        , k2    <- nonDetEltsUniqSet $ nodeCoalesce node1 ]
                        
        
        
        (graph', mPairs)
                = mapAccumL (coalesceNodes aggressive triv) graph cList
        
   in   case catMaybes mPairs of
         []     -> (graph', reverse kkPairsAcc)
         pairs  -> coalesceGraph' aggressive triv graph' (reverse pairs ++ kkPairsAcc)
coalesceNodes
        :: (Uniquable k, Ord k, Eq cls)
        => Bool                 
                                
        -> Triv  k cls color
        -> Graph k cls color
        -> (k, k)               
        -> (Graph k cls color, Maybe (k, k))
coalesceNodes aggressive triv graph (k1, k2)
        | (kMin, kMax)  <- if k1 < k2
                                then (k1, k2)
                                else (k2, k1)
        
        , Just nMin     <- lookupNode graph kMin
        , Just nMax     <- lookupNode graph kMax
        
        , not $ elementOfUniqSet kMin (nodeConflicts nMax)
        , not $ elementOfUniqSet kMax (nodeConflicts nMin)
        
        , nodeId nMin /= nodeId nMax
        = coalesceNodes_merge aggressive triv graph kMin kMax nMin nMax
        
        | otherwise
        = (graph, Nothing)
coalesceNodes_merge
        :: (Uniquable k, Eq cls)
        => Bool
        -> Triv  k cls color
        -> Graph k cls color
        -> k -> k
        -> Node k cls color
        -> Node k cls color
        -> (Graph k cls color, Maybe (k, k))
coalesceNodes_merge aggressive triv graph kMin kMax nMin nMax
        
        | nodeClass nMin /= nodeClass nMax
        = error "GraphOps.coalesceNodes: can't coalesce nodes of different classes."
        | not (isNothing (nodeColor nMin) && isNothing (nodeColor nMax))
        = error "GraphOps.coalesceNodes: can't coalesce colored nodes."
        
        | otherwise
        = let
                
                node    =
                 Node   { nodeId                = kMin
                        , nodeClass             = nodeClass nMin
                        , nodeColor             = Nothing
                        
                        , nodeConflicts
                                = (unionUniqSets (nodeConflicts nMin) (nodeConflicts nMax))
                                        `delOneFromUniqSet` kMin
                                        `delOneFromUniqSet` kMax
                        , nodeExclusions        = unionUniqSets (nodeExclusions nMin) (nodeExclusions nMax)
                        , nodePreference        = nodePreference nMin ++ nodePreference nMax
                        
                        , nodeCoalesce
                                = (unionUniqSets (nodeCoalesce nMin) (nodeCoalesce nMax))
                                        `delOneFromUniqSet` kMin
                                        `delOneFromUniqSet` kMax
                        }
          in    coalesceNodes_check aggressive triv graph kMin kMax node
coalesceNodes_check
        :: Uniquable k
        => Bool
        -> Triv  k cls color
        -> Graph k cls color
        -> k -> k
        -> Node k cls color
        -> (Graph k cls color, Maybe (k, k))
coalesceNodes_check aggressive triv graph kMin kMax node
        
        
        | not aggressive
        , not $ triv (nodeClass node) (nodeConflicts node) (nodeExclusions node)
        = (graph, Nothing)
        | otherwise
        = let 
                Just graph1     = delNode kMax graph
                Just graph2     = delNode kMin graph1
                graph3          = addNode kMin node graph2
          in    (graph3, Just (kMax, kMin))
freezeNode
        :: Uniquable k
        => k                    
        -> Graph k cls color    
        -> Graph k cls color    
freezeNode k
  = graphMapModify
  $ \fm ->
    let 
        Just node = lookupUFM fm k
        node'   = node
                { nodeCoalesce          = emptyUniqSet }
        fm1     = addToUFM fm k node'
        
        freezeEdge k node
         = if elementOfUniqSet k (nodeCoalesce node)
                then node { nodeCoalesce = delOneFromUniqSet (nodeCoalesce node) k }
                else node       
                                
        fm2     = nonDetFoldUniqSet (adjustUFM_C (freezeEdge k)) fm1
                    
                    
                        $ nodeCoalesce node
    in  fm2
freezeOneInGraph
        :: (Uniquable k)
        => Graph k cls color
        -> ( Graph k cls color          
           , Bool )                     
freezeOneInGraph graph
 = let  compareNodeDegree n1 n2
                = compare (sizeUniqSet $ nodeConflicts n1) (sizeUniqSet $ nodeConflicts n2)
        candidates
                = sortBy compareNodeDegree
                $ take 5        
                $ scanGraph (\node -> not $ isEmptyUniqSet (nodeCoalesce node)) graph
   in   case candidates of
         
         []     -> (graph, False)
         
         (n : _)
          -> ( freezeNode (nodeId n) graph
             , True)
freezeAllInGraph
        :: (Uniquable k)
        => Graph k cls color
        -> Graph k cls color
freezeAllInGraph graph
        = foldr freezeNode graph
                $ map nodeId
                $ nonDetEltsUFM $ graphMap graph
                
scanGraph
        :: (Node k cls color -> Bool)
        -> Graph k cls color
        -> [Node k cls color]
scanGraph match graph
        = filter match $ nonDetEltsUFM $ graphMap graph
          
validateGraph
        :: (Uniquable k, Outputable k, Eq color)
        => SDoc                         
        -> Bool                         
        -> Graph k cls color            
        -> Graph k cls color            
validateGraph doc isColored graph
        
        | edges         <- unionManyUniqSets
                                (  (map nodeConflicts       $ nonDetEltsUFM $ graphMap graph)
                                ++ (map nodeCoalesce        $ nonDetEltsUFM $ graphMap graph))
        , nodes         <- mkUniqSet $ map nodeId $ nonDetEltsUFM $ graphMap graph
        , badEdges      <- minusUniqSet edges nodes
        , not $ isEmptyUniqSet badEdges
        = pprPanic "GraphOps.validateGraph"
                (  text "Graph has edges that point to non-existent nodes"
                $$ text "  bad edges: " <> pprUFM (getUniqSet badEdges) (vcat . map ppr)
                $$ doc )
        
        | badNodes      <- filter (not . (checkNode graph))
                        $ nonDetEltsUFM $ graphMap graph
                           
        , not $ null badNodes
        = pprPanic "GraphOps.validateGraph"
                (  text "Node has same color as one of it's conflicts"
                $$ text "  bad nodes: " <> hcat (map (ppr . nodeId) badNodes)
                $$ doc)
        
        
        | isColored
        , badNodes      <- filter (\n -> isNothing $ nodeColor n)
                        $  nonDetEltsUFM $ graphMap graph
        , not $ null badNodes
        = pprPanic "GraphOps.validateGraph"
                (  text "Supposably colored graph has uncolored nodes."
                $$ text "  uncolored nodes: " <> hcat (map (ppr . nodeId) badNodes)
                $$ doc )
        
        | otherwise
        = graph
checkNode
        :: (Uniquable k, Eq color)
        => Graph k cls color
        -> Node  k cls color
        -> Bool                 
checkNode graph node
        | Just color            <- nodeColor node
        , Just neighbors        <- sequence $ map (lookupNode graph)
                                $  nonDetEltsUniqSet $ nodeConflicts node
            
        , neighbourColors       <- catMaybes $ map nodeColor neighbors
        , elem color neighbourColors
        = False
        | otherwise
        = True
slurpNodeConflictCount
        :: Graph k cls color
        -> UniqFM (Int, Int)    
slurpNodeConflictCount graph
        = addListToUFM_C
                (\(c1, n1) (_, n2) -> (c1, n1 + n2))
                emptyUFM
        $ map   (\node
                  -> let count  = sizeUniqSet $ nodeConflicts node
                     in  (count, (count, 1)))
        $ nonDetEltsUFM
        
        $ graphMap graph
setColor
        :: Uniquable k
        => k -> color
        -> Graph k cls color -> Graph k cls color
setColor u color
        = graphMapModify
        $ adjustUFM_C
                (\n -> n { nodeColor = Just color })
                u
{-# INLINE adjustWithDefaultUFM #-}
adjustWithDefaultUFM
        :: Uniquable k
        => (a -> a) -> a -> k
        -> UniqFM a -> UniqFM a
adjustWithDefaultUFM f def k map
        = addToUFM_C
                (\old _ -> f old)
                map
                k def
{-# INLINE adjustUFM_C #-}
adjustUFM_C
        :: Uniquable k
        => (a -> a)
        -> k -> UniqFM a -> UniqFM a
adjustUFM_C f k map
 = case lookupUFM map k of
        Nothing -> map
        Just a  -> addToUFM map k (f a)