module Test.Calendar.Week (
    testWeek,
) where

import Data.Time.Calendar
import Data.Time.Calendar.OrdinalDate
import Data.Time.Calendar.WeekDate
import Test.Arbitrary ()
import Test.Tasty
import Test.Tasty.HUnit
import Test.TestUtil

testDay :: TestTree
testDay =
    nameTest "day" $ do
        let
            day = fromGregorian 2018 1 9
        assertEqual "" (ModifiedJulianDay 58127) day
        assertEqual "" (2018, 2, 2) $ toWeekDate day
        assertEqual "" Tuesday $ dayOfWeek day

allDaysOfWeek :: [DayOfWeek]
allDaysOfWeek = [Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]

testAllDays :: String -> (DayOfWeek -> IO ()) -> TestTree
testAllDays name f = nameTest name $ fmap (\wd -> nameTest (show wd) $ f wd) allDaysOfWeek

testSucc :: TestTree
testSucc = testAllDays "succ" $ \wd -> assertEqual "" (toEnum $ succ $ fromEnum wd) $ succ wd

testPred :: TestTree
testPred = testAllDays "pred" $ \wd -> assertEqual "" (toEnum $ pred $ fromEnum wd) $ pred wd

testSequences :: TestTree
testSequences =
    nameTest
        "sequence"
        [ nameTest "[Monday .. Sunday]" $ assertEqual "" allDaysOfWeek [Monday .. Sunday]
        , nameTest "[Wednesday .. Wednesday]" $ assertEqual "" [Wednesday] [Wednesday .. Wednesday]
        , nameTest "[Sunday .. Saturday]" $
            assertEqual "" [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] [Sunday .. Saturday]
        , nameTest "[Thursday .. Wednesday]" $
            assertEqual "" [Thursday, Friday, Saturday, Sunday, Monday, Tuesday, Wednesday] [Thursday .. Wednesday]
        , nameTest "[Tuesday ..]"
            $ assertEqual
                ""
                [ Tuesday
                , Wednesday
                , Thursday
                , Friday
                , Saturday
                , Sunday
                , Monday
                , Tuesday
                , Wednesday
                , Thursday
                , Friday
                , Saturday
                , Sunday
                , Monday
                , Tuesday
                ]
            $ take 15 [Tuesday ..]
        , nameTest "[Wednesday, Tuesday ..]"
            $ assertEqual
                ""
                [ Wednesday
                , Tuesday
                , Monday
                , Sunday
                , Saturday
                , Friday
                , Thursday
                , Wednesday
                , Tuesday
                , Monday
                , Sunday
                , Saturday
                , Friday
                , Thursday
                , Wednesday
                ]
            $ take 15 [Wednesday, Tuesday ..]
        , nameTest "[Sunday, Friday ..]" $
            assertEqual "" [Sunday, Friday, Wednesday, Monday, Saturday, Thursday, Tuesday, Sunday] $
                take 8 [Sunday, Friday ..]
        , nameTest "[Monday,Sunday .. Tuesday]" $
            assertEqual "" [Monday, Sunday, Saturday, Friday, Thursday, Wednesday, Tuesday] [Monday, Sunday .. Tuesday]
        , nameTest "[Thursday, Saturday .. Tuesday]" $
            assertEqual "" [Thursday, Saturday, Monday, Wednesday, Friday, Sunday, Tuesday] [Thursday, Saturday .. Tuesday]
        ]

testReadShow :: TestTree
testReadShow = testAllDays "read show" $ \wd -> assertEqual "" wd $ read $ show wd

prop_firstDayOfWeekOnAfter_onAfter :: DayOfWeek -> Day -> Bool
prop_firstDayOfWeekOnAfter_onAfter dw d = firstDayOfWeekOnAfter dw d >= d

prop_firstDayOfWeekOnAfter_Day :: DayOfWeek -> Day -> Bool
prop_firstDayOfWeekOnAfter_Day dw d = dayOfWeek (firstDayOfWeekOnAfter dw d) == dw

prop_toFromWeekCalendar :: FirstWeekType -> DayOfWeek -> Day -> Bool
prop_toFromWeekCalendar wt ws d =
    let
        (y, wy, dw) = toWeekCalendar wt ws d
    in
        fromWeekCalendar wt ws y wy dw == d

prop_weekChanges :: FirstWeekType -> DayOfWeek -> Day -> Bool
prop_weekChanges wt ws d =
    let
        (_, wy0, _) = toWeekCalendar wt ws d
        (_, wy1, dw) = toWeekCalendar wt ws $ succ d
    in
        if dw == ws then wy0 /= wy1 else wy0 == wy1

prop_weekYearWholeStart :: DayOfWeek -> Year -> Bool
prop_weekYearWholeStart ws y =
    let
        d = fromWeekCalendar FirstWholeWeek ws y 1 ws
        (y', dy) = toOrdinalDate d
    in
        y == y' && dy >= 1 && dy <= 7

prop_weekYearMostStart :: DayOfWeek -> Year -> Bool
prop_weekYearMostStart ws y =
    let
        d = fromWeekCalendar FirstMostWeek ws y 2 ws
        (y', dy) = toOrdinalDate d
    in
        y == y' && dy >= 5 && dy <= 11

testDiff :: TestTree
testDiff =
    nameTest
        "diff"
        [ nameTest "Friday - Tuesday" $ assertEqual "" 3 $ dayOfWeekDiff Friday Tuesday
        , nameTest "Tuesday - Friday" $ assertEqual "" 4 $ dayOfWeekDiff Tuesday Friday
        , nameTest "firstDayOfWeekOnAfter_onAfter" prop_firstDayOfWeekOnAfter_onAfter
        , nameTest "firstDayOfWeekOnAfter_Day" prop_firstDayOfWeekOnAfter_Day
        , nameTest "toFromWeekCalendar" prop_toFromWeekCalendar
        , nameTest "weekChanges" prop_weekChanges
        , nameTest "weekYearWholeStart" prop_weekYearWholeStart
        , nameTest "weekYearMostStart" prop_weekYearMostStart
        ]

testWeekDays :: TestTree
testWeekDays =
    nameTest
        "Week Days"
        [ testGroup "weekAllDays" weekAllDaysTests
        , testGroup "weekFirstDay" weekFirstDayTests
        , testGroup "weekLastDay" weekLastDayTests
        ]

weekAllDaysTests :: [TestTree]
weekAllDaysTests =
    [ testGroup
        "Property Tests"
        [ nameTest "Week have 7 days" weekHaveSevenDays
        , nameTest "Day is part of the week" dayIsPartOfWeek
        ]
    , testGroup
        "Unit Tests"
        [ nameTest "FirstDay is less than Day-DayOfWeek" $
            assertEqual
                ""
                [YearMonthDay 2023 12 31 .. YearMonthDay 2024 1 6]
                (weekAllDays Sunday (YearMonthDay 2024 1 1))
        , nameTest "FirstDay is equal to Day-DayOfWeek" $
            assertEqual
                ""
                [YearMonthDay 2024 2 26 .. YearMonthDay 2024 3 3]
                (weekAllDays Monday (YearMonthDay 2024 2 26))
        , nameTest "FirstDay is greater than Day-DayOfWeek" $
            assertEqual
                ""
                [YearMonthDay 2022 2 15 .. YearMonthDay 2022 2 21]
                (weekAllDays Tuesday (YearMonthDay 2022 2 21))
        ]
    ]
  where
    weekHaveSevenDays :: DayOfWeek -> Day -> Bool
    weekHaveSevenDays fd d = length (weekAllDays fd d) == 7

    dayIsPartOfWeek :: DayOfWeek -> Day -> Bool
    dayIsPartOfWeek fd d = d `elem` weekAllDays fd d

weekFirstDayTests :: [TestTree]
weekFirstDayTests =
    [ testGroup
        "Property Tests"
        [ nameTest "FirsyDay matches the Day-DayOfWeek" firstDayMatchesDay
        ]
    , testGroup
        "Unit Tests"
        [ nameTest "FirstDay is less than Day-DayOfWeek" $
            assertEqual
                ""
                (YearMonthDay 2022 2 20)
                (weekFirstDay Sunday (YearMonthDay 2022 2 21))
        , nameTest "FirstDay is equal to Day-DayOfWeek" $
            assertEqual
                ""
                (YearMonthDay 2022 2 21)
                (weekFirstDay Monday (YearMonthDay 2022 2 21))
        , nameTest "FirstDay is greater than Day-DayOfWeek" $
            assertEqual
                ""
                (YearMonthDay 2022 2 15)
                (weekFirstDay Tuesday (YearMonthDay 2022 2 21))
        ]
    ]
  where
    firstDayMatchesDay :: DayOfWeek -> Day -> Bool
    firstDayMatchesDay fd d = dayOfWeek (weekFirstDay fd d) == fd

weekLastDayTests :: [TestTree]
weekLastDayTests =
    [ nameTest "FirstDay is less than Day-DayOfWeek" $
        assertEqual
            ""
            (YearMonthDay 2022 2 26)
            (weekLastDay Sunday (YearMonthDay 2022 2 21))
    , nameTest "FirstDay is equal to Day-DayOfWeek" $
        assertEqual
            ""
            (YearMonthDay 2022 2 27)
            (weekLastDay Monday (YearMonthDay 2022 2 21))
    , nameTest "FirstDay is greater than Day-DayOfWeek" $
        assertEqual
            ""
            (YearMonthDay 2022 2 21)
            (weekLastDay Tuesday (YearMonthDay 2022 2 21))
    ]

testWeek :: TestTree
testWeek =
    nameTest
        "Week"
        [ testDay
        , testSucc
        , testPred
        , testSequences
        , testReadShow
        , testDiff
        , testWeekDays
        ]
