//
//  Future.swift
//  Example
//
//  Created by Ali Ammar Hilal on 22.04.2021.
//

import Foundation

/// A future represents a result of a task which may be available now, or
/// in the future, or never. Once the future receives the result, its state can
/// never be changed, either a value or an error is broadcasted to all observers.
///
/// To attach a callback to the `Future` use `on` method:
///
/// ```
/// let user: Future<User, Error>
///
/// user.on(success: { print("received entity: \($0)" },
///         failure: { print("failed with error: \($0)" })
///
/// // As an alternative observe a completion:
/// user.on(completion: { print("completed with result: \($0)" })
/// ```
///
/// Futures are easily composable. `Future<Value, Error>` provides a set of
/// functions like `map`, `flatMap`, `zip`, `reduce` and more to compose futures.
struct Future<Value, Error: Swift.Error> {
	typealias Result = Swift.Result<Value, Error>

	private enum Resolver {
		// A future is resolved by a promise. Promise is a class, it has locks,
		// array of handlers, etc.
		case promise(Promise)

		// A future is already resolved with a result. There are no locks, no
		// classes - no allocations needed when creating a future like that.
		case result(Result)
	}

	private let resolver: Resolver
	private let scheduler: ScheduleWork?

	// MARK: Initializers
	/// Creates a new, pending future.
	///
	/// - parameter closure: The closure is called immediately on the current
	/// thread. You should start an asynchronous task and call either `succeed`
	/// or `fail` when it completes.
	init(_ closure: (_ promise: Promise) -> Void) {
		let promise = Promise()
		self.init(resolver: .promise(promise))
		closure(promise)
	}

	private init(resolver: Resolver, scheduler: ScheduleWork? = nil) {
		self.resolver = resolver
		self.scheduler = scheduler
	}

	/// Creates a future with a given value.
	init(value: Value) {
		self.init(result: .success(value))
	}

	/// Creates a future with a given error.
	init(error: Error) {
		self.init(result: .failure(error))
	}

	/// Creates a future with a given result.
	init(result: Result) {
		self.init(resolver: .result(result))
	}

	// MARK: Adding Callbacks
	/// Returns a new future which dispatches the callbacks on the given scheduler.
	/// This includes both `on` method and the composition functions like `map`.
	func observe(on queue: DispatchQueue) -> Future {
		Future(resolver: resolver, scheduler: Scheduler.async(on: queue))
	}

	/// Returns a new future which dispatches the callbacks on the given scheduler.
	/// This includes both `on` method and the composition functions like `map`.
	func observe(on scheduler: @escaping ScheduleWork) -> Future {
		Future(resolver: resolver, scheduler: scheduler)
	}

	/// Attach callbacks to the future. If the future already has a result,
	/// callbacks are executed immediatelly. If the future doesn't have a result
	/// yet, callbacks will be executed when the future is resolved.
	///
	/// By default, the callbacks are run on `Scheduler.main` which runs immediately
	/// if on the main thread, otherwise asynchronously on the main thread.
	///
	/// - parameters:
	///   - success: Gets called when the future is resolved successfully.
	///   - failure: Gets called when the future is resolved with an error.
	///   - completion: Gets called when the future is resolved.
	func on(success: ((Value) -> Void)? = nil, failure: ((Error) -> Void)? = nil, completion: (() -> Void)? = nil) {
		let scheduler = self.scheduler ?? Scheduler.default
		_cascade { result in
			scheduler {
				switch result {
				case let .success(value): success?(value)
				case let .failure(error): failure?(error)
				}
				completion?()
			}
		}
	}

	/// Attaches a callback that gets called when the future gets resolved
	/// successfully. See `func on(success:failure:completion:)` for more info.
	func on(success: @escaping (Value) -> Void) {
		// Disambiguates so that `on` with a trailing closure selects a
		// `success` closure, not a `completion`.
		on(success: success, failure: nil, completion: nil)
	}

	func cascade(completion: @escaping (Result) -> Void) {
		if let scheduler = self.scheduler {
			_cascade { result in
				scheduler { completion(result) }
			}
		} else {
			_cascade(completion: completion)
		}
	}

	private func _cascade(completion: @escaping (Result) -> Void) {
		switch resolver {
		case let .promise(promise):
			promise.observe(completion: completion)
		case let .result(result):
			completion(result)
		}
	}

	/// A convenience method which is used for implementing cascades of futures.
	///
	/// It calls `cascade(completion:)` directly but technically it could be
	/// implemented in terms of methods without much of a performance hit:
	///
	///     future.observe(on: Scheduler.immediate).on(completion: { ... })
	///
	func cascade(success: @escaping (Value) -> Void, failure: @escaping (Error) -> Void) {
		cascade { result in
			switch result {
			case let .success(value): success(value)
			case let .failure(error): failure(error)
			}
		}
	}

	// MARK: Synchronous Inspection
	/// Returns the value if the future has a value.
	var value: Value? {
		guard case let .success(value) = result else { return nil }
		return value
	}

	/// Returns the error if the future has an error.
	var error: Error? {
		guard case let .failure(error) = result else { return nil }
		return error
	}

	/// Returns the result if the future completed.
	var result: Result? {
		switch resolver {
		case let .promise(promise):
			return promise.result
		case let .result(result):
			return result
		}
	}
}

extension Future where Error == Never {
	/// A special variant that doesn't require a `failure` closure -
	/// `Future<Value, Never>` can't produce an error.
	func cascade(success: @escaping (Value) -> Void) {
		cascade(success: success, failure: { _ in })
	}
}

// MARK: - Disambiguate Init
extension Future where Error == Never {
	/// Creates a future with the given value and automatically assigns `Error`
	/// to be `Never`.
	init(value: Value) {
		self.init(result: .success(value))
	}
}

// MARK: - Promise
extension Future {

	/// A promise to provide a result later.
	final class Promise: CustomDebugStringConvertible {
		private var memoizedResult: Result? // nil when pending
		private var inlinedHandler: ((Result) -> Void)?
		private var handlers: [(Result) -> Void]?
		private var stateHandler: ((BaseViewController.State) -> Void)?
		private let lock = NSLock()

		/// Creates a new pending promise.
		init() { }

		/// Returns a future associated with the promise.
		var future: Future {
			Future(resolver: .promise(self))
		}

		/// Sends a value to the associated future.
		func succeed(value: Value) {
			resolve(result: .success(value))
		}

		/// Sends an error to the associated future.
		func fail(error: Error) {
			resolve(result: .failure(error))
		}

		/// Sends a result to the associated future.
		func resolve(result: Result) {
			lock.lock()
			guard self.memoizedResult == nil else {
				return lock.unlock() // Already resolved
			}
			self.memoizedResult = result
			let inlinedHandler = self.inlinedHandler
			let handlers = self.handlers
			let stateHandler = self.stateHandler
			self.inlinedHandler = nil
			self.stateHandler = nil
			self.handlers = nil
			lock.unlock()

			inlinedHandler?(result)
			handlers?.forEach { $0(result) }
			switch result {
			case .success: stateHandler?(.showingData)
			case .failure(let error): stateHandler?(.error(error))
			}
		}

		func observe(completion: @escaping (Result) -> Void) {
			lock.lock()
			guard let result = memoizedResult else {
				if inlinedHandler == nil {
					inlinedHandler = completion
				} else {
					// Create handlers lazily - in some cases they are no needed
					handlers = handlers ?? []
					handlers?.append(completion)
				}
				return lock.unlock() // Still pending, handlers attached
			}
			lock.unlock()

			completion(result)
		}

		/// Returns the result if the future completed.
		var result: Result? {
			lock.lock()
			defer { lock.unlock() }
			return memoizedResult
		}

		// MARK: CustomDebugStringConvertible
		var debugDescription: String {
			lock.lock()
			defer { lock.unlock() }
			if let result = memoizedResult {
				return "Promise<\(Value.self), \(Error.self)> { .resolved(result: \(result)) }"
			} else {
				let handlerCount = (handlers?.count ?? 0) + (inlinedHandler != nil ? 1 : 0)
				return "Promise<\(Value.self), \(Error.self)> { .pending(handlers: \(handlerCount)) }"
			}
		}
	}
}

/// A convenience typealias to make constructing promises easier.
typealias Promise<Value, Error: Swift.Error> = Future<Value, Error>.Promise

// MARK: - Scheduler
typealias ScheduleWork = (_ work: @escaping () -> Void) -> Void

enum Scheduler {
	/// `Scheduler.main` by default. Change the scheduler to change the default
	/// behavior where callbacks attached via `on` method are always called on
	/// the main thread.
	static var `default` = Scheduler.main

	/// If the task finishes on the main thread, the callbacks are executed
	/// immediately. Otherwise, they are dispatched to be executed
	/// asynchronously on the main thread.
	static let main: ScheduleWork = { work in
		Thread.isMainThread ? work() : DispatchQueue.main.async(execute: work)
	}

	/// Immediately executes the given closure.
	static let immediate: ScheduleWork = { work in
		work()
	}

	/// Runs asynchronously on the given queue.
	static func async(on queue: DispatchQueue, flags: DispatchWorkItemFlags = []) -> ScheduleWork {
		return { work in
			queue.async(flags: flags, execute: work)
		}
	}
}

// MARK: - Catching Init
extension Future where Error == Swift.Error {
	/// Creates a future by evaluating the given throwing closure, capturing the
	/// returned value as a success, or any thrown error as a failure.
	init(catching body: () throws -> Value) {
		self.init(result: Result(catching: body))
	}
}

extension Future {

	/// Returns a future with the result of mapping the given closure over the
	/// current future's value.
	func map<NewValue>(_ transform: @escaping (Value) -> NewValue) -> Future<NewValue, Error> {
		// Technically the same as `flatMap { Future<NewValue, Error>(value: transform($0) }`
		// but this implementation is optimized for performance.
		let promise = Future<NewValue, Error>.Promise()
		cascade(success: { promise.succeed(value: transform($0)) }, failure: promise.fail)
		return promise.future
	}

	// Allow:
	// Future<T, E>.flatMap { Future<U, E> }
	// Future<T, E>.flatMap { Future<U, Never> }
	/// Returns a future which is eventually resolved with the result of the
	/// future returned by the `transform` closure. The `transform` closure is
	/// called when the current future receives a value.
	///
	/// Allows you to "chain" multiple async operations:
	///
	/// ```
	/// let avatar = user
	///     .map { $0.avatarURL }
	///     .flatMap(loadAvatar)
	///
	/// // user: Future<User, Error>
	/// // func loadAvatar(url: URL) -> Future<Avatar, Error>
	/// ```
	func flatMap<NewValue>(_ transform: @escaping (Value) -> Future<NewValue, Error>) -> Future<NewValue, Error> {
		let promise = Future<NewValue, Error>.Promise()
		cascade(success: { transform($0).cascade(completion: promise.resolve) }, failure: promise.fail)
		return promise.future
	}

    func ifFailsFlatMap<NewValue>(_ transform: @escaping (Any) -> Future<NewValue, Error>) -> Future<NewValue, Error> {
        let promise = Future<NewValue, Error>.Promise()
       // cascade(success: { $0 }, failure: { $0 } )
        cascade(success: { $0 }, failure: { transform($0).cascade(success: { $0 }, failure: { $0 }) })
        return promise.future
    }
    
	func flatMap<NewValue>(_ transform: @escaping (Value) -> Future<NewValue, Never>) -> Future<NewValue, Error> {
		// Technically the same as `flatMap { transform($0).castError() }`, but
		// we're doing it from scratch to avoid additional allocations from `castError`
		let promise = Future<NewValue, Error>.Promise()
		cascade(success: { transform($0).cascade(success: promise.succeed) }, failure: promise.fail)
		return promise.future
	}
}

extension Future where Error == Never {

	// Allow:
	// Future<T, Never>.flatMap { Future<U, E> }
	// Future<T, Never>.flatMap { Future<U, Never> } // disambiguate
	func flatMap<NewValue, NewError>(_ transform: @escaping (Value) -> Future<NewValue, NewError>) -> Future<NewValue, NewError> {
		let promise = Future<NewValue, NewError>.Promise()
		cascade(success: { transform($0).cascade(completion: promise.resolve) })
		return promise.future
	}

	func flatMap<NewValue>(_ transform: @escaping (Value) -> Future<NewValue, Never>) -> Future<NewValue, Never> {
		let promise = Future<NewValue, Never>.Promise()
		cascade(success: { transform($0).cascade(success: promise.succeed) })
		return promise.future
	}
}

// MARK: - MapError, FlatMapError
extension Future {

	/// Returns a future with the error which is the result of mapping the given
	/// closure over the current future's error.
	func mapError<NewError>(_ transform: @escaping (Error) -> NewError) -> Future<Value, NewError> {
		let promise = Future<Value, NewError>.Promise()
		cascade(success: promise.succeed, failure: { promise.fail(error: transform($0)) })
		return promise.future
	}

	/// Returns a future which is eventually resolved with the result of the
	/// future returned by the `transform` closure. The `transform` closure is
	/// called when the current future receives an error.
	///
	/// Allows you to continue the chain of futures by "recovering" from an error
	/// with a new future.
	func flatMapError<NewError>(_ transform: @escaping (Error) -> Future<Value, NewError>) -> Future<Value, NewError> {
		let promise = Future<Value, NewError>.Promise()
		cascade(success: promise.succeed, failure: { transform($0).cascade(completion: promise.resolve) })
		return promise.future
	}
}

// MARK: - Zip
extension Future where Value == Any, Error == Swift.Error {

	/// Returns a future which succeedes when all the given futures succeed. If
	/// any of the futures fail, the returned future also fails with that error.
	static func zip<V1, V2, E>(_ f1: Future<V1, E>, _ f2: Future<V2, E>) -> Future<(V1, V2), E> {
		let promise = Future<(V1, V2), E>.Promise()
		func success(value: Any) {
			guard let v1 = f1.value, let v2 = f2.value else { return }
			promise.succeed(value: (v1, v2))
		}
		f1.cascade(success: success, failure: promise.fail)
		f2.cascade(success: success, failure: promise.fail)
		return promise.future
	}

	/// Returns a future which succeedes when all the given futures succeed. If
	/// any of the futures fail, the returned future also fails with that error.
	// swiftlint:disable large_tuple
	static func zip<V1, V2, V3, E>(_ f1: Future<V1, E>, _ f2: Future<V2, E>, _ f3: Future<V3, E>) -> Future<(V1, V2, V3), E> {
		return Future.zip(f1, Future.zip(f2, f3)).map { ($0.0, $0.1.0, $0.1.1) }
	}

	/// Returns a future which succeedes when all the given futures succeed. If
	/// any of the futures fail, the returned future also fails with that error.
	static func zip<V, E>(_ futures: [Future<V, E>]) -> Future<[V], E> {
		return Future.reduce([V](), futures) { $0 + [$1] }
	}
}

// MARK: - Reduce
extension Future where Value == Any, Error == Swift.Error {

	/// Returns a future that succeeded when all the given futures succeed.
	/// The future contains the result of combining the `initialResult` with
	/// the values of all the given future. If any of the futures fail, the
	/// returned future also fails with that error.
	static func reduce<V1, V2, E>(_ initialResult: V1, _ futures: [Future<V2, E>], _ combiningFunction: @escaping (V1, V2) -> V1) -> Future<V1, E> {
		return futures.reduce(Future<V1, E>(value: initialResult)) { lhs, rhs in
			return Future.zip(lhs, rhs).map(combiningFunction)
		}
	}
}

// MARK: - Reduce
// extension Future {
//	static func trackStatus(_ completion: ((BaseViewController.State) -> Void)) -> Future<Value, Error> {
//		Promise().future
//	}
// }
// swiftlint:enable large_tuple
