    module CmdLine.Test(testFile) where

    import Hoogle.All
    import Hoogle.Item.All
    import General.Code
    import Data.Binary.Defer.Index

    testFile :: FilePath -> FilePath -> IO ()
    testFile srcfile dbfile = do
        putStrLn $ "Testing " ++ srcfile
        db <- loadDataBase dbfile
        src <- readFile srcfile
        let bad = filter (not . runTest db) $ catMaybes $ zipWith parseTest [1..] $ lines src
        if null bad then
            putStrLn "All tests passed"
         else do
            putStr $ unlines $ map failedTest bad
            putStrLn $ show (length bad) ++ " tests failed"

    -- LineNo Query NoResults YesResults
    -- NoResults is a list of results that are not allowed to appear
    -- YesResults are sets of results, which must be in order, and within a set must
    --            have the same Score
    data Test = Test Int String Query [String] [[String]]
                deriving Show

    parseTest :: Int -> String -> Maybe Test
    parseTest line str | "@test " `isPrefixOf` str =
        case reads $ drop 5 str of
            [(x,rest)] -> case parseQuery x of
                Right q -> let (no,yes) = partition ("!" `isPrefixOf`) $ words rest
                           in Just $ Test line x q (map tail no) (map (split ',') yes)
                _ -> err
            _ -> err
        where err = error $ "Couldn't parse @test on line " ++ show line
    parseTest line str = Nothing

    runTest :: DataBase -> Test -> Bool
    runTest db (Test _ _ q bad ans) =
            ordered (group $ map resultScore res) &&         -- all results are in order
            all (`elem` map fst items) (concat ans) &&       -- all items are present
            ordered (map (map (`lookupJust` items)) ans) &&  -- all items are in order
            all (`notElem` map fst items) bad                -- all the bad items are absent
        where
            res = searchAll [db] q
            items = map (entryName . fromLink . resultEntry &&& resultScore) res

            ordered ((x:xs):(y:ys):zs) = x < y && all (== x) xs && ordered ((y:ys):zs)
            ordered [x:xs] = all (== x) xs
            ordered [] = True

    failedTest :: Test -> String
    failedTest (Test line str _ _ _) = "Line " ++ show line ++ ", " ++ str