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)
        ]
