 //  Copyright © 2015 Guillaume Lessard. All rights reserved.
 //
 
+import Dispatch
+
 // combine two or more Deferred objects into one.
 
 /// Combine an array of `Deferred`s into a new `Deferred` whose value is an array.

/// If the reducing function throws an error, the resulting `Deferred` will contain that error.
/// The combined `Deferred` will use the qos from the first element of the input array (unless the input array is empty.)
///
/// - parameter deferreds: an array of `Deferred`
/// - parameter combine: a reducing function
/// - returns: a new `Deferred`

public func reduce<T, U>(_ deferreds: [Deferred<T>], initial: U, combine: @escaping (U,T) throws -> U) -> Deferred<U>
{
  guard let first = deferreds.first else { return Deferred(value: initial) }

  let queue = DispatchQueue(label: "reduce-collection", qos: first.qos)
  let accumulator = Deferred(queue: queue, result: Result.value(initial))

  let reduced = deferreds.reduce(accumulator) {

/// - parameter deferreds: an array of `Deferred`
/// - returns: a new `Deferred`

public func combine<Value>(qos: DispatchQoS = DispatchQoS.current ?? .default,
                           _ deferreds: [Deferred<Value>]) -> Deferred<[Value]>
{
  var combined = [Value]()
  combined.reserveCapacity(deferreds.count)

  let reduced = reduce(qos: qos, deferreds, initial: (), combine: { _, value in combined.append(value) })
  return reduced.map { _ in combined }
}

