// Copyright 2018-present, Pinterest, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

extension Array {
    func parallelFlatMap<T>(transform: ((Element) -> [T])) -> [T] {
        return parallelMap(transform).flatMap { $0 }
    }

    /// We have to roll our own solution because concurrentPerform will use slowPath if no NSApplication is available
    func parallelMap<T>(_ transform: ((Element) -> T), progress: ((Int) -> Void)? = nil) -> [T] {
        let count = self.count
        let maxConcurrentJobs = ProcessInfo.processInfo.activeProcessorCount

        guard count > 1 && maxConcurrentJobs > 1 else {
            // skip GCD overhead if we'd only run one at a time anyway
            return map(transform)
        }

        var result = [(Int, [T])]()
        result.reserveCapacity(count)
        let group = DispatchGroup()
        let uuid = NSUUID().uuidString
        let jobCount = Int(ceil(Double(count) / Double(maxConcurrentJobs)))

        let queueLabelPrefix = "com.pinterest.xchammer.map.\(uuid)"
        let resultAccumulatorQueue = DispatchQueue(label: "\(queueLabelPrefix).resultAccumulator")

        withoutActuallyEscaping(transform) { escapingtransform in
            for jobIndex in stride(from: 0, to: count, by: jobCount) {
                let queue = DispatchQueue(label: "\(queueLabelPrefix).\(jobIndex / jobCount)")
                queue.async(group: group) {
                    let jobElements = self[jobIndex..<Swift.min(count, jobIndex + jobCount)]
                    let jobIndexAndResults = (jobIndex, jobElements.map(escapingtransform))
                    resultAccumulatorQueue.sync {
                        result.append(jobIndexAndResults)
                    }
                }
            }
            group.wait()
        }
        return result.sorted { $0.0 < $1.0 }.flatMap { $0.1 }
    }
}


