{-# LANGUAGE StandaloneDeriving, GeneralizedNewtypeDeriving #-}
module Array.Properties where
import Array
import Array.ShowInstances
import Array.EqInstances
import Test.QuickCheck
import Monad(liftM)

prop_L1 :: (ArrayElem a, Eq a) => [a] -> Property
prop_L1 xs = not (null xs) ==>
  forAll (choose (0, length xs - 1)) $ \i ->
    fromList xs ! (Index i)  ==  xs !! i

test_L1Int  = quickCheck (prop_L1 :: [Int]       -> Property)
test_L1Pair = quickCheck (prop_L1 :: [(Int,Int)] -> Property)
test_L1Nest = quickCheck (prop_L1 :: [Array Int] -> Property)

prop_L2 :: (ArrayElem a, Eq a) => [a] -> Bool
prop_L2 xs = toList (fromList xs) == xs

test_L2Int  = quickCheck (prop_L2 :: [Int] -> Bool)
test_L2Pair = quickCheck (prop_L2 :: [(Int,Int)] -> Bool)
test_L2Nest = quickCheck (prop_L2 :: [Array Int] -> Bool)

sliceModel :: [a] -> (Index, Size) -> [a]
sliceModel xs (Index i, Size n) = take n (drop i xs)
toModel :: ArrayElem a => Array a -> [a]
toModel = toList

(~=) :: (ArrayElem a, Eq a) => [a] -> Array a -> Bool
model ~= impl  =  model == toModel impl

prop_L3 :: (ArrayElem a, Eq a) => [a] -> (Index, Size) -> Bool
prop_L3 xs p = sliceModel xs p ~= slice (fromList xs) p

q :: (Testable prop) => prop -> IO ()
q = quickCheck

test_L3Int  = q (prop_L3 :: [Int]       -> (Index, Size) -> Bool)
test_L3Pair = q (prop_L3 :: [(Int,Int)] -> (Index, Size) -> Bool)
test_L3Nest = q (prop_L3 :: [Array Int] -> (Index, Size) -> Bool)

instance (Arbitrary a, ArrayElem a) => Arbitrary (Array a) where
  arbitrary = liftM fromList arbitrary

instance Arbitrary Index where -- no negative indices
  arbitrary = liftM Index $ sized (\n-> choose (0,n)) 

instance Arbitrary Size where -- no negative sizes
  arbitrary = liftM Size $ sized (\n-> choose (0,n)) 

main = sequence_ [ test_L1Int, test_L1Pair, test_L1Nest
                 , test_L2Int, test_L2Pair, test_L2Nest
                 , test_L3Int, test_L3Pair, test_L3Nest
                 ]

{-
An earlier version had

*Array.Properties> test_L3Nest
*** Failed! Falsifiable (after 4 tests):                  
[ArrInt [2],ArrInt [-1,-2,-1]]
(Index 1,Size 1)

The problem was that slice for nested arrays did not preserve the
invariant that the ins :: Array (Index, Size) should satisfy.

In fact, it would be more convenient if the full sum (the element
dropped by init) was also stored.

-}

test1l :: [Array Int]
test1l = [ArrInt [-2,0] ,ArrInt [],ArrInt [0]]
test1a :: Array (Array Int)
test1a = fromList test1l

-- Untested code - partial solution to the exercise.
prop_invariant :: (Num b) => [(b, b)] -> Bool
prop_invariant iss = is == init is'
  where (is, ns) = unzip iss
        is' = scanl (+) 0 ns