{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Properties.Store.Page where

import Test.Framework (Test, testGroup)
import Test.Framework.Providers.HUnit (testCase)
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Test.HUnit hiding (Test, Node)
import Test.QuickCheck

import Control.Applicative ((<$>))

import Data.Int
import Data.List (nub)
import Data.Monoid ((<>))
import Data.Proxy
import qualified Data.Binary as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.Map as M
import qualified Data.Vector as V

import Data.BTree.Impure.Internal.Structures
import Data.BTree.Primitives

import Database.Haskey.Store.Page

--------------------------------------------------------------------------------

tests :: Test
tests = testGroup "Store.Page"
    [ testProperty "binary pageType" prop_binary_pageType
    , testProperty "binary emptyPage" prop_binary_emptyPage
    , testProperty "binary nodePage leaf" prop_binary_leafNodePage
    , testProperty "binary nodePage idx" prop_binary_indexNodePage
    , testCase "zero checksum length" case_zero_checksum_length
    ]

prop_binary_pageType :: Property
prop_binary_pageType = forAll types $ \t ->
    let bs = B.encode t in BL.length bs == 1 && B.decode bs == t
  where
    types = elements [TypeEmpty,
                      TypeConcurrentMeta,
                      TypeOverflow,
                      TypeLeafNode,
                      TypeIndexNode]

prop_binary_emptyPage :: Bool
prop_binary_emptyPage = case decode' emptyPage (encode EmptyPage) of
    Right EmptyPage -> True
    Left _          -> False

prop_binary_leafNodePage :: Property
prop_binary_leafNodePage = forAll genLeafNode $ \leaf ->
    case decode' (leafNodePage zeroHeight key val)
                 (encode (LeafNodePage zeroHeight leaf)) of
        Right (LeafNodePage h n) -> maybe False (== leaf) $ castNode h zeroHeight n
        Left _                   -> False
 where
   key = Proxy :: Proxy Int64
   val = Proxy :: Proxy Bool

prop_binary_indexNodePage :: Property
prop_binary_indexNodePage = forAll genIndexNode $ \(srcHgt, idx) ->
    case decode' (indexNodePage srcHgt key val)
                 (encode (IndexNodePage srcHgt idx)) of
        Right (IndexNodePage h n) -> maybe False (== idx) $ castNode h srcHgt n
        Left _                    -> False
 where
   key = Proxy :: Proxy Int64
   val = Proxy :: Proxy Bool

case_zero_checksum_length :: Assertion
case_zero_checksum_length = do
    assertEqual "zero checksum should prepend 8 bytes" 8 $
        BL.length withZero' - BL.length without'
    assertEqual "zero checksum length should equal regular checksum lenth"
        (BL.length withZero')
        (BL.length with')
  where
    withZero' = encodeZeroChecksum pg
    without'  = encodeNoChecksum pg
    with'     = encode pg

    pg = LeafNodePage zeroHeight (Leaf M.empty :: Node 'Z Int64 Int64)

decode' :: SGet t -> BL.ByteString -> Either String (Page t)
decode' x = decode x . BL.toStrict

--------------------------------------------------------------------------------

genIndexNode :: Gen (Height ('S h), Node ('S h) Int64 Bool)
genIndexNode = do
    h <- genNonZeroHeight
    n <- Idx <$> arbitrary
    return (h, n)

genLeafNode :: Gen (Node 'Z Int64 Bool)
genLeafNode = Leaf <$> arbitrary

instance Arbitrary v => Arbitrary (LeafValue v) where
    arbitrary = oneof [RawValue <$> arbitrary, OverflowValue <$> arbitrary]

instance Arbitrary TxId where
    arbitrary = TxId <$> arbitrary

deriving instance Arbitrary (Height h)

genNonZeroHeight :: Gen (Height h)
genNonZeroHeight = suchThat arbitrary $ \h -> case viewHeight h of
    UZero   -> False
    USucc _ -> True

instance (Key k, Arbitrary k, Arbitrary v) => Arbitrary (Index k v) where
  arbitrary = do
      keys <- V.fromList . nub <$> orderedList
      vals <- V.fromList <$> vector (V.length keys + 1)
      return (Index keys vals)
  shrink (Index keys vals) =
      [ Index newKeys newVals
      | k <- [0..V.length keys - 1]
      , let (preKeys,sufKeys) = V.splitAt k keys
            newKeys           = preKeys <> V.drop 1 sufKeys
            (preVals,sufVals) = V.splitAt k vals
            newVals           = preVals <> V.drop 1 sufVals
      ]

deriving instance Arbitrary (NodeId height key val)
