    module Hoogle.Search.Results(
        mergeDataBaseResults, mergeQueryResults
        ) where

    import General.Code
    import Data.Key
    import qualified Data.IntMap as IntMap
    import Data.Binary.Defer.Index

    import Hoogle.Item.All
    import Hoogle.DataBase.All
    import Hoogle.Query.All
    import Hoogle.Search.Result

    ---------------------------------------------------------------------
    -- MERGE DATABASE

    mergeDataBaseResults :: [[Result]] -> [Result]
    mergeDataBaseResults = map fromKey . fold [] merge . map (map $ toKey f)
        where f r = (resultScore r, entryScore $ fromLink $ resultEntry r)

    entryScoreModule :: Maybe Module -> Entry -> EntryScore
    entryScoreModule mod e = EntryScore
        (length m) (map toLower $ entryName e) (entryName e) m
        where m = maybe [] moduleName mod

    ---------------------------------------------------------------------
    -- MERGE QUERY

    -- each query is correct, elements can be ordered by entry Id
    mergeQueryResults :: Query -> [[Result]] -> [Result]
    mergeQueryResults q = filterResults q . joinResults

    -- join the results of multiple searches
    joinResults :: [[Result]] -> [Result]
    joinResults [] = []
    joinResults [x] = x
    joinResults xs = sortWith scr $ IntMap.elems $
                     fold1 (IntMap.intersectionWith join) $
                     map asSet xs
        where
            asSet = IntMap.fromList . map (linkKey . resultEntry &&& id)

            join r1 r2 = r1{resultScore = sort $ resultScore r1 ++ resultScore r2
                           ,resultView = resultView r1 ++ resultView r2}

            scr = resultScore &&& linkKey . resultEntry

    ---------------------------------------------------------------------
    -- FILTER

    -- | Apply the PlusModule, MinusModule and MinusPackage modes
    filterResults :: Query -> [Result] -> [Result]
    filterResults q = f mods correctModule . f pkgs correctPackage
        where
            f [] act = id
            f xs act = filter (act xs . fromLink . resultEntry)

            mods = filter (\x -> isPlusModule x || isMinusModule x) $ scope q
            pkgs = [x | MinusPackage x <- scope q]

    -- pkgs is a non-empty list of MinusPackage values
    correctPackage :: [String] -> Entry -> Bool
    correctPackage pkgs = (`notElem` pkgs) . packageName . fromLink . entryPackage

    -- mods is a non-empty list of PlusModule/MinusModule
    correctModule :: [Scope] -> Entry -> Bool
    correctModule mods = maybe True (f base mods . moduleName . fromLink) . entryModule
        where
            base = isMinusModule $ head mods

            f z [] y = z
            f z (PlusModule  x:xs) y | doesMatch x y = f True  xs y
            f z (MinusModule x:xs) y | doesMatch x y = f False xs y
            f z (x:xs) y = f z xs y

            -- match if x is further up the tree than y
            doesMatch [] y = True
            doesMatch (x:xs) (y:ys) = x == y && doesMatch xs ys
            doesMatch _ _ = False