{-# LANGUAGE CPP #-}
{-# LANGUAGE TypeOperators #-}
{-|
Module:      Main
Copyright:   (C) 2015 The University of Kansas
License:     BSD-style (see the file LICENSE)
Maintainer:  Andy Gill
Stability:   Experimental

@QuickCheck@ properties for natural transformations.
-}
module Main (main) where

import Control.Natural

import Data.Foldable (toList)
#if !(MIN_VERSION_base(4,8,0))
import Data.Monoid (Monoid(..))
#endif
import Data.Sequence (Seq, fromList)

import Test.QuickCheck.Instances ()
import Test.Tasty (TestTree, defaultMain, testGroup)
import Test.Tasty.QuickCheck (testProperty)

main :: IO ()
main = defaultMain testProperties

testProperties :: TestTree
testProperties = testGroup "QuickCheck properties"
    [ testProperty "Free theorem ([] :~> Seq)" (prop_freeTheorem (+1)    listSeqNT :: [Int] -> Bool)
    , testProperty "Free theorem (Seq :~> [])" (prop_freeTheorem reverse seqListNT :: Seq String -> Bool)
    , testProperty "Monoid laws"               (prop_monoidLaws listShiftNT listReverseNT listShiftNT :: [Int] -> Bool)
    ]

-- | Verifies the free theorem for natural transformations, i.e., that
-- 
-- @
-- fmap h . r == r . fmap h
-- @
prop_freeTheorem :: (Eq (g b), Functor f, Functor g, Transformation f g t)
                 => (a -> b) -> t -> f a -> Bool
prop_freeTheorem h r t = fmap h (r # t) == (r # fmap h t)

-- | Verifies that natural transformations form a law-abiding 'Monoid', i.e., that
-- 
--  * @mappend mempty x = x@
--
--  * @mappend x mempty = x@
--
--  * @mappend x (mappend y z) = mappend (mappend x y) z@
prop_monoidLaws :: (Eq (f a), Monoid t, Transformation f f t)
                => t -> t -> t -> f a -> Bool
prop_monoidLaws x y z t = (mappend mempty x # t) == (x # t)
                       && (mappend x mempty # t) == (x # t)
                       && (mappend x (mappend y z) # t)
                          == (mappend (mappend x y) z # t)

-- | A natural transformations from lists to lists that 'reverse's.
listReverseNT :: [] :~> []
listReverseNT = NT reverse

-- | A natural transformation from lists to lists that shifts all elements to the left,
-- moving the head element to the back.
listShiftNT :: [] :~> []
listShiftNT = NT $ \l -> case l of
                               []     -> []
                               (x:xs) -> xs ++ [x]

-- | A natural transformation from lists to 'Seq's.
listSeqNT :: [] :~> Seq
listSeqNT = NT fromList

-- | A natural transformation from 'Seq's to lists.
seqListNT :: Seq :~> []
seqListNT = NT toList
