------------------------------------------------------------------------
-- |
-- Module      :  Math.Geometry.GridMap.LazyQC
-- Copyright   :  (c) Amy de Buitléir 2012-2017
-- License     :  BSD-style
-- Maintainer  :  amy@nualeargais.ie
-- Stability   :  experimental
-- Portability :  portable
--
-- QuickCheck tests.
--
------------------------------------------------------------------------
{-# LANGUAGE FlexibleContexts, ExistentialQuantification, TypeFamilies,
    MultiParamTypeClasses, FlexibleInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Math.Geometry.GridMap.LazyQC
  (
    test
  ) where

import Data.List ((\\), foldl', intersect)
import Data.Maybe (isJust)
import qualified Math.Geometry.GridMap as GM
import Math.Geometry.GridMap.Lazy
import qualified Math.Geometry.Grid as G
import Math.Geometry.Grid.Square (RectSquareGrid, rectSquareGrid)
import Test.Framework (Test, testGroup)
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Test.QuickCheck 
  ((==>), Gen, Arbitrary, arbitrary, choose, Property, property,
    vectorOf, elements, sized)

-- We want the number of tiles in a test grid to be O(n)
sizedRectSquareGrid :: Int -> Gen RectSquareGrid
sizedRectSquareGrid n = do
  r <- choose (0,n)
  let c = n `div` (r+1)
  return $ rectSquareGrid r c

sizedGridMap :: Int -> Gen (LGridMap RectSquareGrid Int)
sizedGridMap n = do
  g <- sizedRectSquareGrid n
  vs <- vectorOf (G.tileCount g) arbitrary
  let gm = lazyGridMap g vs
  if G.null g
    then return gm
    else do
      -- Arbitrarily delete some values from the map
      m <- choose (0,n)
      ks <- sequence . replicate m . elements . G.indices $ g
      return $ foldl' (flip GM.delete) gm ks

instance Arbitrary (LGridMap RectSquareGrid Int) where
  arbitrary = sized sizedGridMap

-- data GridMapTD = GridMapTD (LGridMap RectSquareGrid Int) (Int,Int)
--   deriving (Eq, Show)

-- sizedGridMapTD :: Int -> Gen GridMapTD
-- sizedGridMapTD n = do
--   gm <- sizedGridMap (n+1)
--   k <- elements . indices $ gm
--   return $ GridMapTD gm k

-- instance Arbitrary GridMapTD where
--   arbitrary = sized sizedGridMapTD

selectIndex :: LGridMap RectSquareGrid Int -> Int -> (Int, Int)
selectIndex gm n = G.indices gm !! k
  where k = n `mod` G.tileCount gm

isSubsetOf :: Eq a => [a] -> [a] -> Bool
isSubsetOf xs ys = Prelude.null (xs \\ ys)

mapValid :: LGridMap RectSquareGrid Int -> Bool
mapValid gm = keysWithValues `isSubsetOf` keys
  where keysWithValues = map fst . GM.toList $ gm
        keys = G.indices . GM.toGrid $ gm

prop_lookup_same_as_bang :: LGridMap RectSquareGrid Int -> Int -> Property
prop_lookup_same_as_bang gm n
  = G.nonNull gm && isJust (GM.lookup k gm) ==>
    Just (gm GM.! k) == GM.lookup k gm
  where k = selectIndex gm n

prop_insert_works :: LGridMap RectSquareGrid Int -> Int -> Int -> Property
prop_insert_works gm v n = G.nonNull gm ==> gm' GM.! k == v
  where k = selectIndex gm n
        gm' = GM.insert k v gm

prop_insert_never_alters_grid
  :: LGridMap RectSquareGrid Int -> (Int, Int) -> Int -> Property
prop_insert_never_alters_grid gm k v = property $
  GM.toGrid (GM.insert k v gm) == GM.toGrid gm

prop_insert_never_invalid
  :: LGridMap RectSquareGrid Int -> (Int, Int) -> Int -> Property
prop_insert_never_invalid gm k v = property $
  mapValid (GM.insert k v gm)

prop_delete_works :: LGridMap RectSquareGrid Int -> Int -> Property
prop_delete_works gm n = G.nonNull gm ==> GM.lookup k gm' == Nothing
  where k = selectIndex gm n
        gm' = GM.delete k gm

prop_delete_never_alters_grid
  :: LGridMap RectSquareGrid Int -> (Int, Int) -> Property
prop_delete_never_alters_grid gm k = property $
  GM.toGrid (GM.delete k gm) == GM.toGrid gm

prop_delete_never_invalid
  :: LGridMap RectSquareGrid Int -> (Int, Int) -> Property
prop_delete_never_invalid gm k = property $
  mapValid (GM.delete k gm)

prop_lazyGridMapIndexed_adds_all_valid_keys
  :: Int -> Int -> [(G.Index RectSquareGrid, Int)] -> Property
prop_lazyGridMapIndexed_adds_all_valid_keys n m kvs = property $
  (GM.keys gm) `intersect` (G.indices g) == GM.keys gm
  where gm = lazyGridMapIndexed g kvs
        g = rectSquareGrid n m

prop_lazyGridMapIndexed_never_adds_invalid_keys
  :: Int -> Int -> [(G.Index RectSquareGrid, Int)] -> Property
prop_lazyGridMapIndexed_never_adds_invalid_keys n m kvs = property $
  null ((GM.keys gm) \\ (G.indices g))
  where gm = lazyGridMapIndexed g kvs
        g = rectSquareGrid n m

test :: Test
test = testGroup "Math.Geometry.GridMap.LazyQC"
  [
    testProperty "prop_lookup_same_as_bang" prop_lookup_same_as_bang,
    testProperty "prop_insert_works" prop_insert_works,
    testProperty "prop_insert_never_alters_grid"
      prop_insert_never_alters_grid,
    testProperty "prop_insert_never_invalid" prop_insert_never_invalid,
    testProperty "prop_delete_works" prop_delete_works,
    testProperty "prop_delete_never_alters_grid"
      prop_delete_never_alters_grid,
    testProperty "prop_delete_never_invalid" prop_delete_never_invalid,
    testProperty "prop_lazyGridMapIndexed_adds_all_valid_keys"
      prop_lazyGridMapIndexed_adds_all_valid_keys,
    testProperty "prop_lazyGridMapIndexed_never_adds_invalid_keys"
      prop_lazyGridMapIndexed_never_adds_invalid_keys
  ]