module Main where

import Data.Proxy
import Data.Word
import Prelude
import Test.Tasty
import Test.Tasty.HUnit

import Data.Countable
import Data.Empty
import Data.Searchable

import Golden
import Show
import Three
import TypeName

(@/=?) :: (Eq a, Show a) => a -> a -> Assertion
unexpected @/=? actual
    | unexpected /= actual = return ()
_unexpected @/=? actual = assertFailure $ "got unexpected " ++ show actual

prevMaybeNext :: (Countable a, Show a) => Maybe a -> Assertion
prevMaybeNext ma =
    case countMaybeNext ma of
        Just a' -> countPrevious a' @=? ma
        Nothing -> return ()

countableTests' :: (Show a, Countable a) => a -> [TestTree]
countableTests' a =
    [ testCase "maybeNextDifferent" $ (Just a) @/=? countMaybeNext (Just a)
    , testCase "prevDifferent" $ (Just a) @/=? (countPrevious a)
    , testCase "maybeNextPrev" $ (Just a) @=? countMaybeNext (countPrevious a)
    , testCase "prevMaybeNext" $ prevMaybeNext (Just a)
    ]

findInNext :: (Countable a) => Int -> a -> TestTree
findInNext n a = testCase "findInNext" $ findInNext' n Nothing
  where
    findInNext' 0 _ = assertFailure "failed"
    findInNext' _ (Just x)
        | x == a = return ()
    findInNext' n' mx =
        case countMaybeNext mx of
            Nothing -> assertFailure "failed"
            mx' -> findInNext' (n' - 1) mx'

countableTests :: (Show a, Countable a) => a -> [TestTree]
countableTests a = (countableTests' a) ++ [findInNext 1000 a]

nextIsMaybeNext :: (Show a, InfiniteCountable a) => Maybe a -> TestTree
nextIsMaybeNext ma = testCase "nextIsMaybeNext" $ (Just (countNext ma)) @=? (countMaybeNext ma)

infiniteCountableTests :: (Show a, InfiniteCountable a) => a -> [TestTree]
infiniteCountableTests a = (countableTests a) ++ [nextIsMaybeNext (Just a)]

checkN :: (Show a, Countable a) => (String -> IO ()) -> Int -> Maybe a -> IO ()
checkN _ 0 _ = return ()
checkN write n ma = let
    ma' = countMaybeNext ma
    in do
           prevMaybeNext ma
           write (show ma ++ "\n")
           case ma' of
               Nothing -> return ()
               _ -> checkN write (n - 1) ma'

testType ::
       forall a. (TypeName a, Show a)
    => (a -> [TestTree])
    -> [a]
    -> TestTree
testType tests vals = testGroup (typeName (Proxy :: Proxy a)) $ fmap (\a -> testGroup (show a) (tests a)) vals
    -- This is to prevent overlapping Show function instance in Text.Show.Functions,
    -- which gets imported somehow with lts-5.

newtype WrapFunction a b =
    MkWrapFunction (a -> b)
    deriving (Eq, Searchable, Countable, TypeName)

instance (Show a, Finite a, Show b) => Show (WrapFunction a b) where
    show (MkWrapFunction f) = showFunction f

instance (Finite a, Finite b) => Finite (WrapFunction a b) where
    allValues = fmap MkWrapFunction allValues
    assemble wabfx = let
        foo abx (MkWrapFunction ab) = abx ab
        in fmap foo $ assemble (wabfx . MkWrapFunction)

allTests :: TestTree
allTests =
    testGroup
        "countable"
        [ testType countableTests (allValues :: [()])
        , testType countableTests (allValues :: [Bool])
        , testType countableTests ([0, 3, 255] :: [Word8])
        , testType countableTests (allValues :: [Maybe ()])
        , testType countableTests (allValues :: [Maybe Bool])
        , testType countableTests (allValues :: [Maybe (Maybe Bool)])
        , testType countableTests ([[], [0], [2], [-1, 1], [0, 0, 0]] :: [[Integer]])
        , testType countableTests' ([[1, 2, 1], [-5, 17, 112]] :: [[Integer]])
        , testType countableTests ([[], [True, True]] :: [[Bool]])
        , testType infiniteCountableTests ([0, 1, -1, 3, -7] :: [Integer])
        , testType countableTests (allValues :: [WrapFunction Three Three])
        , testType countableTests (allValues :: [None])
        , testType countableTests ([[] :: [None]])
        , testGroup
              "list"
              [ goldenVsWriteString "Bool" "test/count.Bool.ref" $ \write -> checkN write 40 (Nothing :: Maybe [Bool])
              , goldenVsWriteString "Word8" "test/count.Word8.ref" $ \write ->
                    checkN write 40 (Nothing :: Maybe [Word8])
              , goldenVsWriteString "Integer" "test/count.Integer.ref" $ \write ->
                    checkN write 40 (Nothing :: Maybe [Integer])
              ]
        ]

main :: IO ()
main = defaultMain allTests
