//
//  SwiftDate
//  Parse, validate, manipulate, and display dates, time and timezones in Swift
//
//  Created by Daniele Margutti
//   - Web: https://www.danielemargutti.com
//   - Twitter: https://twitter.com/danielemargutti
//   - Mail: hello@danielemargutti.com
//
//  Copyright © 2019 Daniele Margutti. Licensed under MIT License.
//

import Foundation

public extension TimeInterval {

	struct ComponentsFormatterOptions {

		/// Fractional units may be used when a value cannot be exactly represented using the available units.
		/// For example, if minutes are not allowed, the value “1h 30m” could be formatted as “1.5h”.
		public var allowsFractionalUnits: Bool?

		/// Specify the units that can be used in the output.
		public var allowedUnits: NSCalendar.Unit?

		/// A Boolean value indicating whether to collapse the largest unit into smaller units when a certain threshold is met.
		public var collapsesLargestUnit: Bool?

		/// The maximum number of time units to include in the output string.
		/// If 0 does not cause the elimination of any units.
		public var maximumUnitCount: Int?

		/// The formatting style for units whose value is 0.
		public var zeroFormattingBehavior: DateComponentsFormatter.ZeroFormattingBehavior?

		/// The preferred style for units.
		public var unitsStyle: DateComponentsFormatter.UnitsStyle?

		/// Locale of the formatter
		public var locale: LocaleConvertible? {
			set { calendar.locale = newValue?.toLocale() }
			get { return calendar.locale }
		}

		/// Calendar
        public var calendar = Calendar.autoupdatingCurrent

		public func apply(toFormatter formatter: DateComponentsFormatter) {
            formatter.calendar = calendar

            if let allowsFractionalUnits = self.allowsFractionalUnits {
                formatter.allowsFractionalUnits = allowsFractionalUnits
            }
            if let allowedUnits = self.allowedUnits {
                formatter.allowedUnits = allowedUnits
            }
            if let collapsesLargestUnit = self.collapsesLargestUnit {
                formatter.collapsesLargestUnit = collapsesLargestUnit
            }
            if let maximumUnitCount = self.maximumUnitCount {
                formatter.maximumUnitCount = maximumUnitCount
            }
            if let zeroFormattingBehavior = self.zeroFormattingBehavior {
                formatter.zeroFormattingBehavior = zeroFormattingBehavior
            }
            if let unitsStyle = self.unitsStyle {
                formatter.unitsStyle = unitsStyle
            }
		}

		public init() {}
	}

	/// Return the local thread shared formatter for date components
	private static func sharedFormatter() -> DateComponentsFormatter {
		let name = "SwiftDate_\(NSStringFromClass(DateComponentsFormatter.self))"
		return threadSharedObject(key: name, create: {
			let formatter = DateComponentsFormatter()
			formatter.includesApproximationPhrase = false
			formatter.includesTimeRemainingPhrase = false
			return formatter
		})
	}

	//@available(*, deprecated: 5.0.13, obsoleted: 5.1, message: "Use toIntervalString function instead")
	func toString(options callback: ((inout ComponentsFormatterOptions) -> Void)? = nil) -> String {
		return self.toIntervalString(options: callback)
	}

	/// Format a time interval in a string with desidered components with passed style.
	///
	/// - Parameters:
	///   - units: units to include in string.
	///   - style: style of the units, by default is `.abbreviated`
	/// - Returns: string representation
	func toIntervalString(options callback: ((inout ComponentsFormatterOptions) -> Void)? = nil) -> String {
		let formatter = DateComponentsFormatter()
		var options = ComponentsFormatterOptions()
		callback?(&options)
		options.apply(toFormatter: formatter)

        let formattedValue = (formatter.string(from: self) ?? "")
        if options.zeroFormattingBehavior?.contains(.pad) ?? false {
            // for some strange reason padding is not added at the very beginning positional item.
            // we'll add it manually if necessaru
            if let index = formattedValue.firstIndex(of: ":"), index.utf16Offset(in: formattedValue) < 2 {
                return "0\(formattedValue)"
            }
        }
        return formattedValue
	}

	/// Format a time interval in a string with desidered components with passed style.
	///
	/// - Parameter options: options for formatting.
	/// - Returns: string representation
	func toString(options: ComponentsFormatterOptions) -> String {
		let formatter = TimeInterval.sharedFormatter()
		options.apply(toFormatter: formatter)
		return (formatter.string(from: self) ?? "")
	}

	/// Return a string representation of the time interval in form of clock countdown (ie. 57:00:00)
	///
	/// - Parameter zero: behaviour with zero.
	/// - Returns: string representation
	func toClock(zero: DateComponentsFormatter.ZeroFormattingBehavior =  [.pad, .dropLeading]) -> String {
		return toIntervalString(options: {
            $0.collapsesLargestUnit = true
            $0.maximumUnitCount = 0
			$0.unitsStyle = .positional
            $0.locale = Locales.englishUnitedStatesComputer
			$0.zeroFormattingBehavior = zero
		})
	}

	/// Extract requeste time units components from given interval.
	/// Reference date's calendar is used to make the extraction.
	///
	/// NOTE:
	///		Extraction is calendar/date based; if you specify a `refDate` calculation is made
	/// 	between the `refDate` and `refDate + interval`.
	/// 	If `refDate` is `nil` evaluation is made from `now()` and `now() + interval` in the context
	/// 	of the `SwiftDate.defaultRegion` set.
	///
	/// - Parameters:
	///   - units: units to extract
	///   - from: starting reference date, `nil` means `now()` in the context of the default region set.
	/// - Returns: dictionary with extracted components
	func toUnits(_ units: Set<Calendar.Component>, to refDate: DateInRegion? = nil) -> [Calendar.Component: Int] {
		let dateTo = (refDate ?? DateInRegion())
		let dateFrom = dateTo.addingTimeInterval(-self)
		let components = dateFrom.calendar.dateComponents(units, from: dateFrom.date, to: dateTo.date)
		return components.toDict()
	}

	/// Express a time interval (expressed in seconds) in another time unit you choose.
	/// Reference date's calendar is used to make the extraction.
	///
	/// - parameter component: time unit in which you want to express the calendar component
	/// - parameter from: starting reference date, `nil` means `now()` in the context of the default region set.
	///
	/// - returns: the value of interval expressed in selected `Calendar.Component`
	func toUnit(_ component: Calendar.Component, to refDate: DateInRegion? = nil) -> Int? {
		return toUnits([component], to: refDate)[component]
	}

}
