{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}
module Main where
import Prelude hiding (foldr, foldl, foldl', foldr1, foldl1, length, null, sum,
                       product, all, any, and, or)
import Data.Foldable
import Control.Exception
import Data.Array
import Data.Foldable
import Data.Typeable
import Data.Either
import Control.Applicative
import Control.DeepSeq
#if __GLASGOW_HASKELL__ < 709
import qualified Data.List as L
#else
import qualified GHC.List as L
#endif

data BadElementException = BadFirst | BadLast deriving (Show, Typeable, Eq)
instance Exception BadElementException

newtype ForceDefault f a = ForceDefault (f a)
instance Foldable f => Foldable (ForceDefault f) where
  foldMap f (ForceDefault c) = foldMap f c

goodLists, badFronts, badBacks :: [[Integer]]
goodLists = [[0..n] | n <- [(-1)..5]]
badFronts = map (throw BadFirst :) goodLists
badBacks  = map (++ [throw BadLast]) goodLists
doubleBads = map (\l -> throw BadFirst : l ++ [throw BadLast]) goodLists
lists =
        goodLists
        ++ badFronts
        ++ badBacks
        ++ doubleBads

makeArray xs = array (1::Int, length xs) (zip [1..] xs)

arrays = map makeArray lists
goodArrays = map makeArray goodLists


strictCons x y = x + 10*y
rightLazyCons x y = x
leftLazyCons x y = y

conses :: [Integer -> Integer -> Integer]
conses = [(+), strictCons, rightLazyCons, leftLazyCons]

runOneRight :: forall f . Foldable f =>
                             (forall a b . (a -> b -> b) -> b -> f a -> b) ->
                             (Integer -> Integer -> Integer) -> f Integer ->
                             IO (Either BadElementException Integer)
runOneRight fol f container = try (evaluate (fol f 12 container))

runOne1 :: forall f . Foldable f => (forall a . (a -> a -> a) -> f a -> a) ->
                              (Integer -> Integer -> Integer) -> f Integer ->
                              IO (Either BadElementException Integer)
runOne1 fol f container = try (evaluate (fol f container))

runOneLeft :: forall f . Foldable f =>
                             (forall a b . (b -> a -> b) -> b -> f a -> b) ->
                              (Integer -> Integer -> Integer) -> f Integer ->
                              IO (Either BadElementException Integer)
runOneLeft fol f container = try (evaluate (fol f 13 container))

runWithAllRight :: forall f . Foldable f =>
                          (forall a b . (a -> b -> b) -> b -> f a -> b) ->
                          [f Integer] -> IO [Either BadElementException Integer]
runWithAllRight fol containers =
       mapM (uncurry (runOneRight fol)) [(f,c) | f <- conses, c <- containers]

runWithAll1 :: forall f . Foldable f =>
                        (forall a . (a -> a -> a) -> f a -> a) ->
                        [f Integer] -> IO [Either BadElementException Integer]
runWithAll1 fol containers =
          mapM (uncurry (runOne1 fol)) [(f,c) | f <- conses, c <- containers]

runWithAllLeft :: forall f . Foldable f =>
                          (forall a b . (b -> a -> b) -> b -> f a -> b) ->
                          [f Integer] -> IO [Either BadElementException Integer]
runWithAllLeft fol containers = mapM (uncurry (runOneLeft fol))
                              [(f,c) | f <- map flip conses, c <- containers]

testWithAllRight :: forall f . Foldable f =>
                 (forall a b . (a -> b -> b) -> b -> f a -> b) ->
                  (forall a b . (a -> b -> b) -> b -> ForceDefault f a -> b) ->
                   [f Integer] -> IO Bool
testWithAllRight fol1 fol2 containers = (==) <$>
       runWithAllRight fol1 containers <*>
           runWithAllRight fol2 (map ForceDefault containers)

testWithAllLeft :: forall f . Foldable f =>
                   (forall a b . (b -> a -> b) -> b -> f a -> b) ->
                   (forall a b . (b -> a -> b) -> b -> ForceDefault f a -> b) ->
                       [f Integer] -> IO Bool
testWithAllLeft fol1 fol2 containers = (==) <$>
      runWithAllLeft fol1 containers <*>
         runWithAllLeft fol2 (map ForceDefault containers)


testWithAll1 :: forall f . Foldable f =>
                        (forall a . (a -> a -> a) -> f a -> a) ->
                        (forall a . (a -> a -> a) -> ForceDefault f a -> a) ->
                                               [f Integer] -> IO Bool
testWithAll1 fol1 fol2 containers =
  (==) <$> runWithAll1 fol1 containers
            <*> runWithAll1 fol2 (map ForceDefault containers)

checkup f g cs = map f cs == map g (map ForceDefault cs)

main = do
         testWithAllRight foldr foldr arrays >>= print
         testWithAllRight foldr' foldr' arrays >>= print
         testWithAllLeft foldl foldl arrays >>= print
         testWithAllLeft foldl' foldl' arrays >>= print
         testWithAll1 foldl1 foldl1 (filter (not . null) arrays) >>= print
         testWithAll1 foldr1 foldr1 (filter (not . null) arrays) >>= print
         -- we won't bother with the fancy laziness tests for the rest
         print $ checkup length length goodArrays
         print $ checkup sum sum goodArrays
         print $ checkup product product goodArrays
         print $ checkup maximum maximum $ filter (not . null) goodArrays
         print $ checkup minimum minimum $ filter (not . null) goodArrays
         print $ checkup toList toList goodArrays
         print $ checkup null null arrays
