//
//  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

#if os(Linux)

#else
internal enum AssociatedKeys: String {
	case customDateFormatter = "SwiftDate.CustomDateFormatter"
}
#endif

extension Date: DateRepresentable {

	/// Just return itself to be compliant with `DateRepresentable` protocol.
	public var date: Date { return self }

	/// For absolute Date object the default region is obtained from the global `defaultRegion` variable.
	public var region: Region {
		return SwiftDate.defaultRegion
	}

	#if os(Linux)
	public var customFormatter: DateFormatter? {
		get {
			debugPrint("Not supported on Linux")
			return nil
		}
		set { debugPrint("Not supported on Linux") }
	}
	#else
	/// Assign a custom formatter if you need a special behaviour during formatting of the object.
	/// Usually you will not need to do it, SwiftDate uses the local thread date formatter in order to
	/// optimize the formatting process. By default is `nil`.
	public var customFormatter: DateFormatter? {
		get {
			let formatter: DateFormatter? = getAssociatedValue(key: AssociatedKeys.customDateFormatter.rawValue, object: self as AnyObject)
			return formatter
		}
		set {
			set(associatedValue: newValue, key: AssociatedKeys.customDateFormatter.rawValue, object: self as AnyObject)
		}
	}
	#endif

	/// Extract the date components.
	public var dateComponents: DateComponents {
		return region.calendar.dateComponents(DateComponents.allComponentsSet, from: self)
	}

	/// Initialize a new date object from string expressed in given region.
	///
	/// - Parameters:
	///   - string: date expressed as string.
	///   - format: format of the date (`nil` uses provided list of auto formats patterns.
	///				Pass it if you can in order to optimize the parse task).
	///   - region: region in which the date is expressed. `nil` uses the `SwiftDate.defaultRegion`.
	public init?(_ string: String, format: String? = nil, region: Region = SwiftDate.defaultRegion) {
		guard let dateInRegion = DateInRegion(string, format: format, region: region) else { return nil }
		self = dateInRegion.date
	}

	/// Initialize a new date from the number of seconds passed since Unix Epoch.
	///
	/// - Parameter interval: seconds

	/// Initialize a new date from the number of seconds passed since Unix Epoch.
	///
	/// - Parameters:
	///   - interval: seconds from Unix epoch time.
	///   - region: region in which the date, `nil` uses the default region at UTC timezone
	public init(seconds interval: TimeInterval, region: Region = Region.UTC) {
		self = DateInRegion(seconds: interval, region: region).date
	}

	/// Initialize a new date corresponding to the number of milliseconds since the Unix Epoch.
	///
	/// - Parameters:
	///   - interval: seconds since the Unix Epoch timestamp.
	///   - region: region in which the date must be expressed, `nil` uses the default region at UTC timezone
	public init(milliseconds interval: Int, region: Region = Region.UTC) {
		self = DateInRegion(milliseconds: interval, region: region).date
	}

	/// Initialize a new date with the opportunity to configure single date components via builder pattern.
	/// Date is therfore expressed in passed region (`DateComponents`'s `timezone`,`calendar` and `locale` are ignored
	/// and overwritten by the region if not `nil`).
	///
	/// - Parameters:
	///   - configuration: configuration callback
	///   - region: region in which the date is expressed. Ignore to use `SwiftDate.defaultRegion`, `nil` to use `DateComponents` data.
	public init?(components configuration: ((inout DateComponents) -> Void), region: Region? = SwiftDate.defaultRegion) {
		guard let date = DateInRegion(components: configuration, region: region)?.date else { return nil }
		self = date
	}

	/// Initialize a new date with given components.
	///
	/// - Parameters:
	///   - components: components of the date.
	///   - region: region in which the date is expressed.
	///				Ignore to use `SwiftDate.defaultRegion`, `nil` to use `DateComponents` data.
	public init?(components: DateComponents, region: Region?) {
		guard let date = DateInRegion(components: components, region: region)?.date else { return nil }
		self = date
	}

	/// Initialize a new date with given components.
	public init(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0, region: Region = SwiftDate.defaultRegion) {
		var components = DateComponents()
		components.year = year
		components.month = month
		components.day = day
		components.hour = hour
		components.minute = minute
		components.second = second
		components.nanosecond = nanosecond
		components.timeZone = region.timeZone
		components.calendar = region.calendar
		self = region.calendar.date(from: components)!
	}

	/// Express given absolute date in the context of the default region.
	///
	/// - Returns: `DateInRegion`
	public func inDefaultRegion() -> DateInRegion {
		return DateInRegion(self, region: SwiftDate.defaultRegion)
	}

	/// Express given absolute date in the context of passed region.
	///
	/// - Parameter region: destination region.
	/// - Returns: `DateInRegion`
	public func `in`(region: Region) -> DateInRegion {
		return DateInRegion(self, region: region)
	}

	/// Return a date in the distant past.
	///
	/// - Returns: Date instance.
	public static func past() -> Date {
		return Date.distantPast
	}

	/// Return a date in the distant future.
	///
	/// - Returns: Date instance.
	public static func future() -> Date {
		return Date.distantFuture
	}

}
