{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}

module Main ( main ) where

import Foreign.C.String
import Data.Bits
import Control.Concurrent
import Control.Exception
import qualified Data.Set as S
import Foreign.C.Types
import Foreign.Marshal.Alloc
import Foreign.Ptr
import Foreign.Storable
import Graphics.Caramia
import Graphics.Caramia.Prelude hiding ( init )
import Graphics.UI.SDL
import System.IO.Unsafe ( unsafePerformIO )
import Test.Framework
import Test.Framework.Providers.HUnit
import Test.HUnit hiding ( Test )

foreign import ccall unsafe "memset" c_memset
    :: Ptr a -> CInt -> CSize -> IO (Ptr b)

sdlLock :: MVar ()
sdlLock = unsafePerformIO $ newMVar ()
{-# NOINLINE sdlLock #-}

setup :: IO a -> IO a
setup action = runInBoundThread $ withMVar sdlLock $ \_ -> withCString "buffer" $ \cstr -> do
    _ <- init SDL_INIT_VIDEO
    _ <- glSetAttribute SDL_GL_CONTEXT_MAJOR_VERSION 3
    _ <- glSetAttribute SDL_GL_CONTEXT_MINOR_VERSION 3
    _ <- glSetAttribute SDL_GL_CONTEXT_PROFILE_MASK SDL_GL_CONTEXT_PROFILE_CORE
    _ <- glSetAttribute SDL_GL_CONTEXT_FLAGS SDL_GL_CONTEXT_DEBUG_FLAG
    w <- createWindow cstr SDL_WINDOWPOS_UNDEFINED SDL_WINDOWPOS_UNDEFINED
                           1500 1000
                           (SDL_WINDOW_OPENGL .|.
                            SDL_WINDOW_SHOWN)
    ctx <- glCreateContext w
    finally (giveContext action) $ do
        glDeleteContext ctx
        destroyWindow w
        quit

main :: IO ()
main = defaultMain tests

tests :: [Test]
tests = [
    testCase "Invalidation does not crash and burn" invalidateSomeBuffers
  , testCase "I can create various types of buffers" createSomeBuffers
  , testCase "I cannot map write-only buffers for reading" writeOnlyTest
  , testCase "I cannot map read-only buffers for writing" readOnlyTest
  , testCase "I cannot map no-access buffers for anything" noAccessTest
  , testCase "I can map readwrite buffers for anything" readWriteTest
  , testCase "What I write I can get back" writeReadBackTest
  , testCase "Mapping with offset works" offsetMappingTest
  , testCase "Mapping with unsynchronized flag set doesn't crash" unsyncTest
  , testCase "Buffer copying works" copyBuffersTest
  , testCase "Explicit flushing tentatively works" explicitFlushTest
  , testCase ("I cannot invoke `explicitFlush` on a mapping without " <>
              "`ExplicitFlush` flag.") incorrectExplicitFlushTest
  , testCase "I cannot map with `ExplicitFlush` and no writing access."
             noWriteExplicitFlushTest
  , testCase "I cannot invoke `explicitFlush` without a mapping."
             explicitFlushNoMapTest
    ]

explicitFlushNoMapTest :: IO ()
explicitFlushNoMapTest = setup $ do
    buf <- newBufferFromList (take 10000 $ repeat (12 :: Word8))
                             (\old -> old { accessFlags = ReadWriteAccess })
    expectException $ explicitFlush buf 10 10

noWriteExplicitFlushTest :: IO ()
noWriteExplicitFlushTest = setup $ do
    buf <- newBufferFromList (take 10000 $ repeat (12 :: Word8))
                             (\old -> old { accessFlags = ReadWriteAccess })
    expectException $ withMapping2 (S.singleton ExplicitFlush)
                      100 100 ReadAccess buf $ const $ return ()
    expectException $ withMapping2 (S.singleton ExplicitFlush)
                      100 100 NoAccess buf $ const $ return ()

incorrectExplicitFlushTest :: IO ()
incorrectExplicitFlushTest = setup $ do
    buf <- newBufferFromList (take 10000 $ repeat (12 :: Word8))
                             (\old -> old { accessFlags = ReadWriteAccess })
    withMapping 100 100 WriteAccess buf $ \_ -> do
        expectException $ explicitFlush buf 10 10


explicitFlushTest :: IO ()
explicitFlushTest = setup $ do
    buf <- newBufferFromList (take 10000 $ repeat (12 :: Word8))
                             (\old -> old { accessFlags = ReadWriteAccess })
    withMapping2 (S.singleton ExplicitFlush) 7800 1000 WriteAccess buf $ \ptr -> do
        pokeElemOff (ptr :: Ptr Word8) 10 8
        pokeElemOff (ptr :: Ptr Word8) 11 9
        pokeElemOff (ptr :: Ptr Word8) 12 10
        explicitFlush buf 8 3
    withMapping 7800 100 ReadAccess buf $ \ptr -> do
        let assM x y = do z <- peekElemOff (ptr :: Ptr Word8) x
                          assertEqual "explicitly flushed bytes are the same"
                                      y z
        assM 10 8
        assM 11 9
        assM 12 10

copyBuffersTest :: IO ()
copyBuffersTest = setup $ do
    buf <- newBufferFromList (take 10000 $ repeat (77 :: Word8))
                             (\old -> old { accessFlags = ReadWriteAccess })
    buf2 <- newBufferFromList (take 20000 $ repeat (99 :: Word8))
                              (\old -> old { accessFlags = ReadWriteAccess })
    buf3 <- newBufferFromList (replicate 300 111 ++ replicate 300 222 :: [Word8])
                              (\old -> old { accessFlags = ReadWriteAccess })
    -- copy from another to another
    copy buf 11 buf2 13 5
    -- copying inside the same buffer
    copy buf3 3 buf3 311 5

    withMapping 0 100 ReadAccess buf $ \ptr -> do
        let cptr = castPtr ptr :: Ptr Word8
            ass = assertEqual "bytes copied look correct"
            assM x off = do v <- peekElemOff cptr off
                            ass x v
        assM 77 0
        assM 77 1
        assM 77 2
        assM 77 3
        assM 77 4
        assM 77 10
        assM 99 11
        assM 99 12
        assM 99 13
        assM 99 14
        assM 99 15
        assM 77 16
    withMapping 0 600 ReadAccess buf3 $ \ptr -> do
        let cptr = castPtr ptr :: Ptr Word8
            ass = assertEqual "bytes copied look correct"
            assM x off = do v <- peekElemOff cptr off
                            ass x v
        assM 111 0
        assM 111 1
        assM 111 2
        assM 222 3
        assM 222 4
        assM 222 5
        assM 222 6
        assM 222 7
        assM 111 8
        assM 111 9
        assM 111 10

offsetMappingTest :: IO ()
offsetMappingTest = setup $ do
    buf <- newBufferFromList (take 10000 $ cycle [(0 :: Word8)..])
                             (\old -> old { accessFlags = ReadWriteAccess })
    for_ offsets $ \off -> withMapping off 32 ReadAccess buf $ \ptr -> do
        let cptr = castPtr ptr :: Ptr Word8
            expect m v = assertEqual "a byte read from a buffer should be the same that was written"
                                   (fromIntegral $ (off+m) `mod` 256)
                                   v
        v1 <- peekElemOff cptr 0
        v2 <- peekElemOff cptr 1
        v3 <- peekElemOff cptr 2
        v4 <- peekElemOff cptr 3
        expect 0 v1
        expect 1 v2
        expect 2 v3
        expect 3 v4
  where
    offsets = [ 0, 16, 1024, 2048, 2040, 8000 ]

writeReadBackTest :: IO ()
writeReadBackTest = setup $ do
    buf <- newBuffer defaultBufferCreation
               { size = 1000000
               , accessFlags = ReadWriteAccess }
    withMapping 0 1000000 WriteAccess buf $ \ptr -> do
        void $ c_memset ptr 33 1000000
        pokeElemOff (castPtr ptr :: Ptr Word8) 12371 177
    withMapping 0 1000000 ReadAccess buf $ \ptr -> do
        let cptr = castPtr ptr :: Ptr Word8
        v <- peekElemOff cptr 12371
        assertEqual "a byte read from a buffer should be the same that was written"
                    177
                    v
        v_prec <- peekElemOff cptr 12370
        v_succ <- peekElemOff cptr 12372
        assertEqual "a byte read from a buffer should be the same that was written"
                    33
                    v_prec
        assertEqual "a byte read from a buffer should be the same that was written"
                    33
                    v_succ

writeOnlyTest :: IO ()
writeOnlyTest = setup $ do
    buf <- newBuffer defaultBufferCreation
               { size = 1024
               , accessFlags = WriteAccess }
    expectException $ bufferMap 0 1024 ReadAccess buf
    expectException $ bufferMap 0 1024 ReadWriteAccess buf
    withMapping 0 1024 WriteAccess buf (const $ return ())

readOnlyTest :: IO ()
readOnlyTest = setup $ do
    buf <- newBuffer defaultBufferCreation
               { size = 1024
               , accessFlags = ReadAccess }
    expectException $ bufferMap 0 1024 WriteAccess buf
    expectException $ bufferMap 0 1024 ReadWriteAccess buf
    withMapping 0 1024 ReadAccess buf (const $ return ())

noAccessTest :: IO ()
noAccessTest = setup $ do
    buf <- newBuffer defaultBufferCreation
               { size = 1024
               , accessFlags = NoAccess }
    for_ allAccessFlags $ \flags ->
        expectException $ bufferMap 0 1024 flags buf

readWriteTest :: IO ()
readWriteTest = setup $ do
    buf <- newBuffer defaultBufferCreation
               { size = 1024
               , accessFlags = ReadWriteAccess }
    for_ allAccessFlags $ \flags ->
        when (flags /= NoAccess) $
            withMapping 0 1024 flags buf (const $ return ())

expectException :: IO a -> IO ()
expectException action = do
    result <- try action
    case result of
        Left (_ :: SomeException) -> return ()
        Right _ -> error "expected exception"

withSomeBuffers :: (BufferCreation -> Buffer -> IO ()) -> IO ()
withSomeBuffers action =
    for_ allAccessHints $ \hints ->
    for_ allAccessFlags $ \flags ->
    for_ sizes $ \size -> do
        let c = defaultBufferCreation {
                    size = size
                  , initialData = Nothing
                  , accessFlags = flags
                  , accessHints = hints }
        newBuffer c >>= action c
        allocaBytes size $ \ptr -> do
            let c' = c { initialData = Just ptr }
            newBuffer c' >>= action c'
        runPendingFinalizers

unsyncTest :: IO ()
unsyncTest = setup $ withSomeBuffers $ \cr buf -> do
    when (accessFlags cr == WriteAccess ||
          accessFlags cr == ReadWriteAccess) $
        withMapping2 (S.singleton UnSynchronized)
                     0
                     (size cr)
                     WriteAccess
                     buf $ const $ return ()

invalidateSomeBuffers :: IO ()
invalidateSomeBuffers = setup $ withSomeBuffers $ \_ -> invalidateBuffer

createSomeBuffers :: IO ()
createSomeBuffers = setup $ withSomeBuffers (\_ _ -> return ())

sizes :: [Int]
sizes = [ 1, 2, 3, 4, 8, 11, 12, 512, 1024, 1023, 1025, 1000000 ]

allAccessFlags :: [AccessFlags]
allAccessFlags = [ReadAccess, WriteAccess, ReadWriteAccess, NoAccess]

allAccessHints :: [(AccessFrequency, AccessNature)]
allAccessHints = [ (f, n) | f <- [Stream, Static, Dynamic]
                          , n <- [Draw, Read, Copy] ]

