{-# LANGUAGE TypeOperators #-}

module Test.Data.Store01
( tests
) where

--------------------------------------------------------------------------------
import Control.Arrow
import Control.Applicative
--------------------------------------------------------------------------------
import Test.Framework
import Test.Framework.Providers.QuickCheck2
import Test.QuickCheck
--------------------------------------------------------------------------------
import qualified Data.List as L
import           Data.Maybe
import qualified Data.Foldable
import qualified Data.Set
import qualified Data.IntSet
--------------------------------------------------------------------------------
import qualified Data.Store as S
import           Data.Store (M, O, (.:), (.:.), (:.)(..), (.<), (.<=), (.>), (.>=), (./=), (.==), (.&&), (.||))
--------------------------------------------------------------------------------

data D = D
    { dOM :: Int
    , dMO :: [Int]
    , dMM :: [Int]
    } deriving (Eq, Ord, Show)

type DID = Int

data DStoreTag = DStoreTag

type DSTS  = DID :. Int :. Int :. Int
type DSKRS = O       :. O   :. M   :. M 
type DSIRS = O       :. M   :. O   :. M 
type DS = S.Store DStoreTag DSKRS DSIRS DSTS D
type DSKey = S.Key DSKRS DSTS
type DSSelection = S.Selection DStoreTag DSKRS DSIRS DSTS

sOO :: (DStoreTag, S.N0)
sOO = (DStoreTag, S.n0)

sOM :: (DStoreTag, S.N1)
sOM = (DStoreTag, S.n1)

sMO :: (DStoreTag, S.N2)
sMO = (DStoreTag, S.n2)

sMM :: (DStoreTag, S.N3)
sMM = (DStoreTag, S.n3)

makeKey :: Int -> Int -> [Int] -> [Int] -> DSKey
makeKey oo om mo mm = 
    S.dimO oo .: S.dimO om .: S.dimM mo .:. S.dimM mm

vkey :: D -> DSKey
vkey (D om mo mm) =  
    S.dimA .: S.dimO om .: S.dimM mo .:. S.dimM mm


vkey' :: Int -> D -> DSKey
vkey' i (D om mo mm) = 
    S.dimO i .: S.dimO om .: S.dimM mo .:. S.dimM mm

tests :: [Test]
tests =
    [ testProperty "insert1" prop_insert1
    , testProperty "insert2" prop_insert2
   
    , testProperty "insert'1" prop_insert'1

    , testProperty "lookup1" prop_lookup1
    , testProperty "lookupOrderBy1" prop_lookupOrderBy1
    
    , testProperty "update1" prop_update1
    , testProperty "update2" prop_update2
    , testProperty "update3" prop_update3
    ]

-- | Tests insert (auto-incrementation) #1.
prop_insert1 (D om mo mm) =
    case S.insert (vkey v) v emptyS of
      Just (i :. _, store) -> (i == minBound) && (S.size store == 1)
      _ -> False

    where
      emptyS :: DS
      emptyS = S.empty

      v = D om (L.nub mo) (L.nub mm)

-- | Tests insert (auto-incrementation) #2.
prop_insert2 = ids == map fst inserts
    where
      (i0 :. _, s0) = fromJust $ S.insert (vkey $ mval 0) (mval 0) S.empty

      inserts :: [(Int, DS)]
      inserts =
        foldl (\acc@((_, s') : _) v -> let (i :. _, s) = fromJust $ S.insert (vkey v) v s'
                                       in  (i, s) : acc
              ) [(i0, s0)] ds

      ids :: [Int]
      ids = reverse . take 100 $ iterate succ minBound

      ds :: [D]
      ds = map mval [1..99]

      mval :: Int -> D
      mval i = D i [i] [i]

-- | Tests insert' (deleting collisions)
prop_insert'1 xs =
  (Data.Set.fromList (S.elements store1) == Data.Set.fromList (S.elements store2)) &&
  checkLookup sOO oos &&
  checkLookup sOM oms &&
  checkLookup sMO mos &&
  checkLookup sMM mms
  where
    lookupSet :: DSSelection -> DS -> Data.Set.Set D
    lookupSet sel s = Data.Set.fromList $ map snd $ S.lookup sel s

    checkLookup dim xs =
      all (\x -> lookupSet (dim .== x) store1 == lookupSet (dim .== x) store2) xs

    kes :: [(DSKey, D)]
    kes = zipWith (\i x -> (vkey' i x, x)) [0 ..] xs

    oos :: [Int]
    oos = [ 0 .. 5000 ]

    oms :: [Int]
    oms = Data.IntSet.toList $
      foldr (\(D om _ _) acc -> Data.IntSet.insert om acc) Data.IntSet.empty xs

    mos :: [Int]
    mos = Data.IntSet.toList $
      foldr (\(D _ mo _) acc -> Data.IntSet.union (Data.IntSet.fromList mo) acc) Data.IntSet.empty xs

    mms :: [Int]
    mms = Data.IntSet.toList $
      foldr (\(D _ _ mm) acc -> Data.IntSet.union (Data.IntSet.fromList mm) acc) Data.IntSet.empty xs

    store1 :: DS
    store1 = S.fromList' kes
    
    store2 :: DS
    store2 = foldl (\acc (k, x) -> snd $ S.insert' k x acc) store1 kes

-- | Tests insert, lookup (EQ, LT, GT, NEQ) #1.
prop_lookup1 = byOO_EQ  && byOM_EQ  && byMO_EQ  && byMM_EQ  &&
               byOO_LT  && byOM_LT  && byMO_LT  && byMM_LT  &&
               byOO_GT  && byOM_GT  && byMO_GT  && byMM_GT  &&
               byOO_NEQ && byOM_NEQ && byMO_NEQ && byMM_NEQ
    where
      byOO_EQ :: Bool
      byOO_EQ = all (\r -> length r == 1) $
        map (\k -> S.lookup (sOO .== k) store) oos

      byOM_EQ :: Bool
      byOM_EQ = all (\r -> length r == 50) $
        map (\k -> S.lookup (sOM .== k) store) oms

      byMO_EQ :: Bool
      byMO_EQ = all (\r -> length r == 1) $
        map (\k -> S.lookup (sMO .== k) store) mos

      byMM_EQ :: Bool
      byMM_EQ = all (\(k, r) -> length r == (100 - k)) $
        map (\k -> (k, S.lookup (sMM .== k) store)) mms

      byOO_LT :: Bool
      byOO_LT = all (\(k, r) -> length r == (k + minBound)) $
        map (\k -> (k, S.lookup (sOO .< k) store)) oos

      byOM_LT :: Bool
      byOM_LT = all (\(k, r) -> length r == (k * 50)) $
        map (\k -> (k, S.lookup (sOM .< k) store)) oms

      byMO_LT :: Bool
      byMO_LT = all (\(k, r) -> length r == k) $
        map (\k -> (k, S.lookup (sMO .< k) store)) mos

      byMM_LT :: Bool
      byMM_LT = all (\(k, r) -> length r == (if k == 0 then 0 else 100)) $
        map (\k -> (k, S.lookup (sMM .< k) store)) mms

      byOO_GT :: Bool
      byOO_GT = all (\(k, r) -> length r == (99 - k + minBound)) $
        map (\k -> (k, S.lookup (sOO .> k) store)) oos

      byOM_GT :: Bool
      byOM_GT = all (\(k, r) -> length r == ((1 - k) * 50)) $
        map (\k -> (k, S.lookup (sOM .> k) store)) oms

      byMO_GT :: Bool
      byMO_GT = all (\(k, r) -> length r == (99 - k)) $
        map (\k -> (k, S.lookup (sMO .> k) store)) mos

      byMM_GT :: Bool
      byMM_GT = all (\(k, r) -> length r == (99 - k)) $
        map (\k -> (k, S.lookup (sMM .> k) store)) mms
      
      byOO_NEQ :: Bool
      byOO_NEQ = all (\(k, r) -> length r == 99) $
        map (\k -> (k, S.lookup (sOO ./= k) store)) oos

      byOM_NEQ :: Bool
      byOM_NEQ = all (\(k, r) -> length r == 50) $
        map (\k -> (k, S.lookup (sOM ./= k) store)) oms

      byMO_NEQ :: Bool
      byMO_NEQ = all (\(k, r) -> length r == 99) $
        map (\k -> (k, S.lookup (sMO ./= k) store)) mos

      byMM_NEQ :: Bool
      byMM_NEQ = all (\(k, r) -> length r == (if k == 0 then 99 else 100)) $
        map (\k -> (k, S.lookup (sMM ./= k) store)) mms
      
      store :: DS
      store = foldl (\s v -> snd . fromJust $ S.insert (vkey v) v s) S.empty ds

      ds :: [D]
      ds = map mval [0..99]

      mval :: Int -> D
      mval i = D (i `mod` 2) [i] [0..i]

      -- Every key in this list corresponds to exactly 1 d.
      oos :: [Int]
      oos = take 100 $ iterate succ minBound

      -- Every key in this list corresponds to exactly 50 ds.
      oms :: [Int]
      oms = [0, 1]

      -- Every key in this list corresponds to exactly 1 d.
      mos :: [Int]
      mos = [0..99]

      -- Every key 'k' in this list corresponds to exactly '100 - k'
      -- ds.
      mms :: [Int]
      mms = [0..99]

-- | Tests insert, lookupOrderByA #1.
prop_lookupOrderBy1 = l1 && l2 
    where
      l1 :: Bool
      l1 = isSortedOM (S.lookupOrderByA (sMM .>= 20) sOM store) &&
           isSortedOM (S.lookupOrderByA (sMM .<= 50 .&& sOM .== 5) sOM store) &&
           isSortedOM (S.lookupOrderByA (sOO .>= 20) sOM store) &&
           isSortedOM (S.lookupOrderByA (sOM .>= 3) sOM store) 

      isSortedOM :: [(S.RawKey DSKRS DSTS, D)] -> Bool
      isSortedOM [] = True      
      isSortedOM [_] = True
      isSortedOM ((_ :. x1 :. _, _) : (_ :. x2 :. _, _) : t) = x1 <= x2 && isSortedOM t       
      
      l2 :: Bool
      l2 = isSortedOO (S.lookupOrderByA (sMM .>= 20) sOO store) &&
           isSortedOO (S.lookupOrderByA (sMM .<= 50 .&& sOM .== 1) sOO store) &&
           isSortedOO (S.lookupOrderByA (sOO .>= 20) sOO store) &&
           isSortedOO (S.lookupOrderByA (sOM .>= 3) sOO store)

      isSortedOO :: [(S.RawKey DSKRS DSTS, D)] -> Bool
      isSortedOO [] = True      
      isSortedOO [_] = True
      isSortedOO ((x1 :. _, _) : (x2 :. _, _) : t) = x1 <= x2 && isSortedOO t       

      store :: DS
      store = foldl (\s v -> snd . fromJust $ S.insert (vkey v) v s) S.empty ds

      ds :: [D]
      ds = map mval [0..99]

      mval :: Int -> D
      mval i = D (i `mod` 20) [i] [0..i]

      -- Every key in this list corresponds to exactly 1 d.
      oos :: [Int]
      oos = take 100 $ iterate succ minBound

      -- Every key in this list corresponds to exactly 50 ds.
      oms :: [Int]
      oms = [0..19]

      -- Every key in this list corresponds to exactly 1 d.
      mos :: [Int]
      mos = [0..99]

      -- Every key 'k' in this list corresponds to exactly '100 - k'
      -- ds.
      mms :: [Int]
      mms = [0..99]

-- | Tests insert, delete #1
prop_update1 = deleteMM
    where
      deleteMM :: Bool
      deleteMM = all (\(k, s, l) -> S.size s == k && length l == 0) $
        map (\k -> let res = S.delete (sMM .== k) store
                   in  (k, res, S.lookup (sMM .== k) res)
            ) mms  

      store :: DS
      store = foldl (\s v -> snd . fromJust $ S.insert (vkey v) v s) S.empty ds

      ds :: [D]
      ds = map mval [0..99]

      mval :: Int -> D
      mval i = D (i `mod` 2) [i] [0..i]
      
      mms :: [Int]
      mms = [0..99]

-- | Tests insert, delete #2
prop_update2 = test1
    where
      test1 :: Bool
      test1 = lookupRes1 == [v2] &&
              lookupRes2 == [v3] &&
              S.size res == 2
        where
          res = S.delete ((sOM .== 1) .&& (sMM .== 1)) store
          lookupRes1 = map snd $ S.lookup (sOM .== 1) res
          lookupRes2 = map snd $ S.lookup (sMM .== 1) res

      store :: DS
      store = foldl (\s v -> snd . fromJust $ S.insert (vkey v) v s) S.empty ds

      ds :: [D]
      ds = [v1, v2, v3]

      v1 = D 1 [1] [1]
      v2 = D 1 [2] [2, 3]
      v3 = D 2 [3] [1, 2]
  
-- | Tests insert, update (changing key)
prop_update3 = test1
    where
      test1 :: Bool
      test1 = lookupRes1 == [v2] &&
              lookupRes2 == [v3] &&
              lookupRes3 == [v1] &&
              lookupRes4 == [v1] &&
              lookupRes5 == [v1] &&
              S.size res == 3
        where
          res = fromJust $ S.update (\v -> Just (v, Just $ makeKey 0 0 [0] [0])) (sOO .== minBound) store
          lookupRes1 = map snd $ S.lookup (sOM .== 1) res
          lookupRes2 = map snd $ S.lookup (sMM .== 1) res
          lookupRes3 = map snd $ S.lookup (sOM .== 0) res
          lookupRes4 = map snd $ S.lookup (sMO .== 0) res
          lookupRes5 = map snd $ S.lookup (sMM .== 0) res

      store :: DS
      store = foldl (\s v -> snd . fromJust $ S.insert (vkey v) v s) S.empty ds

      ds :: [D]
      ds = [v1, v2, v3]

      v1 = D 1 [1] [1]
      v2 = D 1 [2] [2, 3]
      v3 = D 2 [3] [1, 2]

--------------------------------------------------------------------------------
-- | QuickCheck machinery.

instance Arbitrary D where
    arbitrary = (\(om, mo, mm) -> D om (L.nub mo) (L.nub mm)) <$> arbitrary

