{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
module RIO.DequeSpec (spec) where

import RIO
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck.Arbitrary
import Test.QuickCheck.Gen
import qualified Data.Vector as VB
import qualified Data.Vector.Generic as VG
import qualified Data.Vector.Unboxed as VU
import qualified Data.Vector.Storable as VS
import qualified Data.Vector.Generic.Mutable as V

data DequeAction
    = PushFront Int
    | PushBack Int
    | PopFront
    | PopBack
    deriving Show
instance Arbitrary DequeAction where
    arbitrary = oneof $ concat
        [ replicate 25 $ fmap PushFront arbitrary
        , replicate 25 $ fmap PushBack arbitrary
        , [return PopFront, return PopBack]
        ]

manyPushes :: [DequeAction]
manyPushes = concat
    [ replicate 50 $ PushBack 0
    , replicate 50 PopFront
    , replicate 50 $ PushFront 0
    , replicate 50 PopBack
    ]

specialCase :: [DequeAction]
specialCase =
    [PushBack 9, PushBack 5,PushBack 11,PushBack 2,PushBack 13,PushBack 10,PushBack 4,PushBack 13,PushBack 7,PushBack 8,PushBack 6,PushBack 4,PushBack 7,PushBack 9,PushBack 10,PushBack 3,PushBack 2,PushBack 12,PushBack 12 ,PushBack 6,PushBack 3,PushBack 5,PushBack 14,PushBack 14,PushBack 11,PushBack 8,PopFront,PopFront,PopFront,PushBack 11,PushBack 3,PopFront,PopFront,PushBack 13,PushBack 12,PopFront,PushBack 10,PushBack 7,PopFront,PopFront,PushBack 13,PushBack 9,PopFront,PushBack 7,PushBack 2,PopFront,PopFront,PushBack 6,PushBack 4,PopFront,PopFront,PopFront,PushBack 9,PushBack 3,PopFront,PushBack 10,PushBack 6,PopFront,PopFront,PopFront,PushBack 12,PushBack 5,PopFront,PushBack 12,PushBack 5,PopFront,PushBack 6,PushBack 4,PopFront,PopFront,PopFront,PushBack 14,PushBack 10,PopFront,PushBack 14,PushBack 10,PopFront,PushBack 11,PushBack 8,PopFront,PushBack 8,PushBack 2,PopFront,PopFront,PopFront,PushBack 13,PushBack 7,PopFront,PushBack 12,PushBack 5,PopFront,PushBack 10,PushBack 8, PopFront,PushBack 7,PushBack 2,PopFront,PopFront,PushBack 9,PushBack 4,PopFront,PopFront,PopFront,PopFront,PopFront,PopFront,PopFront,PopFront,PushBack 4,PushBack 9,PushBack 3,PushBack 10,PushBack 6,PushBack 4,PushBack 13,PushBack 7,PushBack 9,PushBack 3,PopFront]

spec :: Spec
spec = do
  let runActions
        :: forall v . (VG.Vector v Int, Show (v Int), Eq (v Int))
        => Proxy v
        -> [DequeAction]
        -> IO ()
      runActions proxy actions = do
        base <- newIORef [] :: IO (IORef [Int])
        tested <- newDeque :: IO (Deque (VG.Mutable v) (PrimState IO) Int)
        for_ (PopFront : PopBack : actions) $ \action -> do
          case action of
            PushFront i -> do
              pushFrontRef base i
              pushFrontDeque tested i
              same proxy base tested
            PushBack i -> do
              pushBackRef base i
              pushBackDeque tested i
              same proxy base tested
            PopFront -> do
              expected <- popFrontRef base
              actual <- popFrontDeque tested
              actual `shouldBe` expected
              same proxy base tested
            PopBack -> do
              expected <- popBackRef base
              actual <- popBackDeque tested
              actual `shouldBe` expected
              same proxy base tested
        let drain = do
              expected <- popBackRef base
              actual <- popBackDeque tested
              actual `shouldBe` expected
              case actual of
                Just _ -> drain
                Nothing -> return $! ()
        drain
      test name proxy = describe name $ do
        prop "arbitrary actions" $ runActions proxy
        it "many pushes" $ runActions proxy manyPushes
        it "special case" $ runActions proxy specialCase

  test "UDeque" (Proxy :: Proxy VU.Vector)
  test "SDeque" (Proxy :: Proxy VS.Vector)
  test "BDeque" (Proxy :: Proxy VB.Vector)

pushFrontRef :: IORef [Int] -> Int -> IO ()
pushFrontRef ref i = modifyIORef ref (i:)

pushBackRef :: IORef [Int] -> Int -> IO ()
pushBackRef ref i = modifyIORef ref (++ [i])

popFrontRef :: IORef [Int] -> IO (Maybe Int)
popFrontRef ref = do
  is <- readIORef ref
  case is of
    i:is' -> do
      writeIORef ref is'
      pure $ Just i
    [] -> pure Nothing

popBackRef :: IORef [Int] -> IO (Maybe Int)
popBackRef ref = do
  is <- readIORef ref
  case reverse is of
    i:is' -> do
      writeIORef ref $ reverse is'
      pure $ Just i
    [] -> pure Nothing

same ::
     forall v. (Show (v Int), Eq (v Int), VG.Vector v Int)
  => Proxy v
  -> IORef [Int]
  -> Deque (VG.Mutable v) (PrimState IO) Int
  -> IO ()
same proxy ref deque = do
  fromRef <- readIORef ref
  fromRight <- foldrDeque (\i rest -> pure $ i : rest) [] deque
  fromRight `shouldBe` fromRef
  fromLeft <- foldlDeque (\rest i -> pure $ i : rest) [] deque
  fromLeft `shouldBe` reverse fromRef
  dequeToList deque `shouldReturn` fromRef
  dequeToVector deque `shouldReturn` (VU.fromList fromRef :: VU.Vector Int)
  uv :: v Int <- freezeDeque deque
  uv `shouldBe` VG.fromList fromRef
