# OPTIONS_GHC -Wall #
# LANGUAGE CPP #
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 708
  For the IsList test
#if __GLASGOW_HASKELL__ == 708
  For pattern synonym use only in GHC 7.8
#endif
#endif


--------------------------------------------------------------------------------

module Main (main) where

--------------------------------------------------------------------------------

import Prelude hiding (concat, foldr, head, map, replicate, tail)
import qualified Data.List as List
import Test.QuickCheck
import Test.QuickCheck.Instances ()
import Test.QuickCheck.Function

import Data.DList.NonEmpty

import OverloadedStrings (testOverloadedStrings)

import Data.Semigroup (Semigroup(..))
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NE

--------------------------------------------------------------------------------

eqWith :: (Eq b, Show b) => (a -> b) -> (a -> b) -> a -> Property
eqWith f g x = f x === g x

eqOn :: (Eq b, Show b) => (a -> Bool) -> (a -> b) -> (a -> b) -> a -> Property
eqOn c f g x = c x ==> f x === g x

--------------------------------------------------------------------------------

prop_model :: NonEmpty Int -> Property
prop_model = eqWith id (toNonEmpty . fromNonEmpty)

prop_singleton :: Int -> Property
prop_singleton = eqWith (:|[]) (toNonEmpty . singleton)

prop_cons :: Int -> NonEmpty Int -> Property
prop_cons c = eqWith (NE.cons c) (toNonEmpty . cons c . fromNonEmpty)

prop_snoc :: NonEmpty Int -> Int -> Property
prop_snoc xs c = xs <> (c :| []) === toNonEmpty (snoc (fromNonEmpty xs) c)

prop_append :: NonEmpty Int -> NonEmpty Int -> Property
prop_append xs ys = xs <> ys === toNonEmpty (fromNonEmpty xs `append` fromNonEmpty ys)

prop_concat1 :: NonEmpty (NonEmpty Int) -> Property
prop_concat1 = eqWith sconcat (toNonEmpty . concat1 . fmap fromNonEmpty)

-- The condition reduces the size of replications and thus the eval time.
prop_replicate :: Int -> Int -> Property
prop_replicate n =
  eqOn (const (n < 100)) (rep n) (toNonEmpty . replicate n)
  where
    rep m x = x :| List.replicate (m - 1) x

prop_head :: NonEmpty Int -> Property
prop_head = eqWith NE.head (head . fromNonEmpty)

prop_tail :: NonEmpty Int -> Property
prop_tail = eqWith NE.tail (tail . fromNonEmpty)

prop_unfoldr :: Fun Int (Int, Maybe Int) -> Int -> Int -> Property
prop_unfoldr (Fun _ f) n =
  eqOn (const (n >= 0)) (NE.take n . NE.unfoldr f) (NE.take n . toNonEmpty . unfoldr f)

prop_map :: Fun Int Int -> NonEmpty Int -> Property
prop_map (Fun _ f) = eqWith (fmap f) (toNonEmpty . map f . fromNonEmpty)

prop_map_fusion :: (Int -> Int) -> (a -> Int) -> NonEmpty a -> Property
prop_map_fusion f g =
  eqWith (fmap f . fmap g) (toNonEmpty . map f . map g . fromNonEmpty)

prop_show_read :: [Int] -> Property
prop_show_read = eqWith id (read . show)

prop_read_show :: NonEmpty Int -> Property
prop_read_show x = eqWith id (show . f . read) $ "fromNonEmpty (" <> show x <> ")"
  where
    f :: NonEmptyDList Int -> NonEmptyDList Int
    f = id

#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 708
  | Test that the IsList instance methods compile and work with simple lists
prop_IsList :: Property
prop_IsList = test_fromNonEmpty [1,2,3] .&&. test_toNonEmpty (fromNonEmpty [1,2,3])
  where
    test_fromNonEmpty, test_toNonEmpty :: NonEmptyDList Int -> Property
    test_fromNonEmpty x = x === fromNonEmpty [1,2,3]
    test_toNonEmpty [1,2,3] = property True
    test_toNonEmpty _       = property False

prop_patterns :: NonEmpty Int -> Property
prop_patterns xs = case fromNonEmpty xs of
  Cons y ys -> xs === (y:|ys)
  _         -> property False
#endif

prop_Semigroup_append :: NonEmpty Int -> NonEmpty Int -> Property
prop_Semigroup_append xs ys = xs <> ys === toNonEmpty (fromNonEmpty xs <> fromNonEmpty ys)

prop_Semigroup_sconcat :: NonEmpty (NonEmpty Int) -> Property
prop_Semigroup_sconcat xs = sconcat xs === toNonEmpty (sconcat (fmap fromNonEmpty xs))

prop_Semigroup_stimes :: Int -> NonEmpty Int -> Property
prop_Semigroup_stimes n xs = n >=1 ==>
    stimes n xs === toNonEmpty (stimes n (fromNonEmpty xs))

--------------------------------------------------------------------------------

props :: [(String, Property)]
props =
  [ ("model",             property prop_model)
  , ("singleton",         property prop_singleton)
  , ("cons",              property prop_cons)
  , ("snoc",              property prop_snoc)
  , ("append",            property prop_append)
  , ("concat1",           property prop_concat1)
  , ("replicate",         property prop_replicate)
  , ("head",              property prop_head)
  , ("tail",              property prop_tail)
  , ("unfoldr",           property prop_unfoldr)
  , ("map",               property prop_map)
  , ("map fusion",        property (prop_map_fusion (+1) (+1)))
  , ("read . show",       property prop_show_read)
  , ("show . read",       property prop_read_show)
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 708
  , ("IsList",            property prop_IsList)
  , ("patterns",          property prop_patterns)
#endif
  , ("Semigroup <>",      property prop_Semigroup_append)
  , ("Semigroup sconcat", property prop_Semigroup_sconcat)
  , ("Semigroup stimes",  property prop_Semigroup_stimes)
  ]

--------------------------------------------------------------------------------

main :: IO ()
main = do
  testOverloadedStrings
  quickCheck $ conjoin $ fmap (uncurry label) props

------------------------------------------------------------------------------
------------------------------------------------------------------------------
------------------------------------------------------------------------------
------------------------------------------------------------------------------
 The condition reduces the size of replications and thus the eval time.
------------------------------------------------------------------------------
------------------------------------------------------------------------------
# OPTIONS_GHC -Wall #
# LANGUAGE CPP #
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 708
  For the IsList test
#if __GLASGOW_HASKELL__ == 708
  For pattern synonym use only in GHC 7.8
#endif
#endif



module Main (main) where


import Prelude hiding (concat, foldr, head, map, replicate, tail)
import qualified Data.List as List
import Test.QuickCheck
import Test.QuickCheck.Instances ()
import Test.QuickCheck.Function

import Data.DList.NonEmpty

import OverloadedStrings (testOverloadedStrings)

import Data.Semigroup (Semigroup(..))
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NE


eqWith :: (Eq b, Show b) => (a -> b) -> (a -> b) -> a -> Property
eqWith f g x = f x === g x

eqOn :: (Eq b, Show b) => (a -> Bool) -> (a -> b) -> (a -> b) -> a -> Property
eqOn c f g x = c x ==> f x === g x


prop_model :: NonEmpty Int -> Property
prop_model = eqWith id (toNonEmpty . fromNonEmpty)

prop_singleton :: Int -> Property
prop_singleton = eqWith (:|[]) (toNonEmpty . singleton)

prop_cons :: Int -> NonEmpty Int -> Property
prop_cons c = eqWith (NE.cons c) (toNonEmpty . cons c . fromNonEmpty)

prop_snoc :: NonEmpty Int -> Int -> Property
prop_snoc xs c = xs <> (c :| []) === toNonEmpty (snoc (fromNonEmpty xs) c)

prop_append :: NonEmpty Int -> NonEmpty Int -> Property
prop_append xs ys = xs <> ys === toNonEmpty (fromNonEmpty xs `append` fromNonEmpty ys)

prop_concat1 :: NonEmpty (NonEmpty Int) -> Property
prop_concat1 = eqWith sconcat (toNonEmpty . concat1 . fmap fromNonEmpty)

prop_replicate :: Int -> Int -> Property
prop_replicate n =
  eqOn (const (n < 100)) (rep n) (toNonEmpty . replicate n)
  where
    rep m x = x :| List.replicate (m - 1) x

prop_head :: NonEmpty Int -> Property
prop_head = eqWith NE.head (head . fromNonEmpty)

prop_tail :: NonEmpty Int -> Property
prop_tail = eqWith NE.tail (tail . fromNonEmpty)

prop_unfoldr :: Fun Int (Int, Maybe Int) -> Int -> Int -> Property
prop_unfoldr (Fun _ f) n =
  eqOn (const (n >= 0)) (NE.take n . NE.unfoldr f) (NE.take n . toNonEmpty . unfoldr f)

prop_map :: Fun Int Int -> NonEmpty Int -> Property
prop_map (Fun _ f) = eqWith (fmap f) (toNonEmpty . map f . fromNonEmpty)

prop_map_fusion :: (Int -> Int) -> (a -> Int) -> NonEmpty a -> Property
prop_map_fusion f g =
  eqWith (fmap f . fmap g) (toNonEmpty . map f . map g . fromNonEmpty)

prop_show_read :: [Int] -> Property
prop_show_read = eqWith id (read . show)

prop_read_show :: NonEmpty Int -> Property
prop_read_show x = eqWith id (show . f . read) $ "fromNonEmpty (" <> show x <> ")"
  where
    f :: NonEmptyDList Int -> NonEmptyDList Int
    f = id

#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 708
  | Test that the IsList instance methods compile and work with simple lists
prop_IsList :: Property
prop_IsList = test_fromNonEmpty [1,2,3] .&&. test_toNonEmpty (fromNonEmpty [1,2,3])
  where
    test_fromNonEmpty, test_toNonEmpty :: NonEmptyDList Int -> Property
    test_fromNonEmpty x = x === fromNonEmpty [1,2,3]
    test_toNonEmpty [1,2,3] = property True
    test_toNonEmpty _       = property False

prop_patterns :: NonEmpty Int -> Property
prop_patterns xs = case fromNonEmpty xs of
  Cons y ys -> xs === (y:|ys)
  _         -> property False
#endif

prop_Semigroup_append :: NonEmpty Int -> NonEmpty Int -> Property
prop_Semigroup_append xs ys = xs <> ys === toNonEmpty (fromNonEmpty xs <> fromNonEmpty ys)

prop_Semigroup_sconcat :: NonEmpty (NonEmpty Int) -> Property
prop_Semigroup_sconcat xs = sconcat xs === toNonEmpty (sconcat (fmap fromNonEmpty xs))

prop_Semigroup_stimes :: Int -> NonEmpty Int -> Property
prop_Semigroup_stimes n xs = n >=1 ==>
    stimes n xs === toNonEmpty (stimes n (fromNonEmpty xs))


props :: [(String, Property)]
props =
  [ ("model",             property prop_model)
  , ("singleton",         property prop_singleton)
  , ("cons",              property prop_cons)
  , ("snoc",              property prop_snoc)
  , ("append",            property prop_append)
  , ("concat1",           property prop_concat1)
  , ("replicate",         property prop_replicate)
  , ("head",              property prop_head)
  , ("tail",              property prop_tail)
  , ("unfoldr",           property prop_unfoldr)
  , ("map",               property prop_map)
  , ("map fusion",        property (prop_map_fusion (+1) (+1)))
  , ("read . show",       property prop_show_read)
  , ("show . read",       property prop_read_show)
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 708
  , ("IsList",            property prop_IsList)
  , ("patterns",          property prop_patterns)
#endif
  , ("Semigroup <>",      property prop_Semigroup_append)
  , ("Semigroup sconcat", property prop_Semigroup_sconcat)
  , ("Semigroup stimes",  property prop_Semigroup_stimes)
  ]


main :: IO ()
main = do
  testOverloadedStrings
  quickCheck $ conjoin $ fmap (uncurry label) props
