{-# LANGUAGE CPP, GeneralizedNewtypeDeriving, OverloadedStrings #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, Rank2Types #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Properties.Map
    where

import qualified Data.ByteString.Char8 as B
import Data.CritBit.Map.Lazy (CritBitKey, CritBit, byteCount)
import qualified Data.CritBit.Map.Lazy as C
import qualified Data.CritBit.Set as CSet
import Data.Foldable (foldMap)
import Data.Function (on)
import Data.List (unfoldr, sort, nubBy)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Monoid (Sum(..))
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Data.Vector.Generic as G
import qualified Data.Vector.Unboxed as U
import Data.Word
import Properties.Common
import Test.Framework (Test, testGroup)
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Test.QuickCheck.Property ((.&&.))

--only needed for a test requiring containers >= 0.5
#if MIN_VERSION_containers(0,5,0)
import Data.Functor.Identity (Identity(..))
#endif

type V = Word8

-- * Common modifier functions

kvvf :: (CritBitKey k) => k -> V -> V -> V
kvvf k v1 v2 = fromIntegral (byteCount k) * 3 + v1 * 2 - v2

kvvfm :: (CritBitKey k) => k -> V -> V -> Maybe V
kvvfm k v1 v2 = if even v1 then Just (kvvf k v1 v2) else Nothing

kvf :: (CritBitKey k) => k -> V -> V
kvf k v = kvvf k v 0

kvfm :: (CritBitKey k) => k -> V -> Maybe V
kvfm k v = kvvfm k v 0

vvfm :: V -> V -> Maybe V
vvfm = kvvfm ("" :: T.Text)

vfm :: V -> Maybe V
vfm = kvfm ("" :: T.Text)

propertiesFor :: Props k
propertiesFor w = concat [[]
  -- ** Lists
  , prop sa "t_fromList" $
        (C.fromList =*== Map.fromList) id
  , prop sa "t_fromListWith" $
        (C.fromListWith (-) =*== Map.fromListWith (-)) id
  , prop sa "t_fromListWithKey" $
        (C.fromListWithKey kvvf =*== Map.fromListWithKey kvvf) id

    -- * Query
  , prop sa "t_null" $
        C.null =*= Map.null
  , prop sa "t_size" $
        C.size =*= Map.size
  , prop sa "t_member" $
        C.member =?*= Map.member
  , prop sa "t_member" $
        C.notMember =?*= Map.notMember
  , prop sa "t_lookup" $
        C.lookup =?*= Map.lookup
  , prop sa "t_findWithDefault" $
        C.findWithDefault =??*= Map.findWithDefault

#if MIN_VERSION_containers(0,5,0)
  , prop sa "t_lookupGT" $
        C.lookupGT =?*= Map.lookupGT
  , prop sa "t_lookupGE" $
        C.lookupGE =?*= Map.lookupGE
  , prop sa "t_lookupLT" $
        C.lookupLT =?*= Map.lookupLT
  , prop sa "t_lookupLE" $
        C.lookupLE =?*= Map.lookupLE
#endif

  -- * Insertion
  , pmprop sa "t_insert" $
        C.insert =??*= Map.insert
  , pmprop sa "t_insertWith" $
        C.insertWith (-) =??*= Map.insertWith (-)
  , pmprop sa "t_insertWithKey" $
        C.insertWithKey kvvf =??*= Map.insertWithKey kvvf
  , pmprop sa "t_insertLookupWithKey" $
        C.insertLookupWithKey kvvf =??*= Map.insertLookupWithKey kvvf

  -- * Deletion
  , pmprop sa "t_delete" $
        C.delete =?*= Map.delete
  , pmprop sa "t_adjust" $
        C.adjust (+3) =?*= Map.adjust (+3)
  , pmprop sa "t_adjustWithKey" $
        C.adjustWithKey kvf =?*= Map.adjustWithKey kvf
  , pmprop sa "t_update" $
        C.update vfm =?*= Map.update vfm
  , pmprop sa "t_updateWithKey" $
        C.updateWithKey kvfm =?*= Map.updateWithKey kvfm
  , pmprop sa "t_updateLookupWithKey" $
        C.updateLookupWithKey kvfm =?*= Map.updateLookupWithKey kvfm
  , prop sa "t_alter" $
        let f = Just . maybe 1 (+1)
        in C.alter f =?*= Map.alter f
  , prop sa "t_alter_delete" $
        C.alter (const Nothing) =?*= Map.alter (const Nothing)

  -- * Combination
  -- ** Union
  , prop sa "t_union" $
        C.union =**= Map.union
  , prop sa "t_unionWith" $
        C.unionWith (-) =**= Map.unionWith (-)
  , prop sa "t_unionWithKey" $
        C.unionWithKey kvvf =**= Map.unionWithKey kvvf
  , prop sa "t_unions" $
        (  C.unions . map   C.fromList =*==
         Map.unions . map Map.fromList) fromSmall
  , prop sa "t_unionsWith" $
        (  C.unionsWith (-) . map   C.fromList =*==
         Map.unionsWith (-) . map Map.fromList) fromSmall
  , prop sa "t_unionL" $
        C.unionL =**= Map.union
  , prop sa "t_unionR" $
        C.unionR =**= flip Map.union

  -- ** Difference
  , prop sa "t_difference" $
        C.difference =**= Map.difference
  , prop sa "t_differenceWith" $
        C.differenceWith vvfm =**= Map.differenceWith vvfm
  , prop sa "t_differenceWithKey" $
        C.differenceWithKey kvvfm =**= Map.differenceWithKey kvvfm

  -- ** Intersection
  , prop sa "t_intersection" $
        C.intersection =**= Map.intersection
  , prop sa "t_intersectionWith" $
        C.intersectionWith (-) =**= Map.intersectionWith (-)
  , prop sa "t_intersectionWithKey" $
        C.intersectionWithKey kvvf =**= Map.intersectionWithKey kvvf

  -- * Traversal
  -- ** Map
  , prop sa "t_map" $
        C.map (+3) =*= Map.map (+3)
  , prop sa "t_mapWithKey" $
        C.mapWithKey kvf =*= Map.mapWithKey kvf
#if MIN_VERSION_containers(0,5,0)
  , prop sa "t_traverseWithKey" $
      let f _ = Identity . show . (+3)
      in runIdentity . C.traverseWithKey f =*= runIdentity . Map.traverseWithKey f
#endif
  , prop sa "t_mapAccum" $
        let f i v = (i + 1 :: Int, show $ v + 3)
        in C.mapAccum f 0 =*= Map.mapAccum f 0
  , prop sa "t_mapAccumWithKey" $
        let f i k v = (i + byteCount k, show $ v + 3)
        in C.mapAccumWithKey f 0 =*= Map.mapAccumWithKey f 0
  , prop sa "t_mapAccumRWithKey" $
        let f i k v = (i + byteCount k, show $ v + 3)
        in C.mapAccumRWithKey f 0 =*= Map.mapAccumRWithKey f 0
  , prop sa "t_mapKeys" $
        C.mapKeys kf =*= Map.mapKeys kf
  , prop sa "t_mapKeysWith" $
        C.mapKeysWith (+) kf =*= Map.mapKeysWith (+) kf
  , prop sa "t_mapKeysMonotonic" $
        C.mapKeysMonotonic prepends =*= Map.mapKeysMonotonic prepends

  -- * Folds
  , prop sa "t_foldl" $
        C.foldl (-) 0 =*= Map.foldl (-) 0
  , prop sa "t_foldlWithKey" $
        let f i k v = i * 37 + (byteCount k) * 17 + fromIntegral v
        in C.foldlWithKey f 0 =*= Map.foldlWithKey f 0
  , prop sa "t_foldr" $
        C.foldr (-) 0 =*= Map.foldr (-) 0
  , prop sa "t_foldrWithKey" $
        let f k v i = i * 37 + (byteCount k) * 17 + fromIntegral v
        in C.foldrWithKey f 0 =*= Map.foldrWithKey f 0

  -- ** Strict folds
  , prop sa "t_foldl'" $
        C.foldl' (-) 0 =*= Map.foldl' (-) 0
  , prop sa "t_foldlWithKey'" $
        let f i k v = i * 37 + (byteCount k) * 17 + fromIntegral v
        in C.foldlWithKey' f 0 =*= Map.foldlWithKey' f 0
  , prop sa "t_foldr'" $
        C.foldr' (-) 0 =*= Map.foldr' (-) 0
  , prop sa "t_foldrWithKey'" $
        let f k v i = i * 37 + (byteCount k) * 17 + fromIntegral v
        in C.foldrWithKey' f 0 =*= Map.foldrWithKey' f 0

  -- * Conversion
  , prop sa "t_elems" $
        C.elems =*= Map.elems
  , prop sa "t_keys" $
        C.keys =*= Map.keys
  , prop sa "assocs" $
        C.assocs =*= Map.assocs
  , prop sa "t_keysSet" $
        CSet.toList . C.keysSet =*= Set.toList . Map.keysSet
#if MIN_VERSION_containers(0,5,0)
  , prop sa "t_fromSet" $
        let f = length . show
        in C.fromSet f . C.keysSet =*= Map.fromSet f . Map.keysSet
#endif

  -- ** Ordered lists
  , prop sa "t_toAscList" $
        C.toAscList =*= Map.toAscList
  , prop sa "t_toDescList" $
        C.toDescList =*= Map.toDescList
  , prop sa "t_fromAscList" $
        (C.fromAscList =*== Map.fromAscList) sort
  , prop sa "t_fromAscListWith" $
        (C.fromAscListWith (+) =*== Map.fromAscListWith (+)) sort
  , prop sa "t_fromAscListWithKey" $
        (C.fromAscListWithKey kvvf =*== Map.fromAscListWithKey kvvf) sort
  , prop sa "t_fromDistinctAscList" $
        let p = nubBy ((==) `on` fst) . sort
        in (C.fromDistinctAscList =*== Map.fromDistinctAscList) p

  -- * Filter
  , prop sa "t_filter" $
        C.filter odd =*= Map.filter odd
  , prop sa "t_filterWithKey" $
        let p k v = odd $ kvf k v
        in C.filterWithKey p =*= Map.filterWithKey p
  , prop sa "t_partition" $ C.partition odd =*= Map.partition odd
  , prop sa "t_partitionWithKey" $
       let p k v = odd $ kvf k v
       in C.partitionWithKey p =*= Map.partitionWithKey p

  , prop sa "t_mapMaybe" $
        C.mapMaybe vfm =*= Map.mapMaybe vfm
  , prop sa "t_mapMaybeWithKey" $
        C.mapMaybeWithKey kvfm =*= Map.mapMaybeWithKey kvfm
  , prop sa "t_mapEither" $
        let f v = if even v then Left (2 * v) else Right (3 * v)
        in C.mapEither f =*= Map.mapEither f
  , prop sa "t_mapEitherWithKey" $
        let f k v = if even v then Left (kvf k v) else Right (3 * v)
        in C.mapEitherWithKey f =*= Map.mapEitherWithKey f

  , pmprop sa "t_split" $
        C.split =?*= Map.split
  , pmprop sa "t_splitLookup" $
        C.splitLookup =?*= Map.splitLookup

  -- * Submap
  , prop sa "t_isSubmapOf" $
        C.isSubmapOf =**= Map.isSubmapOf
  , prop sa "t_isSubmapOfBy" $
        C.isSubmapOfBy (<=) =**= Map.isSubmapOfBy (<=)
  , prop sa "t_isProperSubmapOf" $
        C.isProperSubmapOf =**= Map.isProperSubmapOf
  , prop sa "t_isProperSubmapOfBy" $
        C.isProperSubmapOfBy (<=) =**= Map.isProperSubmapOfBy (<=)

  -- * Min\/Max
  , prop sa "t_findMin" $ notEmpty $
        C.findMin =*= Map.findMin
  , prop sa "t_findMax" $ notEmpty $
        C.findMax =*= Map.findMax
  , prop sa "t_deleteMin" $ notEmpty $
        C.deleteMin =*= Map.deleteMin
  , prop sa "t_deleteMax" $ notEmpty $
        C.deleteMax =*= Map.deleteMax
  , prop sa "t_deleteFindMin" $ notEmpty $
        C.deleteFindMin =*= Map.deleteFindMin
  , prop sa "t_deleteFindMax" $ notEmpty $
        C.deleteFindMax =*= Map.deleteFindMax
  , prop sa "t_updateMin" $
        C.updateMinWithKey kvfm =*= Map.updateMinWithKey kvfm
  , prop sa "t_updateMax" $
        C.updateMaxWithKey kvfm =*= Map.updateMaxWithKey kvfm
  , prop sa "t_updateMinWithKey" $
        C.updateMinWithKey kvfm =*= Map.updateMinWithKey kvfm
  , prop sa "t_updateMaxWithKey" $
        C.updateMaxWithKey kvfm =*= Map.updateMaxWithKey kvfm
  , prop sa "t_minView" $
        unfoldr C.minView =*= unfoldr Map.minView
  , prop sa "t_maxView" $
        unfoldr C.maxView =*= unfoldr Map.maxView
  , prop sa "t_minViewWithKey" $
        unfoldr C.minViewWithKey =*= unfoldr Map.minViewWithKey
  , prop sa "t_maxViewWithKey" $
        unfoldr C.maxViewWithKey =*= unfoldr Map.maxViewWithKey

  , prop sa "t_foldMap" $
        foldMap Sum =*= foldMap Sum
  ]
  where
    prop sa' name q = [testProperty name $ q sa']
    pmprop sa' name t = [
       testProperty (name ++ "_general") $ general
     , testProperty (name ++ "_present") $ present
     , testProperty (name ++ "_missing") $ missing
     ]
     where
       general k   kvs = t sa' kvs k
       present k v kvs = t sa' ((k, v):kvs) k
       missing k   kvs = t sa' (filter ((/= k) . fst) kvs) k

    sa = sameAs w

    sameAs :: (CritBitKey k, Ord k)
           => k -> SameAs (CritBit k V) (Map k V) [(k, V)]
    sameAs _ = SameAs C.fromList C.toList Map.fromList Map.toList

properties :: [Test]
properties = [
    testGroup "text" $ propertiesFor T.empty
  , testGroup "bytestring" $ propertiesFor B.empty
  , testGroup "vector" [
      testGroup "unboxed" [
        testGroup "Word8" $ propertiesFor (G.empty :: U.Vector Word8)
      , testGroup "Word16" $ propertiesFor (G.empty :: U.Vector Word16)
      , testGroup "Word32" $ propertiesFor (G.empty :: U.Vector Word32)
      , testGroup "Word64" $ propertiesFor (G.empty :: U.Vector Word64)
      , testGroup "Word" $ propertiesFor (G.empty :: U.Vector Word)
      , testGroup "Char" $ propertiesFor (G.empty :: U.Vector Char)
      ]
  ]
  ]

instance (Eq k, Show k, Eq v, Show v) => Eq' (CritBit k v) (Map k v) where
   c =^= m = C.toList c =^= Map.toList m

instance (Eq' a1 b1, Eq k, Show k, Eq v, Show v) => Eq' (a1, CritBit k v) (b1, Map k v) where
  (a1, a2) =^= (b1, b2) = a1 =^= b1 .&&. a2 =^= b2

-- Handy functions for fiddling with from ghci.

blist :: [B.ByteString] -> CritBit B.ByteString Word8
blist = C.fromList . flip zip [0..]

tlist :: [T.Text] -> CritBit T.Text Word8
tlist = C.fromList . flip zip [0..]

mlist :: [B.ByteString] -> Map B.ByteString Word8
mlist = Map.fromList . flip zip [0..]
