//
//  Date+Manipulations.swift
//  DateToolsTests
//
//  Created by Grayson Webster on 9/28/16.
//  Copyright © 2016 Matthew York. All rights reserved.
//

import Foundation

/**
 *  Extends the Date class by adding manipulation methods for transforming dates
 */
public extension Date {
    // MARK: - StartOf

    /**
     *  Return a date set to the start of a given component.
     *
     *  - parameter component: The date component (second, minute, hour, day, month, or year)
     *
     *  - returns: A date retaining the value of the given component and all larger components,
     *  with all smaller components set to their minimum
     */
    func start(of component: Component) -> Date {
        var newDate = self
        if component == .second {
            newDate.second(second)
        } else if component == .minute {
            newDate.second(0)
        } else if component == .hour {
            newDate.second(0)
            newDate.minute(0)
        } else if component == .day {
            newDate.second(0)
            newDate.minute(0)
            newDate.hour(0)
        } else if component == .month {
            newDate.second(0)
            newDate.minute(0)
            newDate.hour(0)
            newDate.day(1)
        } else if component == .year {
            newDate.second(0)
            newDate.minute(0)
            newDate.hour(0)
            newDate.day(1)
            newDate.month(1)
        }
        return newDate
    }

    /**
     *  Return a date set to the end of a given component.
     *
     *  - parameter component: The date component (second, minute, hour, day, month, or year)
     *
     *  - returns: A date retaining the value of the given component and all larger components,
     *  with all smaller components set to their maximum
     */
    func end(of component: Component) -> Date {
        var newDate = self
        if component == .second {
            newDate.second(newDate.second + 1)
            newDate -= 0.001
        } else if component == .minute {
            newDate.second(60)
            newDate -= 0.001
        } else if component == .hour {
            newDate.second(60)
            newDate -= 0.001
            newDate.minute(59)
        } else if component == .day {
            newDate.second(60)
            newDate -= 0.001
            newDate.minute(59)
            newDate.hour(23)
        } else if component == .month {
            newDate.second(60)
            newDate -= 0.001
            newDate.minute(59)
            newDate.hour(23)
            newDate.day(daysInMonth(date: newDate))
        } else if component == .year {
            newDate.second(60)
            newDate -= 0.001
            newDate.minute(59)
            newDate.hour(23)
            newDate.month(12)
            newDate.day(31)
        }

        return newDate
    }

    func daysInMonth(date: Date) -> Int {
        let month = date.month
        if month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 {
            // 31 day month
            return 31
        } else if month == 2, date.isInLeapYear {
            // February with leap year
            return 29
        } else if month == 2, !date.isInLeapYear {
            // February without leap year
            return 28
        } else {
            // 30 day month
            return 30
        }
    }

    // MARK: - Addition / Subtractions

    /**
     *  # Add (TimeChunk to Date)
     *  Increase a date by the value of a given `TimeChunk`.
     *
     *  - parameter chunk: The amount to increase the date by (ex. 2.days, 4.years, etc.)
     *
     *  - returns: A date with components increased by the values of the
     *  corresponding `TimeChunk` variables
     */
    func add(_ chunk: TimeChunk) -> Date {
        let calendar = Calendar.autoupdatingCurrent
        var components = DateComponents()
        components.year = chunk.years
        components.month = chunk.months
        components.day = chunk.days + (chunk.weeks * 7)
        components.hour = chunk.hours
        components.minute = chunk.minutes
        components.second = chunk.seconds
        return calendar.date(byAdding: components, to: self)!
    }

    /**
     *  # Subtract (TimeChunk from Date)
     *  Decrease a date by the value of a given `TimeChunk`.
     *
     *  - parameter chunk: The amount to decrease the date by (ex. 2.days, 4.years, etc.)
     *
     *  - returns: A date with components decreased by the values of the
     *  corresponding `TimeChunk` variables
     */
    func subtract(_ chunk: TimeChunk) -> Date {
        let calendar = Calendar.autoupdatingCurrent
        var components = DateComponents()
        components.year = -chunk.years
        components.month = -chunk.months
        components.day = -(chunk.days + (chunk.weeks * 7))
        components.hour = -chunk.hours
        components.minute = -chunk.minutes
        components.second = -chunk.seconds
        return calendar.date(byAdding: components, to: self)!
    }

    // MARK: - Operator Overloads

    /**
     *  Operator overload for adding a `TimeChunk` to a date.
     */
    static func + (leftAddend: Date, rightAddend: TimeChunk) -> Date {
        return leftAddend.add(rightAddend)
    }

    /**
     *  Operator overload for subtracting a `TimeChunk` from a date.
     */
    static func - (minuend: Date, subtrahend: TimeChunk) -> Date {
        return minuend.subtract(subtrahend)
    }

    /**
     *  Operator overload for adding a `TimeInterval` to a date.
     */
    static func + (leftAddend: Date, rightAddend: Int) -> Date {
        return leftAddend.addingTimeInterval(TimeInterval(rightAddend))
    }

    /**
     *  Operator overload for subtracting a `TimeInterval` from a date.
     */
    static func - (minuend: Date, subtrahend: Int) -> Date {
        return minuend.addingTimeInterval(-TimeInterval(subtrahend))
    }
}