{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE Rank2Types #-}
module YesodCoreTest.Cache
    ( cacheTest
    , Widget
    , resourcesC
    ) where

import Test.Hspec

import Network.Wai
import Network.Wai.Test

import Yesod.Core
import UnliftIO.IORef
import Data.Typeable (Typeable)
import qualified Data.ByteString.Lazy.Char8 as L8

data C = C

newtype V1 = V1 Int

newtype V2 = V2 Int

mkYesod "C" [parseRoutes|
/    RootR GET
/key KeyR GET
/nested NestedR GET
/nested-key NestedKeyR GET
|]

instance Yesod C where
    errorHandler e = liftIO (print e) >> defaultErrorHandler e

getRootR :: Handler RepPlain
getRootR = do
    ref <- newIORef 0
    V1 v1a <- cached $ atomicModifyIORef ref $ \i -> (i + 1, V1 $ i + 1)
    V1 v1b <- cached $ atomicModifyIORef ref $ \i -> (i + 1, V1 $ i + 1)

    V2 v2a <- cached $ atomicModifyIORef ref $ \i -> (i + 1, V2 $ i + 1)
    V2 v2b <- cached $ atomicModifyIORef ref $ \i -> (i + 1, V2 $ i + 1)

    cacheBySet "3" (V2 3)
    V2 v3a <- cacheByGet "3" >>= \x ->
      case x of
        Just y -> return y
        Nothing -> error "must be Just"
    V2 v3b <- cachedBy "3" $ (pure $ V2 4)

    return $ RepPlain $ toContent $ show [v1a, v1b, v2a, v2b, v3a, v3b]

getKeyR :: Handler RepPlain
getKeyR = do
    ref <- newIORef 0
    V1 v1a <- cachedBy "1" $ atomicModifyIORef ref $ \i -> (i + 1, V1 $ i + 1)
    V1 v1b <- cachedBy "1" $ atomicModifyIORef ref $ \i -> (i + 1, V1 $ i + 1)

    V2 v2a <- cachedBy "1" $ atomicModifyIORef ref $ \i -> (i + 1, V2 $ i + 1)
    V2 v2b <- cachedBy "1" $ atomicModifyIORef ref $ \i -> (i + 1, V2 $ i + 1)

    V2 v3a <- cachedBy "2" $ atomicModifyIORef ref $ \i -> (i + 1, V2 $ i + 1)
    V2 v3b <- cachedBy "2" $ atomicModifyIORef ref $ \i -> (i + 1, V2 $ i + 1)


    cacheBySet "4" (V2 4)
    V2 v4a <- cacheByGet "4" >>= \x ->
      case x of
        Just y -> return y
        Nothing -> error "must be Just"
    V2 v4b <- cachedBy "4" $ (pure $ V2 5)

    return $ RepPlain $ toContent $ show [v1a, v1b, v2a, v2b, v3a, v3b, v4a, v4b]

getNestedR :: Handler RepPlain
getNestedR = getNested cached

getNestedKeyR :: Handler RepPlain
getNestedKeyR = getNested $ cachedBy "3"

-- | Issue #1266
getNested ::  (forall a. Typeable a => (Handler a -> Handler a)) -> Handler RepPlain
getNested cacheMethod = do
    ref <- newIORef 0
    let getV2 = atomicModifyIORef ref $ \i -> (i + 1, V2 $ i + 1)
    V1 _ <- cacheMethod $ do
      V2 val <- cacheMethod $ getV2
      return $ V1 val
    V2 v2 <- cacheMethod $ getV2

    return $ RepPlain $ toContent $ show v2

cacheTest :: Spec
cacheTest =
  describe "Test.Cache" $ do
    it "cached" $ runner $ do
      res <- request defaultRequest
      assertStatus 200 res
      assertBody (L8.pack $ show [1, 1, 2, 2, 3, 3 :: Int]) res

    it "cachedBy" $ runner $ do
      res <- request defaultRequest { pathInfo = ["key"] }
      assertStatus 200 res
      assertBody (L8.pack $ show [1, 1, 2, 2, 3, 3, 4, 4 :: Int]) res

    it "nested cached" $ runner $ do
      res <- request defaultRequest { pathInfo = ["nested"] }
      assertStatus 200 res
      assertBody (L8.pack $ show (1 :: Int)) res

    it "nested cachedBy" $ runner $ do
      res <- request defaultRequest { pathInfo = ["nested-key"] }
      assertStatus 200 res
      assertBody (L8.pack $ show (1 :: Int)) res

runner :: Session () -> IO ()
runner f = toWaiApp C >>= runSession f
