//
//  DateExtensionTests.swift
//  PascalTests
//
//  Created by Gilbert Lo on 3/27/19.
//  Copyright © 2019 Evidation Health, Inc. All rights reserved.
//

import XCTest

class DateExtensionTests: EvidationCommonTests {
    
    func testIsMoreThanYearsAgo() {
        let now = Date()
        XCTAssertFalse(now.isMoreThan(yearsAgo: 1, from: now))
        
        // Test that Monday to Sunday of the same week returns 0
        let calendar = Calendar.current
        let monday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 1,
                           hour: 9, minute: 0, second: 0)
        )
        let sunday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 7,
                           hour: 22, minute: 0, second: 0)
        )
        XCTAssertFalse(monday!.isMoreThan(yearsAgo: 1, from: sunday!))
        
        // Test year boundary (Tuesday Dec 25, 2018 to Saturday Jan 5, 2019)
        let tuesday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2018, month: 12, day: 25,
                           hour: 22, minute: 0, second: 0)
        )
        let saturday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 1, day: 5,
                           hour: 3, minute: 0, second: 0)
        )
        XCTAssertTrue(tuesday!.isMoreThan(yearsAgo: 0, from: saturday!))
        XCTAssertFalse(tuesday!.isMoreThan(yearsAgo: 1, from: saturday!))
        
        // Test Christmas of 2018 (tuesday) to New Years of 2020 (wednesday) is 2 years ago
        let nextYear = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2020, month: 1, day: 1,
                           hour: 12, minute: 0, second: 0)
        )
        XCTAssertTrue(tuesday!.isMoreThan(yearsAgo: 1, from: nextYear!))
        XCTAssertFalse(tuesday!.isMoreThan(yearsAgo: 2, from: nextYear!))
        
        let threeYearsAgo = createDate(numberOfYearsAgo: 3, from: now)
        XCTAssertTrue(threeYearsAgo.isMoreThan(yearsAgo: 1, from: now))
        XCTAssertTrue(threeYearsAgo.isMoreThan(yearsAgo: 2, from: now))
        XCTAssertFalse(threeYearsAgo.isMoreThan(yearsAgo: 3, from: now))
    }

    func testNumberOfWeeksAgo() {
        // Test that Monday to Sunday of the same week returns 0
        let calendar = Calendar.current
        let monday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 1,
                           hour: 9, minute: 0, second: 0)
        )
        let sunday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 7,
                           hour: 22, minute: 0, second: 0)
        )
        XCTAssertEqual(monday!.numberOfWeeksAgo(from: sunday!), 0)
        
        // Test that the previous Sunday to Monday returns 1 week ago
        let previousSunday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 3, day: 31,
                           hour: 22, minute: 0, second: 0)
        )
        XCTAssertEqual(previousSunday!.numberOfWeeksAgo(from: monday!), 1)
        
        // Test year boundary (Tuesday Dec 25, 2018 to Saturday Jan 5, 2019)
        let tuesday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2018, month: 12, day: 25,
                           hour: 22, minute: 0, second: 0)
        )
        let saturday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 1, day: 5,
                           hour: 3, minute: 0, second: 0)
        )
        XCTAssertEqual(tuesday!.numberOfWeeksAgo(from: saturday!), 1)
        
        // Test Christmas of 2018 (tuesday) to New Years of 2020 (wednesday) returns 53
        let nextYear = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2020, month: 1, day: 1,
                           hour: 12, minute: 0, second: 0)
        )
        XCTAssertEqual(tuesday!.numberOfWeeksAgo(from: nextYear!), 53)
        
        let now = Date()
        let twoWeekAgoDate = createDate(numberOfWeeksAgo: 2, from: now)
        XCTAssertEqual(twoWeekAgoDate.numberOfWeeksAgo(from: now), 2)
        
        let tenWeekAgoDate = createDate(numberOfWeeksAgo: 10, from: now)
        XCTAssertEqual(tenWeekAgoDate.numberOfWeeksAgo(from: now), 10)
    }
    
    func testGetClosestWeekday() {
        let calendar = Calendar.current
        let monday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 1,
                           hour: 0, minute: 0, second: 0)
        )
        let mondayMidday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 1,
                           hour: 12, minute: 1, second: 1)
        )
        let sunday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 7,
                           hour: 0, minute: 0, second: 0)
        )
        // Date precision:
        // https://stackoverflow.com/questions/46161848/swift-dates-are-equivalent-under-30-nanoseconds
        let milliseconds = 1000000 - 1
        let sundayEvening = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 7,
                           hour: 23, minute: 59, second: 59, nanosecond: milliseconds)
        )
        let lastMonday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 3, day: 25,
                           hour: 0, minute: 0, second: 0)
        )
        let nextMonday = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 8,
                           hour: 0, minute: 0, second: 0)
        )
        
        XCTAssertEqual(monday!.getClosest(.monday, direction: .forward, considerToday: false)!, nextMonday)
        XCTAssertEqual(monday!.getClosest(.monday, direction: .backward, considerToday: true)!, monday)
        XCTAssertEqual(monday!.getClosest(.monday, direction: .backward, considerToday: false)!, lastMonday)
        
        XCTAssertEqual(mondayMidday!.getClosest(.monday, direction: .forward, considerToday: true)!, monday)
        XCTAssertEqual(mondayMidday!.getClosest(.monday, direction: .forward, considerToday: false)!, nextMonday)
        XCTAssertEqual(mondayMidday!.getClosest(.monday, direction: .backward, considerToday: true)!, monday)
        XCTAssertEqual(mondayMidday!.getClosest(.monday, direction: .backward, considerToday: false)!, lastMonday)
        
        XCTAssertEqual(monday!.getClosest(.sunday, direction: .forward, considerToday: false)!, sunday)
        XCTAssertEqual(monday!.getClosest(.sunday, direction: .forward, considerToday: true)!, sunday)
        
        XCTAssertEqual(sunday!.getClosest(.monday, direction: .forward, considerToday: true)!, nextMonday)
        XCTAssertEqual(sunday!.getClosest(.monday, direction: .forward, considerToday: false)!, nextMonday)
        XCTAssertEqual(sunday!.getClosest(.monday, direction: .backward, considerToday: true)!, monday)
        XCTAssertEqual(sunday!.getClosest(.monday, direction: .backward, considerToday: false)!, monday)
        
        XCTAssertEqual(sundayEvening!.getClosest(.sunday, direction: .forward, considerToday: true)!, sunday)
        XCTAssertEqual(sundayEvening!.getClosest(.sunday, direction: .backward, considerToday: true)!, sunday)
        
        XCTAssertEqual(sundayEvening!.getClosest(.monday, direction: .forward, considerToday: true)!, nextMonday)
        XCTAssertEqual(sundayEvening!.getClosest(.monday, direction: .forward, considerToday: false)!, nextMonday)
        XCTAssertEqual(sundayEvening!.getClosest(.monday, direction: .backward, considerToday: true)!, monday)
        XCTAssertEqual(sundayEvening!.getClosest(.monday, direction: .backward, considerToday: false)!, monday)
    }
    
    func testNumberOfDaysAgo() {
        // Test that same day returns 0
        let calendar = Calendar.current
        var dateOne = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 1,
                           hour: 9, minute: 0, second: 0)
        )
        var dateTwo = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 1,
                           hour: 22, minute: 0, second: 0)
        )
        XCTAssertEqual(dateOne!.numberOfDaysAgo(from: dateTwo!), 0)
        XCTAssertEqual(dateTwo!.numberOfDaysAgo(from: dateOne!), 0)
        
        // Test more than 48 hours, less than 72 hours difference
        dateOne = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 5,
                           hour: 9, minute: 0, second: 0)
        )
        
        dateTwo = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 7,
                           hour: 22, minute: 0, second: 0)
        )
        XCTAssertEqual(dateOne!.numberOfDaysAgo(from: dateTwo!), 2)
        XCTAssertEqual(dateTwo!.numberOfDaysAgo(from: dateOne!), -2)
        
        // Test 23 hour 59 mins 59 sec difference
        dateOne = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 5,
                           hour: 0, minute: 0, second: 0)
        )
        
        dateTwo = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 4, day: 5,
                           hour: 23, minute: 59, second: 59)
        )
        XCTAssertEqual(dateOne!.numberOfDaysAgo(from: dateTwo!), 0)
        XCTAssertEqual(dateTwo!.numberOfDaysAgo(from: dateOne!), 0)
    }
    
    func testAddDays() {
        let calendar = Calendar.current
        let referenceDate = calendar.date(from:
            DateComponents(calendar: calendar,
                           year: 2019, month: 6, day: 27,
                           hour: 0, minute: 0, second: 0)
        )!
        
        var testDate = referenceDate.addDays(day: 2)
        XCTAssertEqual(testDate.timeIntervalSince(referenceDate), 2 * 24 * 60 * 60)
        
        testDate = referenceDate.addDays(day: 0)
        XCTAssertEqual(testDate.timeIntervalSince(referenceDate), 0)
        
        // Test negative days
        testDate = referenceDate.addDays(day: -2)
        XCTAssertEqual(testDate.timeIntervalSince(referenceDate), -2 * 24 * 60 * 60)
    }
    
    func testRoundToMinuteInterval() {
        var comps = DateComponents()
        comps.year = 2020
        comps.month = 1
        comps.day = 1
        comps.hour = 23
        comps.minute = 57
        
        var roundedComps = comps
        roundedComps.minute = 55 // Round down
        
        var calendar = Calendar(identifier: .gregorian)
        calendar.locale = Locale(identifier: "en_US_POSIX")
        var date = calendar.date(from: comps)
        
        var roundedDate = date?.round(toMinuteInterval: 5)
        XCTAssertNotNil(roundedDate)
        XCTAssertEqual(roundedDate, calendar.date(from: roundedComps))
        
        comps.minute = 58
        // Round up to midnight of the next day
        roundedComps.day = 2
        roundedComps.hour = 0
        roundedComps.minute = 0
        
        date = calendar.date(from: comps)
        roundedDate = date?.round(toMinuteInterval: 5)
        XCTAssertNotNil(roundedDate)
        XCTAssertEqual(roundedDate, calendar.date(from: roundedComps))
     
        // Change minute interval to 20
        comps.minute = 19
        roundedComps.day = 1
        roundedComps.hour = 23
        roundedComps.minute = 20
        date = calendar.date(from: comps)
        roundedDate = date?.round(toMinuteInterval: 20)
        XCTAssertNotNil(roundedDate)
        XCTAssertEqual(roundedDate, calendar.date(from: roundedComps))
        
        comps.minute = 20
        comps.second = 33
        date = calendar.date(from: comps)
        roundedDate = date?.round(toMinuteInterval: 20)
        XCTAssertNotNil(roundedDate)
        XCTAssertEqual(roundedDate, calendar.date(from: roundedComps))
        
        comps.minute = 30
        roundedComps.minute = 40
        date = calendar.date(from: comps)
        roundedDate = date?.round(toMinuteInterval: 20)
        XCTAssertNotNil(roundedDate)
        XCTAssertEqual(roundedDate, calendar.date(from: roundedComps))
    }

    private func createDate(numberOfWeeksAgo: Int, from date: Date) -> Date {
        return Calendar.current.date(byAdding: .day, value: -(7 * numberOfWeeksAgo), to: date)!
    }
    
    private func createDate(numberOfYearsAgo: Int, from date: Date) -> Date {
        return Calendar.current.date(byAdding: .year, value: -numberOfYearsAgo, to: date)!
    }

}
