content: {-# LANGUAGE RankNTypes #-}
  module TimeVarTests
    ( timeVarTests
    ) where
  
  import           Control.Exception
  import           Control.Monad.ST.Unsafe
  import           Data.IORef
  import           System.IO.Unsafe
  import           Test.Tasty
  import           Test.Tasty.ExpectedFailure
  import           Test.Tasty.HUnit
  
  import           Reanimate
  import           Reanimate.Scene
  
  timeVarTests :: TestTree
  timeVarTests =
    testGroup "Time variables"
    [ expectFail $ tc "Error check1" $ do
        v <- newVar (0::Double)
        checkAt v
          [ (0,0), (0.25, 0.25), (0.5, 0.5), (1,0) ]
    , expectFail $ tc "Error check2" $ do
        io $ True @?= False
    , tc "Default value" $ do
        v <- newVar True
        checkAt v
          [ (-1, True), (0, True), (1, True) ]
    , tc "Modified value" $ do
        v <- newVar False
        writeVar v True
        checkAt v
          [(-1, False), (0, True), (1, True)]
    , tc "Modified value" $ do
        v <- newVar False
        writeVar v True
        writeVar v False
        checkAt v
          [(-1, False), (0, False), (1, False)]
    , tc "Overwrite future" $ do
        v <- newVar "def"
        fork $ do
          wait 1
          writeVar v "written in the future"
        writeVar v "overwrite"
        checkAt v
          [ (-1, "def"), (0, "overwrite"), (1, "overwrite") ]
  
    , tc "Interrupt tween" $ do
        v <- newVar 0
        fork $ tweenVar v 1 $ \prev t -> fromToS prev 1 t
        checkAt v
          [ (0,0), (0.25, 0.25), (0.5, 0.5), (1,1) ]
        fork $ do
          wait 0.5
          writeVar v 0.5
        checkAt v
          [ (0,0), (0.25, 0.25), (0.5, 0.5), (1,0.5) ]
    , tc "Double tween" $ do
        v <- newVar 0
        fork $ tweenVar v 1 $ \prev t -> fromToS prev 1 t
        fork $ tweenVar v 1 $ \prev t -> fromToS prev 0 t
        checkAt v
          [ (0,0), (0.25, 0.25*0.75), (0.5, 0.5*0.5), (1,1*0) ]
    , expectFail $ tc "Negative tween duration" $ do
        v <- newVar 0
        tweenVar v (-1) $ \prev t -> fromToS prev 1 t
    , tc "Tween 1" $ do
        v <- newVar 0
        wait 1
        tweenVar v 1 $ \prev t -> fromToS prev 1 t
        checkAt v
          [ (0,0), (1,0), (1.5,0.5), (2,1), (3,1) ]
    , tc "Tween 2" $ do
        v <- newVar (0::Int)
        wait 1
        tweenVar v 1 $ \_prev _t -> 1
        checkAt v
          [ (0,0), (1,1), (1.5,1), (2,1), (3,1) ]
    , tc "Tween 3" $ do
        v <- newVar (0::Int)
        wait 1
        tweenVar v 0 $ \_prev _t -> 1
        checkAt v
          [ (0,0), (1,1), (2,1)]
    , tc "Tween 4" $ do
        v <- newVar (0::Double)
        tweenVar v 1 $ \prev t -> fromToS prev 1 t
        tweenVar v 1 $ \prev t -> fromToS prev 0 t
        checkAt v
          [ (0,0), (0.5,0.5), (1,1), (1.5,0.5), (2,0)]
    , tc "Tween 5" $ do
        v <- newVar (0::Double)
        tweenVar v 1 $ \prev t -> fromToS prev 1 t
        tweenVar v 1 $ \prev t -> fromToS prev 0 t
        wait (-1)
        writeVar v 2
        checkAt v
          [ (0,0), (0.5,0.5), (1,2), (1.5,2), (2,2)]
    , tc "Tween zero duration" $ do
        v <- newVar 0
        wait 1
        tweenVar v 0 $ \_prev t -> t
        checkAt v
          [ (0,0), (1,1), (2,1)]
    , tc "Performance/writeVar" $ do
        v <- newVar (0::Int)
        ref <- io $ newIORef (0::Int)
        modifyVar v $ \old -> unsafePerformIO (modifyIORef ref (+1) >> return 1) + old
        at0 <- readVar v
        wait 1
        at1 <- readVar v
        io $ at0+at1 @?= 2
        counter <- io $ readIORef ref
        io $ counter @?= 1
    , tc "Performance/tweenVar" $ do
        v <- newVar (0::Double)
        ref <- io $ newIORef (0::Int)
        let expensive prev t = unsafePerformIO $ do
              modifyIORef ref (+1)
              evaluate (fromToS prev 1 t)
        tweenVar v 1 expensive
        -- Reading at 0.5 and 1 should call the expensive function.
        -- Reading at 2 and 3 should reuse the result from 1.
        -- The count should therefore be 2 and not 4.
        checkAt v
          [ (0.5,0.5), (1,1), (2,1), (3,1) ]
        counter <- io $ readIORef ref
        io $ counter @?= 2
    , tc "Performance/tweenVar 2" $ do
        v <- newVar (0::Double)
        fork $ wait 10 >> writeVar v 1
        ref <- io $ newIORef (0::Int)
        let expensive prev t = unsafePerformIO $ do
              modifyIORef ref (+1)
              evaluate (fromToS prev 1 t)
        tweenVar v 1 expensive
        wait 2
        writeVar v 2
        -- Reading at 0.5 and 1 should call the expensive function.
        -- Reading at 2 should reuse the result from 1.
        -- The count should therefore be 2 and not 4.
        checkAt v
          [ (0.5,0.5), (1,1), (2,1), (3,2), (4,2) ]
        counter <- io $ readIORef ref
        io $ counter @?= 2
    ]
  
  checkAt :: (Eq a, Show a) => Var s a -> [(Time, a)] -> Scene s ()
  checkAt _ [] = pure ()
  checkAt v ((d,expected):xs) = do
    fork $ do
      now <- queryNow
      wait (d-now)
      val <- readVar v
      io $ val @?= expected
    checkAt v xs
  
  tc :: TestName -> (forall s. Scene s ()) -> TestTree
  tc name action = testCase name $
    (evalScene action @?= ())
    `catch` \(ErrorCall err) -> assertFailure err
  
  io :: IO a -> Scene s a
  io action = liftST (unsafeIOToST action)
  