{-# LANGUAGE
    FlexibleContexts
  , GADTs
  , Rank2Types
  , ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
module Main (main) where

import Control.Applicative
import Control.Monad.ST
import qualified Control.Monad.ST.Lazy as Lazy

import Data.ByteArraySlice
import Data.Int
import Data.Tuple.IO
import Data.Tuple.ST
import Data.Tuple.Storable
import Data.Typeable (Typeable, typeOf)
import Data.Var.IO
import Data.Var.ST
import Data.Var.Storable
import Data.Word

import Foreign.Storable

import Test.Framework
import Test.Framework.Providers.QuickCheck2
import Test.QuickCheck
import Test.QuickCheck.Gen
import Test.QuickCheck.Monadic

main :: IO ()
main =
  defaultMain
  [ testProperty "IOVar" prop_IOVar
  , testProperty "IOUVar" prop_IOUVar
  , testProperty "STVar" prop_STVar
  , testProperty "LazySTVar" prop_LazySTVar
  , testProperty "STUVar" prop_STUVar
  , testProperty "LazySTUVar" prop_LazySTUVar
  , testProperty "StorableVar" prop_StorableVar
  , testProperty "IOTuple" prop_IOTuple
  , testProperty "IOUTuple" prop_IOUTuple
  , testProperty "STTuple" prop_STTuple
  , testProperty "LazySTTuple" prop_LazySTTuple
  , testProperty "STUTuple" prop_STUTuple
  , testProperty "LazySTUTuple" prop_LazySTUTuple
  , testProperty "StorableTuple" prop_StorableTuple
  , testProperty "StorableTuple'" (prop_StorableTuple' ::
                                      Int16 -> Int16 ->
                                      Float -> Float ->
                                      Double -> Double ->
                                      Float -> Float ->
                                      Word16 -> Word16 ->
                                      Property)
  ]

varWriteRead var a = do
  a' <- run $ do
    writeVar var a
    readVar var
  assert $ a == a'

prop_IOVar (a :: Integer, b) = monadicIO $ do
  var <- run $ newIOVar a
  varWriteRead var b

newIOVar :: a -> IO (IOVar a)
newIOVar = newVar

prop_IOUVar (SomeByteArraySlice2 a a') = monadicIO $ do
  var <- run $ newIOUVar a
  varWriteRead var a'

newIOUVar :: ByteArraySlice a => a -> IO (IOUVar a)
newIOUVar = newVar

prop_STVar (a :: Integer, b) = monadicST $ do
  var <- run $ newSTVar a
  varWriteRead var b

newSTVar :: a -> ST s (STVar s a)
newSTVar = newVar

prop_LazySTVar (a :: Integer, b) = monadicLazyST $ do
  var <- run $ newLazySTVar a
  varWriteRead var b

newLazySTVar :: a -> Lazy.ST s (STVar s a)
newLazySTVar = newVar

prop_STUVar (SomeByteArraySlice2 a a') = monadicST $ do
  var <- run $ newSTUVar a
  varWriteRead var a'

newSTUVar :: ByteArraySlice a => a -> ST s (STUVar s a)
newSTUVar = newVar

prop_LazySTUVar (SomeByteArraySlice2 a a') = monadicLazyST $ do
  var <- run $ newLazySTUVar a
  varWriteRead var a'

newLazySTUVar :: ByteArraySlice a => a -> Lazy.ST s (STUVar s a)
newLazySTUVar = newVar

prop_StorableVar (SomeStorable2 a a') = monadicIO $ do
  var <- run $ newStorableVar a
  varWriteRead var a'

newStorableVar :: Storable a => a -> IO (StorableVar a)
newStorableVar = newVar

tupleWriteRead tuple (a, b, c, d, e) = do
  writeOnly1 tuple a
  a' <- run $ read1 tuple
  assert $ a == a'
  writeOnly2 tuple b
  b' <- run $ read2 tuple
  assert $ b == b'
  writeOnly3 tuple c
  c' <- run $ read3 tuple
  assert $ c == c'
  writeOnly4 tuple d
  d' <- run $ read4 tuple
  assert $ d == d'
  writeOnly5 tuple e
  e' <- run $ read5 tuple
  assert $ e == e'

tupleThawFreeze thaw xs = do
  tuple <- run $ thaw xs
  xs' <- run $ freezeTuple tuple
  assert $ xs == xs'

writeOnly1 t a = do
  xs <- run $ readAllBut1 t
  run $ write1 t a
  xs' <- run $ readAllBut1 t
  assert $ xs == xs'

readAllBut1 t = (,,,) <$> read2 t <*> read3 t <*> read4 t <*> read5 t

writeOnly2 t a = do
  xs <- run $ readAllBut2 t
  run $ write2 t a
  xs' <- run $ readAllBut2 t
  assert $ xs == xs'

readAllBut2 t = (,,,) <$> read1 t <*> read3 t <*> read4 t <*> read5 t

writeOnly3 t a = do
  xs <- run $ readAllBut3 t
  run $ write3 t a
  xs' <- run $ readAllBut3 t
  assert $ xs == xs'

readAllBut3 t = (,,,) <$> read1 t <*> read2 t <*> read4 t <*> read5 t

writeOnly4 t a = do
  xs <- run $ readAllBut4 t
  run $ write4 t a
  xs' <- run $ readAllBut4 t
  assert $ xs == xs'

readAllBut4 t = (,,,) <$> read1 t <*> read2 t <*> read3 t <*> read5 t

writeOnly5 t a = do
  xs <- run $ readAllBut5 t
  run $ write5 t a
  xs' <- run $ readAllBut5 t
  assert $ xs == xs'

readAllBut5 t = (,,,) <$> read1 t <*> read2 t <*> read3 t <*> read4 t

prop_IOTuple (a :: IntegerTuple, b) = monadicIO $ do
  tuple <- run $ thawIOTuple a
  tupleWriteRead tuple b
  tupleThawFreeze thawIOTuple a

thawIOTuple :: MTuple IOTuple a IO => a -> IO (IOTuple a)
thawIOTuple = thawTuple

prop_IOUTuple (SomeByteArraySlice2 a a',
               SomeByteArraySlice2 b b',
               SomeByteArraySlice2 c c',
               SomeByteArraySlice2 d d',
               SomeByteArraySlice2 e e') = monadicIO $ do
  let xs = (a, b, c, d, e)
  tuple <- run $ thawIOUTuple xs
  tupleWriteRead tuple (a', b', c', d', e')
  tupleThawFreeze thawIOUTuple xs

thawIOUTuple :: MTuple IOUTuple a IO => a -> IO (IOUTuple a)
thawIOUTuple = thawTuple

prop_STTuple (a :: IntegerTuple, b) = monadicST $ do
  tuple <- run $ thawSTTuple a
  tupleWriteRead tuple b
  tupleThawFreeze thawSTTuple a

thawSTTuple :: MTuple (STTuple s) a (ST s) => a -> ST s (STTuple s a)
thawSTTuple = thawTuple

prop_LazySTTuple (a :: IntegerTuple, b) = monadicLazyST $ do
  tuple <- run $ thawLazySTTuple a
  tupleWriteRead tuple b
  tupleThawFreeze thawLazySTTuple a

thawLazySTTuple :: MTuple (STTuple s) a (Lazy.ST s) => a -> Lazy.ST s (STTuple s a)
thawLazySTTuple = thawTuple

prop_STUTuple (SomeByteArraySlice2 a a',
               SomeByteArraySlice2 b b',
               SomeByteArraySlice2 c c',
               SomeByteArraySlice2 d d',
               SomeByteArraySlice2 e e') = monadicST $ do
  let xs = (a, b, c, d, e)
  tuple <- run $ thawSTUTuple xs
  tupleWriteRead tuple (a', b', c', d', e')
  tupleThawFreeze thawSTUTuple xs

thawSTUTuple :: MTuple (STUTuple s) a (ST s) => a -> ST s (STUTuple s a)
thawSTUTuple = thawTuple

prop_LazySTUTuple (SomeByteArraySlice2 a a',
                   SomeByteArraySlice2 b b',
                   SomeByteArraySlice2 c c',
                   SomeByteArraySlice2 d d',
                   SomeByteArraySlice2 e e') = monadicLazyST $ do
  let xs = (a, b, c, d, e)
  tuple <- run $ thawLazySTUTuple xs
  tupleWriteRead tuple (a', b', c', d', e')
  tupleThawFreeze thawLazySTUTuple xs

thawLazySTUTuple :: MTuple (STUTuple s) a (Lazy.ST s) => a -> Lazy.ST s (STUTuple s a)
thawLazySTUTuple = thawTuple

prop_StorableTuple (SomeStorable2 a a',
                    SomeStorable2 b b',
                    SomeStorable2 c c',
                    SomeStorable2 d d',
                    SomeStorable2 e e') = prop_StorableTuple' a a' b b' c c' d d' e e'

prop_StorableTuple' a a' b b' c c' d d' e e' = monadicIO $ do
  let xs = (a, b, c, d, e)
  tuple <- run $ thawStorableTuple xs
  tupleWriteRead tuple (a', b', c', d', e')
  tupleThawFreeze thawStorableTuple xs

thawStorableTuple :: MTuple StorableTuple a IO => a -> IO (StorableTuple a)
thawStorableTuple = thawTuple

data SomeByteArraySlice2 where
  SomeByteArraySlice2 :: ( Show a
                         , Eq a
                         , Arbitrary a
                         , ByteArraySlice a
                         ) => a -> a -> SomeByteArraySlice2

instance Show SomeByteArraySlice2 where
  showsPrec p (SomeByteArraySlice2 a a') = showsPrec p (a, a')
  show (SomeByteArraySlice2 a a') = show (a, a')

instance Arbitrary SomeByteArraySlice2 where
  arbitrary = do
    (ByteArraySliceDict (Proxy :: Proxy a)) <- arbitrary
    (a, a') :: (a, a) <- arbitrary
    return $ SomeByteArraySlice2 a a'
  shrink (SomeByteArraySlice2 a a') =
    map (uncurry SomeByteArraySlice2) $ shrink (a, a')

data ByteArraySliceDict where
  ByteArraySliceDict :: ( Show a
                        , Eq a
                        , Arbitrary a
                        , ByteArraySlice a
                        ) => Proxy a -> ByteArraySliceDict

instance Arbitrary ByteArraySliceDict where
  arbitrary = do
    n <- size
    oneof $ if n <= 0 then leaf else leaf ++ branch
    where
      leaf =
        map pure
        [ ByteArraySliceDict (Proxy :: Proxy Bool)
        , ByteArraySliceDict (Proxy :: Proxy Char)
        , ByteArraySliceDict (Proxy :: Proxy Double)
        , ByteArraySliceDict (Proxy :: Proxy Float)
        , ByteArraySliceDict (Proxy :: Proxy Int)
        , ByteArraySliceDict (Proxy :: Proxy Int8)
        , ByteArraySliceDict (Proxy :: Proxy Int16)
        , ByteArraySliceDict (Proxy :: Proxy Int32)
        , ByteArraySliceDict (Proxy :: Proxy Int64)
        , ByteArraySliceDict (Proxy :: Proxy Word)
        , ByteArraySliceDict (Proxy :: Proxy Word8)
        , ByteArraySliceDict (Proxy :: Proxy Word16)
        , ByteArraySliceDict (Proxy :: Proxy Word32)
        , ByteArraySliceDict (Proxy :: Proxy Word64)
        ]
      branch =
        [ do
             let m = resize' (`div` 2) arbitrary
             ByteArraySliceDict (Proxy :: Proxy a) <- m
             ByteArraySliceDict (Proxy :: Proxy b) <- m
             return $ ByteArraySliceDict (Proxy :: Proxy (a, b))
        , do
             let m = resize' (`div` 3) arbitrary
             ByteArraySliceDict (Proxy :: Proxy a) <- m
             ByteArraySliceDict (Proxy :: Proxy b) <- m
             ByteArraySliceDict (Proxy :: Proxy c) <- m
             return $ ByteArraySliceDict (Proxy :: Proxy (a, b, c))
        , do
             let m = resize' (`div` 4) arbitrary
             ByteArraySliceDict (Proxy :: Proxy a) <- m
             ByteArraySliceDict (Proxy :: Proxy b) <- m
             ByteArraySliceDict (Proxy :: Proxy c) <- m
             ByteArraySliceDict (Proxy :: Proxy d) <- m
             return $ ByteArraySliceDict (Proxy :: Proxy (a, b, c, d))
        , do
             let m = resize' (`div` 5) arbitrary
             ByteArraySliceDict (Proxy :: Proxy a) <- m
             ByteArraySliceDict (Proxy :: Proxy b) <- m
             ByteArraySliceDict (Proxy :: Proxy c) <- m
             ByteArraySliceDict (Proxy :: Proxy d) <- m
             ByteArraySliceDict (Proxy :: Proxy e) <- m
             return $ ByteArraySliceDict (Proxy :: Proxy (a, b, c, d, e))
        ]

data SomeStorable2 where
  SomeStorable2 :: ( Show a
                   , Eq a
                   , Typeable a
                   , Arbitrary a
                   , Storable a
                   ) => a -> a -> SomeStorable2

instance Show SomeStorable2 where
  showsPrec p (SomeStorable2 a a') = showsPrec p ((a, typeOf a), (a', typeOf a'))
  show (SomeStorable2 a a') = show ((a, typeOf a), (a', typeOf a'))

instance Arbitrary SomeStorable2 where
  arbitrary = do
    StorableDict (Proxy :: Proxy a) <- arbitrary
    (a, a') :: (a, a) <- arbitrary
    return $ SomeStorable2 a a'
  shrink (SomeStorable2 a a') =
    map (uncurry SomeStorable2) $ shrink (a, a')

data StorableDict where
  StorableDict :: ( Show a
                  , Eq a
                  , Typeable a
                  , Arbitrary a
                  , Storable a
                  ) => Proxy a -> StorableDict

instance Arbitrary StorableDict where
  arbitrary =
    elements
    [ StorableDict (Proxy :: Proxy Bool)
    , StorableDict (Proxy :: Proxy Char)
    , StorableDict (Proxy :: Proxy Double)
    , StorableDict (Proxy :: Proxy Float)
    , StorableDict (Proxy :: Proxy Int)
    , StorableDict (Proxy :: Proxy Int8)
    , StorableDict (Proxy :: Proxy Int16)
    , StorableDict (Proxy :: Proxy Int32)
    , StorableDict (Proxy :: Proxy Int64)
    , StorableDict (Proxy :: Proxy Word)
    , StorableDict (Proxy :: Proxy Word8)
    , StorableDict (Proxy :: Proxy Word16)
    , StorableDict (Proxy :: Proxy Word32)
    , StorableDict (Proxy :: Proxy Word64)
    ]

type IntegerTuple = (Integer, Integer, Integer, Integer, Integer)

monadicLazyST :: (forall s . PropertyM (Lazy.ST s) a) -> Property
monadicLazyST m = property (runLazySTGen (monadic' m))

runLazySTGen :: (forall s . Gen (Lazy.ST s a)) -> Gen a
runLazySTGen g = MkGen $ \ r n -> Lazy.runST (unGen g r n)

size :: Gen Int
size = MkGen $ \ _ n -> n

resize' :: (Int -> Int) -> Gen a -> Gen a
resize' f m = sized $ \ n -> resize (f n) m

data Proxy a = Proxy
