{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Z.Data.Vector.BaseSpec where

import qualified Data.List                as List
import           Data.Word
import           Data.Hashable            (hashWithSalt, hash)
import           Test.QuickCheck
import           Test.QuickCheck.Function
import           Test.QuickCheck.Property
import           Test.Hspec
import           Test.Hspec.QuickCheck
import qualified Z.Data.Vector.Base     as V
import           Z.Data.ASCII

spec :: Spec
spec = describe "vector-base" $ do
    describe "vector Eq Ord property" $ do
        prop "vector eq === List.eq" $ \ xs ys ->
            (V.pack @V.Vector @Integer xs == V.pack ys) === (xs == ys)
        prop "vector eq === List.eq" $ \ xs ys ->
            (V.pack @V.PrimVector @Int xs == V.pack ys) === (xs == ys)
        prop "vector eq === List.eq" $ \ xs ys ->
            (V.pack @V.PrimVector @Word8 xs == V.pack ys) === (xs == ys)

        prop "vector compare === List.compare" $ \ xs ys ->
            (V.pack @V.Vector @Integer xs `compare` V.pack ys) === (xs `compare` ys)
        prop "vector compare === List.compare" $ \ xs ys ->
            (V.pack @V.PrimVector @Int xs `compare` V.pack ys) === (xs `compare` ys)
        prop "vector compare === List.compare" $ \ xs ys ->
            (V.pack @V.PrimVector @Word8 xs `compare` V.pack ys) === (xs `compare` ys)

    describe "vector unpack(R) . pack(R)(N) == id" . modifyMaxSuccess (*10) . modifyMaxSize (*10) $ do
        prop "unpack . pack === id" $ \ xs ->
            (V.unpack @V.Vector @Integer) (V.pack xs)  === xs
        prop "unpack . pack === id" $ \ xs ->
            (V.unpack @V.PrimVector @Int) (V.pack xs)  === xs
        prop "unpack . pack === id" $ \ xs ->
            (V.unpack @V.PrimVector @Word8) (V.pack xs)  === xs

        prop "unpackR . packR === id" $ \ xs ->
            (V.unpackR @V.Vector @Integer) (V.packR xs)  === xs
        prop "unpackR . packR === id" $ \ xs ->
            (V.unpackR @V.PrimVector @Int) (V.packR xs)  === xs
        prop "unpackR . packR === id" $ \ xs ->
            (V.unpackR @V.PrimVector @Word8) (V.packR xs)  === xs

    describe "vector pack == packN" . modifyMaxSuccess (*10) . modifyMaxSize (*10) $ do
        prop "pack === packN XX" $ \ xs d ->
            (V.pack @V.Vector @Integer xs) === (V.packN d xs)
        prop "pack === packN XX" $ \ xs d ->
            (V.pack @V.PrimVector @Int xs) === (V.packN d xs)
        prop "pack === packN XX" $ \ xs d ->
            (V.pack @V.PrimVector @Word8 xs) === (V.packN d xs)

        prop "packR === packRN XX" $ \ xs d ->
            (V.packR @V.Vector @Integer xs) === (V.packRN d xs)
        prop "packR === packRN XX" $ \ xs d ->
            (V.packR @V.PrimVector @Int xs) === (V.packRN d xs)
        prop "packR === packRN XX" $ \ xs d ->
            (V.packR @V.PrimVector @Word8 xs) === (V.packRN d xs)

    describe "Bytes Hashable instance property" . modifyMaxSuccess (*10) . modifyMaxSize (*10) $ do

        prop "Vector Word8's hash should be equal to hashWithSalt (Bytes's one) (Bytes's length)" $ \ xs ->
            hash (V.pack @V.Vector @Word8 xs) === hashWithSalt (hash (V.pack @V.PrimVector @Word8 xs)) (List.length xs)
        prop "Vector a's hash should be equal to [a]'s hash" $ \ xs ->
            hash (V.pack @V.Vector @Word8 xs) === hash xs
        prop "Vector a's hash should be equal to [a]'s hash" $ \ xs ->
            hash (V.pack @V.Vector @Int xs) === hash xs
        prop "Vector a's hash should be equal to [a]'s hash" $ \ xs ->
            hash (V.pack @V.Vector @Integer xs) === hash xs


    describe "Bytes IsString instance property" $ do
        prop "ASCII string" $
            "hello world" === V.pack @V.PrimVector (List.map c2w "hello world")
        prop "UTF8 string" $
            "你好世界" === V.pack @V.PrimVector (List.map c2w "你好世界")

    describe "vector length == List.length" $ do
        prop "vector length === List.length" $ \ xs ->
            (V.length $ V.pack @V.Vector @Integer xs)  ===  List.length xs
        prop "vector length === List.length" $ \ xs ->
            (V.length $ V.pack @V.PrimVector @Int xs)  ===  List.length xs
        prop "vector length === List.length" $ \ xs ->
            (V.length $ V.pack @V.PrimVector @Word8 xs)  ===  List.length xs

    describe "vector append == List.(++)" $ do
        prop "vector eq === List.eq" $ \ xs ys ->
            (V.unpack $ V.pack @V.Vector @Integer xs `V.append` V.pack ys) === (xs ++ ys)
        prop "vector eq === List.eq" $ \ xs ys ->
            (V.unpack $ V.pack @V.PrimVector @Int xs `V.append` V.pack ys) === (xs ++ ys)
        prop "vector eq === List.eq" $ \ xs ys ->
            (V.unpack $ V.pack @V.PrimVector @Word8 xs `V.append` V.pack ys) === (xs ++ ys)

    describe "vector map/map' == List.map" $ do
        prop "vector map === List.map" $ \ xs (Fun _ f) ->
            (V.map @V.Vector @V.Vector (f :: Integer -> Integer) $ V.pack @V.Vector @Integer xs) ===
                (V.pack $ List.map f xs)
        prop "vector map' === List.map" $ \ xs (Fun _ f) ->
            (V.map' @V.Vector @V.Vector (f :: Integer -> Integer) $ V.pack @V.Vector @Integer xs) ===
                (V.pack $ List.map f xs)
        prop "vector map === List.map" $ \ xs (Fun _ f)  ->
            (V.map @V.PrimVector @V.PrimVector (f :: Int -> Word8) $ V.pack @V.PrimVector @Int xs) ===
                (V.pack $ List.map f xs)
        prop "vector map === List.map" $ \ xs (Fun _ f) ->
            (V.map @V.PrimVector @V.PrimVector (f :: Word8 -> Int) $ V.pack @V.PrimVector @Word8 xs) ===
                (V.pack $ List.map f xs)
        prop "vector map === List.map" $ \ xs (Fun _ f) ->
            (V.map @V.PrimVector @V.Vector (f :: Word8 -> Integer) $ V.pack @V.PrimVector @Word8 xs) ===
                (V.pack $ List.map f xs)
        prop "vector map === List.map" $ \ xs (Fun _ f) ->
            (V.map @V.Vector @V.PrimVector (f :: Integer -> Word8) $ V.pack @V.Vector @Integer xs) ===
                (V.pack $ List.map f xs)

    describe "vector imap' (const f) == List.map f" $ do
        prop "vector imap' (const f) == List.map f" $ \ xs (Fun _ f) ->
            (V.imap' @V.Vector @V.PrimVector (const (f :: Integer -> Word8)) $ V.pack @V.Vector @Integer xs) ===
                (V.pack $ List.map f xs)

    describe "vector imap' const == List.zipWith const [0..]" $ do
        prop "vector imap' const == List.zipWith const [0..]" $ \ xs ->
            (V.imap' @V.Vector @V.PrimVector const $ V.pack @V.Vector @Integer xs) ===
                (V.pack $ List.zipWith const [0..] xs)

    describe "vector foldl' == List.foldl'" $ do
        prop "vector foldl' === List.foldl'" $ \ xs f x ->
            (V.foldl' (applyFun2 f :: Integer -> Integer -> Integer) x . V.pack @V.Vector @Integer $ xs)  ===
                (List.foldl' (applyFun2 f) x $ xs)
        prop "vector foldl' x === List.foldl' x" $ \ xs f x ->
            (V.foldl' (applyFun2 f :: Int -> Int -> Int) x . V.pack @V.PrimVector @Int $ xs)  ===
                (List.foldl' (applyFun2 f) x $ xs)
        prop "vector foldl' x === List.foldl' x" $ \ xs f x ->
            (V.foldl' (applyFun2 f :: Int -> Word8 -> Int) x . V.pack @V.PrimVector @Word8 $ xs)  ===
                (List.foldl' (applyFun2 f) x $ xs)

    describe "vector foldr' == List.foldr'" $ do
        prop "vector foldr' === List.foldr" $ \ xs f x ->
            (V.foldr' (applyFun2 f :: Integer -> Integer -> Integer) x . V.pack @V.Vector @Integer $ xs)  ===
                (List.foldr (applyFun2 f) x $ xs)
        prop "vector foldr' x === List.foldr' x" $ \ xs f x ->
            (V.foldr' (applyFun2 f :: Int -> Int -> Int) x . V.pack @V.PrimVector @Int $ xs)  ===
                (List.foldr (applyFun2 f) x $ xs)
        prop "vector foldr' x === List.foldr' x" $ \ xs f x ->
            (V.foldr' (applyFun2 f :: Word8 -> Int -> Int) x . V.pack @V.PrimVector @Word8 $ xs)  ===
                (List.foldr (applyFun2 f) x $ xs)

    describe "vector concat == List.concat" $ do
        prop "vector concat === List.concat" $ \ xss ->
            (V.concat . List.map (V.pack @V.Vector @Integer) $ xss)  === (V.pack . List.concat $ xss)
        prop "vector concat === List.concat" $ \ xss ->
            (V.concat . List.map (V.pack @V.PrimVector @Int) $ xss) === (V.pack . List.concat $ xss)
        prop "vector concat === List.concat" $ \ xss ->
            (V.concat . List.map (V.pack @V.PrimVector @Word8) $ xss) === (V.pack . List.concat $ xss)

    describe "vector concatMap == List.concatMap" $ do
        prop "vector concatMap === List.concatMap" $ \ xss (Fun _ f) ->
            (V.concatMap (V.pack @V.Vector @Integer . f) . V.pack $ xss)  === (V.pack . List.concatMap f $ xss)
        prop "vector concatMap === List.concatMap" $ \ xss (Fun _ f) ->
            (V.concatMap (V.pack @V.PrimVector @Int . f) . V.pack $ xss) === (V.pack . List.concatMap f $ xss)
        prop "vector concatMap === List.concatMap" $ \ xss (Fun _ f) ->
            (V.concatMap (V.pack @V.PrimVector @Word8 . f) . V.pack $ xss) === (V.pack . List.concatMap f $ xss)

    describe "vector all == List.all" $ do
        prop "vector all === List.all" $ \ xs (Fun _ f) ->
            (V.all f . V.pack @V.Vector @Integer $ xs)  === (List.all f $ xs)
        prop "vector all === List.all" $ \ xs (Fun _ f) ->
            (V.all f . V.pack @V.PrimVector @Int $ xs)  === (List.all f $ xs)
        prop "vector all === List.all" $ \ xs (Fun _ f) ->
            (V.all f . V.pack @V.PrimVector @Word8 $ xs)  === (List.all f $ xs)

    describe "vector any == List.any" $ do
        prop "vector any === List.any" $ \ xs (Fun _ f) ->
            (V.any f . V.pack @V.Vector @Integer $ xs)  === (List.any f $ xs)
        prop "vector any === List.any" $ \ xs (Fun _ f) ->
            (V.any f . V.pack @V.PrimVector @Int $ xs)  === (List.any f $ xs)
        prop "vector any === List.any" $ \ xs (Fun _ f) ->
            (V.any f . V.pack @V.PrimVector @Word8 $ xs)  === (List.any f $ xs)

    describe "vector sum == Prelude.sum" $ do
        prop "vector sum === List.sum" $ \ xs ->
            (V.sum . V.pack @V.Vector @Integer $ xs)  === (sum $ xs)
        prop "vector sum === List.sum" $ \ xs ->
            (V.sum . V.pack @V.PrimVector @Int $ xs)  === (sum $ xs)
        prop "vector sum === List.sum" $ \ xs ->
            (V.sum . V.pack @V.PrimVector @Word8 $ xs)  === (sum $ xs)

    describe "vector product == Prelude.product" $ do
        prop "vector product === List.product" $ \ xs ->
            (V.product . V.pack @V.Vector @Integer $ xs)  === (product $ xs)
        prop "vector product === List.product" $ \ xs ->
            (V.product . V.pack @V.PrimVector @Int $ xs)  === (product $ xs)
        prop "vector product === List.product" $ \ xs ->
            (V.product . V.pack @V.PrimVector @Word8 $ xs)  === (product $ xs)

    describe "vector product' == Prelude.product" $ do
        prop "vector product' === List.product" $ \ xs ->
            (V.product' . V.pack @V.Vector @Integer $ xs)  === (product $ xs)
        prop "vector product === List.product" $ \ xs ->
            (V.product' . V.pack @V.PrimVector @Int $ xs)  === (product $ xs)
        prop "vector product === List.product" $ \ xs ->
            (V.product' . V.pack @V.PrimVector @Word8 $ xs)  === (product $ xs)

    describe "vector maximumMaybe == Just . List.maximum" $ do
        prop "vector maximumMaybe === Just . List.maximum" $ \ (NonEmpty xs) ->
            (V.maximumMaybe . V.pack @V.Vector @Integer $ xs)  === (Just . List.maximum $ xs)
        prop "vector maximumMaybe === Just . List.maximum" $ \ (NonEmpty xs) ->
            (V.maximumMaybe . V.pack @V.PrimVector @Int $ xs)  === (Just . List.maximum $ xs)
        prop "vector maximumMaybe === Just . List.maximum" $ \ (NonEmpty xs) ->
            (V.maximumMaybe . V.pack @V.PrimVector @Word8 $ xs)  === (Just . List.maximum $ xs)

    describe "vector minimumMaybe == Just . List.minimum" $ do
        prop "vector minimumMaybe === Just . List.minimum" $ \ (NonEmpty xs) ->
            (V.minimumMaybe . V.pack @V.Vector @Integer $ xs)  === (Just . List.minimum $ xs)
        prop "vector minimumMaybe === Just . List.minimum" $ \ (NonEmpty xs) ->
            (V.minimumMaybe . V.pack @V.PrimVector @Int $ xs)  === (Just . List.minimum $ xs)
        prop "vector minimumMaybe === Just . List.minimum" $ \ (NonEmpty xs) ->
            (V.minimumMaybe . V.pack @V.PrimVector @Word8 $ xs)  === (Just . List.minimum $ xs)

    describe "vector count x == List.length . List.filter (==x)" $ do
        prop "vector count === List.length . List.filter (==x)" $ \ xs x ->
            (V.count x . V.pack @V.Vector @Integer $ xs)  === (List.length . List.filter (==x) $ xs)
        prop "vector count === List.length . List.filter (==x)" $ \ xs x ->
            (V.count x . V.pack @V.PrimVector @Int $ xs)  === (List.length . List.filter (==x) $ xs)
        prop "vector count === List.length . List.filter (==x)" $ \ xs x ->
            (V.count x . V.pack @V.PrimVector @Word8 $ xs)  === (List.length . List.filter (==x) $ xs)

    describe "vector mapAccumL == List.mapAccumL" $ do
        prop "vector mapAccumL === Just . List.mapAccumL" $ \ xs x f ->
            (V.unpack @V.Vector <$> (V.mapAccumL (applyFun2 f) x . V.pack @V.Vector @Integer $ xs))
                === (List.mapAccumL (applyFun2 f) x xs :: (Integer, [Integer]))
        prop "vector mapAccumL === Just . List.mapAccumL" $ \ xs x f ->
            (V.unpack @V.PrimVector <$> (V.mapAccumL (applyFun2 f) x . V.pack @V.PrimVector @Int $ xs))
                === (List.mapAccumL (applyFun2 f) x xs :: (Int, [Int]))
        prop "vector mapAccumL === Just . List.mapAccumL" $ \ xs x f ->
            (V.unpack @V.PrimVector <$> (V.mapAccumL (applyFun2 f) x . V.pack @V.PrimVector @Word8 $ xs))
                === (List.mapAccumL (applyFun2 f) x xs :: (Word8, [Word8]))

    describe "vector mapAccumR == List.mapAccumR" $ do
        prop "vector mapAccumR === Just . List.mapAccumR" $ \ xs x f ->
            (V.unpack @V.Vector <$> (V.mapAccumR (applyFun2 f) x . V.pack @V.Vector @Integer $ xs))
                === (List.mapAccumR (applyFun2 f) x xs :: (Integer, [Integer]))
        prop "vector mapAccumR === Just . List.mapAccumR" $ \ xs x f ->
            (V.unpack @V.PrimVector <$> (V.mapAccumR (applyFun2 f) x . V.pack @V.PrimVector @Int $ xs))
                === (List.mapAccumR (applyFun2 f) x xs :: (Int, [Int]))
        prop "vector mapAccumR === Just . List.mapAccumR" $ \ xs x f ->
            (V.unpack @V.PrimVector <$> (V.mapAccumR (applyFun2 f) x . V.pack @V.PrimVector @Word8 $ xs))
                === (List.mapAccumR (applyFun2 f) x xs :: (Word8, [Word8]))

    describe "vector replicate == List.replicate" $ do
        prop "vector replicate = List.replicate" $ \ n x ->
            (V.unpack . V.replicate @V.Vector @Integer n $ x)  === (List.replicate n $ x)
        prop "vector replicate = List.replicate" $ \ n x ->
            (V.unpack . V.replicate @V.PrimVector @Int n $ x)  === (List.replicate n $ x)
        prop "vector replicate = List.replicate" $ \ n x ->
            (V.unpack . V.replicate @V.PrimVector @Word8 n $ x)  === (List.replicate n $ x)

    describe "vector unfoldrN n == List.take n . List.unfoldr" $ do
        prop "vector unfoldrN n == List.take n . List.unfoldr" $ \ (Fun _ f) (x :: Integer) n ->
            (V.unpack . fst . V.unfoldrN @V.Vector @Integer @Integer n f $ x)
                === (List.take n . List.unfoldr f $ x)
        prop "vector unfoldrN n == List.take n . List.unfoldr" $ \ (Fun _ f) (x :: Int) n ->
            (V.unpack . fst . V.unfoldrN @V.PrimVector @Int @Int n f $ x)
                === (List.take n . List.unfoldr f $ x)
        prop "vector unfoldrN n == List.take n . List.unfoldr" $ \ (Fun _ f) (x :: Word8) n ->
            (V.unpack . fst . V.unfoldrN @V.PrimVector @Word8 @Word8 n f $ x)
                === (List.take n . List.unfoldr f $ x)

    describe "vector elem == List.elem" $ do
        prop "vector elem = List.elem" $ \ y x ->
            (V.elem y . V.pack @V.Vector @Integer $ x)  === (List.elem y $ x)
        prop "vector elem = List.elem" $ \ y x ->
            (V.elem y . V.pack @V.PrimVector @Int $ x)  === (List.elem y $ x)
        prop "vector elem = List.elem" $ \ y x ->
            (V.elem y . V.pack @V.PrimVector @Word8 $ x)  === (List.elem y $ x)

    describe "vector elemIndex == List.elemIndex" $ do
        prop "vector elemIndex = List.elemIndex" $ \ y x ->
            (V.elemIndex y . V.pack @V.Vector @Integer $ x)  === (List.elemIndex y $ x)
        prop "vector elemIndex = List.elemIndex" $ \ y x ->
            (V.elemIndex y . V.pack @V.PrimVector @Int $ x)  === (List.elemIndex y $ x)
        prop "vector elemIndex = List.elemIndex" $ \ y x ->
            (V.elemIndex y . V.pack @V.PrimVector @Word8 $ x)  === (List.elemIndex y $ x)