{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE FlexibleInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module UnitTests.Distribution.Compat.Graph
    ( tests
    , arbitraryGraph
    ) where

import Distribution.Compat.Graph

import qualified Prelude
import Prelude hiding (null)
import Test.Tasty
import Test.Tasty.QuickCheck
import qualified Data.Set as Set
import Control.Monad
import qualified Data.Graph as G
import Data.Array ((!))
import Data.Maybe
import Data.List (sort)

tests :: [TestTree]
tests =
    [ testProperty "arbitrary unbroken" (prop_arbitrary_unbroken :: Graph (Node Int ()) -> Bool)
    , testProperty "nodes consistent" (prop_nodes_consistent :: Graph (Node Int ()) -> Bool)
    , testProperty "edges consistent" (prop_edges_consistent :: Graph (Node Int ()) -> Property)
    , testProperty "closure consistent" (prop_closure_consistent :: Graph (Node Int ()) -> Property)
    ]

-- Our arbitrary instance does not generate broken graphs
prop_arbitrary_unbroken :: Graph a -> Bool
prop_arbitrary_unbroken g = Prelude.null (broken g)

-- Every node from 'toList' maps to a vertex which
-- is present in the constructed graph, and maps back
-- to a node correctly.
prop_nodes_consistent :: (Eq a, IsNode a) => Graph a -> Bool
prop_nodes_consistent g = all p (toList g)
  where
    (_, vtn, ktv) = toGraph g
    p n = case ktv (nodeKey n) of
            Just v  -> vtn v == n
            Nothing -> False

-- A non-broken graph has the 'nodeNeighbors' of each node
-- equal the recorded adjacent edges in the node graph.
prop_edges_consistent :: IsNode a => Graph a -> Property
prop_edges_consistent g = Prelude.null (broken g) ==> all p (toList g)
  where
    (gr, vtn, ktv) = toGraph g
    p n = sort (nodeNeighbors n)
       == sort (map (nodeKey . vtn) (gr ! fromJust (ktv (nodeKey n))))

-- Closure is consistent with reachable
prop_closure_consistent :: (Show a, IsNode a) => Graph a -> Property
prop_closure_consistent g =
    not (null g) ==>
    forAll (elements (toList g)) $ \n ->
           Set.fromList (map nodeKey (fromJust (closure g [nodeKey n])))
        == Set.fromList (map (nodeKey . vtn) (G.reachable gr (fromJust (ktv (nodeKey n)))))
  where
    (gr, vtn, ktv) = toGraph g

hasNoDups :: Ord a => [a] -> Bool
hasNoDups = loop Set.empty
  where
    loop _ []       = True
    loop s (x:xs) | s' <- Set.insert x s, Set.size s' > Set.size s
                    = loop s' xs
                  | otherwise
                    = False

-- | Produces a graph of size @len@.  We sample with 'suchThat'; if we
-- dropped duplicate entries our size could be smaller.
arbitraryGraph :: (Ord k, Show k, Arbitrary k, Arbitrary a)
               => Int -> Gen (Graph (Node k a))
arbitraryGraph len = do
    -- Careful! Assume k is much larger than size.
    ks <- vectorOf len arbitrary `suchThat` hasNoDups
    ns <- forM ks $ \k -> do
        a <- arbitrary
        ns <- listOf (elements ks)
        -- Allow duplicates!
        return (N a k ns)
    return (fromDistinctList ns)

instance (Ord k, Show k, Arbitrary k, Arbitrary a)
      => Arbitrary (Graph (Node k a)) where
    arbitrary = sized $ \n -> do
        len <- choose (0, n)
        arbitraryGraph len
