module Test.Calendar.Valid (
    testValid,
) where

import Data.Time
import Data.Time.Calendar.Julian
import Data.Time.Calendar.OrdinalDate
import Data.Time.Calendar.WeekDate
import Test.QuickCheck.Property
import Test.Tasty
import Test.Tasty.QuickCheck hiding (reason)

validResult :: (Eq c, Show c, Eq t, Show t) => (s -> c) -> Bool -> (t -> c) -> (c -> t) -> (c -> Maybe t) -> s -> Result
validResult sc valid toComponents fromComponents fromComponentsValid s = let
    c = sc s
    mt = fromComponentsValid c
    t' = fromComponents c
    c' = toComponents t'
    in if valid
        then case mt of
            Nothing -> rejected
            Just t ->
                if t' /= t
                    then failed{reason = "'fromValid' gives " ++ show t ++ ", but 'from' gives " ++ show t'}
                    else
                        if c' /= c
                            then
                                failed
                                    { reason =
                                        "found valid, but converts "
                                            ++ show c
                                            ++ " -> "
                                            ++ show t'
                                            ++ " -> "
                                            ++ show c'
                                    }
                            else succeeded
        else case mt of
            Nothing ->
                if c' /= c
                    then succeeded
                    else failed{reason = show c ++ " found invalid, but converts with " ++ show t'}
            Just _ -> rejected

validTest ::
    (Arbitrary s, Show s, Eq c, Show c, Eq t, Show t) =>
    String ->
    (s -> c) ->
    (t -> c) ->
    (c -> t) ->
    (c -> Maybe t) ->
    TestTree
validTest name sc toComponents fromComponents fromComponentsValid =
    testGroup
        name
        [ testProperty "valid" $ property $ validResult sc True toComponents fromComponents fromComponentsValid
        , testProperty "invalid" $ property $ validResult sc False toComponents fromComponents fromComponentsValid
        ]

toSundayStartWeek :: Day -> (Integer, Int, Int)
toSundayStartWeek day = let
    (y, _) = toOrdinalDate day
    (w, d) = sundayStartWeek day
    in (y, w, d)

toMondayStartWeek :: Day -> (Integer, Int, Int)
toMondayStartWeek day = let
    (y, _) = toOrdinalDate day
    (w, d) = mondayStartWeek day
    in (y, w, d)

newtype WYear
    = MkWYear Year
    deriving (Eq, Show)

instance Arbitrary WYear where
    arbitrary = fmap MkWYear $ choose (-1000, 3000)

newtype WMonthOfYear
    = MkWMonthOfYear MonthOfYear
    deriving (Eq, Show)

instance Arbitrary WMonthOfYear where
    arbitrary = fmap MkWMonthOfYear $ choose (-5, 17)

newtype WDayOfMonth
    = MkWDayOfMonth DayOfMonth
    deriving (Eq, Show)

instance Arbitrary WDayOfMonth where
    arbitrary = fmap MkWDayOfMonth $ choose (-5, 35)

newtype WDayOfYear
    = MkWDayOfYear DayOfYear
    deriving (Eq, Show)

instance Arbitrary WDayOfYear where
    arbitrary = fmap MkWDayOfYear $ choose (-20, 400)

newtype WWeekOfYear
    = MkWWeekOfYear WeekOfYear
    deriving (Eq, Show)

instance Arbitrary WWeekOfYear where
    arbitrary = fmap MkWWeekOfYear $ choose (-5, 60)

newtype WDayOfWeek
    = MkWDayOfWeek Int
    deriving (Eq, Show)

instance Arbitrary WDayOfWeek where
    arbitrary = fmap MkWDayOfWeek $ choose (-5, 15)

fromYMD :: (WYear, WMonthOfYear, WDayOfMonth) -> (Year, MonthOfYear, DayOfMonth)
fromYMD (MkWYear y, MkWMonthOfYear ym, MkWDayOfMonth md) = (y, ym, md)

fromYD :: (WYear, WDayOfYear) -> (Year, DayOfYear)
fromYD (MkWYear y, MkWDayOfYear yd) = (y, yd)

fromYWD :: (WYear, WWeekOfYear, WDayOfWeek) -> (Year, WeekOfYear, Int)
fromYWD (MkWYear y, MkWWeekOfYear yw, MkWDayOfWeek wd) = (y, yw, wd)

testValid :: TestTree
testValid =
    testGroup
        "testValid"
        [ validTest
            "Gregorian"
            fromYMD
            toGregorian
            (\(y, m, d) -> fromGregorian y m d)
            (\(y, m, d) -> fromGregorianValid y m d)
        , validTest
            "OrdinalDate"
            fromYD
            toOrdinalDate
            (\(y, d) -> fromOrdinalDate y d)
            (\(y, d) -> fromOrdinalDateValid y d)
        , validTest
            "WeekDate"
            fromYWD
            toWeekDate
            (\(y, w, d) -> fromWeekDate y w d)
            (\(y, w, d) -> fromWeekDateValid y w d)
        , validTest
            "SundayStartWeek"
            fromYWD
            toSundayStartWeek
            (\(y, w, d) -> fromSundayStartWeek y w d)
            (\(y, w, d) -> fromSundayStartWeekValid y w d)
        , validTest
            "MondayStartWeek"
            fromYWD
            toMondayStartWeek
            (\(y, w, d) -> fromMondayStartWeek y w d)
            (\(y, w, d) -> fromMondayStartWeekValid y w d)
        , validTest "Julian" fromYMD toJulian (\(y, m, d) -> fromJulian y m d) (\(y, m, d) -> fromJulianValid y m d)
        ]
