{-# LANGUAGE FlexibleInstances #-}

module Test.Data.Raft.Entry where

import qualified Data.List             as List
import           Data.Sequence         (Seq)
import qualified Data.Sequence         as Seq
import           Test.Tasty.QuickCheck

import           Data.Raft.Entry
import           Data.Raft.Index
import           Data.Raft.Term
import           Data.RaftPrelude
import           Test.Data.List
import           Test.Data.Raft.Index  ()
import           Test.Data.Raft.Term   ()

instance Arbitrary (Entry Int) where
  arbitrary = genericEntry

instance Arbitrary EntryReference where
  arbitrary = genericEntryReference

genericEntry :: Gen (Entry Int)
genericEntry = Entry
    <$> arbitrary
    <*> arbitrary
    <*> arbitrary

genericEntryReference :: Gen EntryReference
genericEntryReference = EntryReference
    <$> arbitrary
    <*> arbitrary

entryReferenceAfter :: EntryReference -> Gen EntryReference
entryReferenceAfter (EntryReference (Index idx) (Term term)) = do
  idx' <- choose (idx + 1, maxBound)
  term' <- choose (term + 1, maxBound)
  return $ EntryReference (Index idx') (Term term')

entryAfter :: Entry Int -> Gen (Entry Int)
entryAfter (Entry (Index idx) (Term term) _) = do
  idx' <- choose (idx + 1, maxBound)
  term' <- choose (term + 1, maxBound)
  value' <- arbitrary
  return $ Entry (Index idx') (Term term') value'

-- | Generate a sequence of entries for appending to logs. Sequence
-- starts just after the previous entry reference.
entrySequenceFrom :: EntryReference -> Gen (Seq (Entry Int))
entrySequenceFrom ref = sized $ \size -> do
  elist <- entriesFromAsc ref
  return $ Seq.fromList $ List.take size elist

-- | Generate a sequence of entries that is required to contain the
-- provided entry, somewhere.
entrySequenceContaining  :: Entry Int -> Gen (Seq (Entry Int))
entrySequenceContaining mustHave = sized $ \size -> do
  idx <- choose (0, size - 1)
  beforeStream <- entriesFromDesc (entryReference mustHave)
  afterStream <- entriesFromAsc (entryReference mustHave)
  let before = List.take (idx - 1) beforeStream
      after = List.take (size - idx - 1) afterStream
  return $ Seq.fromList $ List.reverse before <> [mustHave] <> after

-- | Produces an infiniate stream of entries moving forwards from a
-- starting entry.
entriesFromAsc :: EntryReference -> Gen [Entry Int]
entriesFromAsc (EntryReference (Index sIdx) (Term sTerm)) = do
  let indicies = fmap Index $ denseOrderedListFromAsc (sIdx + 1)
  terms <- fmap Term <$> denseOrderedUniqueListFromAsc sTerm
  values <- infiniteList
  return $ List.zipWith3 Entry indicies terms values

-- | Produces an infiniate stream of entries moving backwards from a
-- starting entry.
entriesFromDesc :: EntryReference -> Gen [Entry Int]
entriesFromDesc (EntryReference (Index sIdx) (Term sTerm)) = do
  let indicies = fmap Index $ denseOrderedListFromDesc (sIdx + 1)
  terms <- fmap Term <$> denseOrderedUniqueListFromDesc sTerm
  values <- infiniteList
  return $ List.zipWith3 Entry indicies terms values
