// Foundation/NSURLSession/NSURLSessionTask.swift - NSURLSession API
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
// -----------------------------------------------------------------------------
///
/// URLSession API code.
/// - SeeAlso: NSURLSession.swift
///
// -----------------------------------------------------------------------------




import CoreFoundation
import Dispatch


/// A cancelable object that refers to the lifetime
/// of processing a given request.
open class URLSessionTask : NSObject, NSCopying {
    /// How many times the task has been suspended, 0 indicating a running task.
    fileprivate var suspendCount = 1
    fileprivate var easyHandle: _EasyHandle!
    fileprivate var totalDownloaded = 0
    fileprivate var session: URLSessionProtocol! //change to nil when task completes
    fileprivate let body: _Body
    fileprivate let tempFileURL: URL
    
    /// The internal state that the task is in.
    ///
    /// Setting this value will also add / remove the easy handle.
    /// It is independt of the `state: URLSessionTask.State`. The
    /// `internalState` tracks the state of transfers / waiting for callbacks.
    /// The `state` tracks the overall state of the task (running vs.
    /// completed).
    /// - SeeAlso: URLSessionTask._InternalState
    fileprivate var internalState = _InternalState.initial {
        // We manage adding / removing the easy handle and pausing / unpausing
        // here at a centralized place to make sure the internal state always
        // matches up with the state of the easy handle being added and paused.
        willSet {
            if !internalState.isEasyHandlePaused && newValue.isEasyHandlePaused {
                fatalError("Need to solve pausing receive.")
            }
            if internalState.isEasyHandleAddedToMultiHandle && !newValue.isEasyHandleAddedToMultiHandle {
                session.remove(handle: easyHandle)
            }
        }
        didSet {    
            if !oldValue.isEasyHandleAddedToMultiHandle && internalState.isEasyHandleAddedToMultiHandle {
                session.add(handle: easyHandle)
            }
            if oldValue.isEasyHandlePaused && !internalState.isEasyHandlePaused {
                fatalError("Need to solve pausing receive.")
            }
            if case .taskCompleted = internalState {
                updateTaskState()
                guard let s = session as? URLSession else { fatalError() }
                s.workQueue.async {
                    s.taskRegistry.remove(self)
                }
            }
        }
    }
    /// All operations must run on this queue.
    fileprivate let workQueue: DispatchQueue 
    /// This queue is used to make public attributes thread safe. It's a
    /// **concurrent** queue and must be used with a barries when writing. This
    /// allows multiple concurrent readers or a single writer.
    fileprivate let taskAttributesIsolation: DispatchQueue 
    
    public override init() {
        // Darwin Foundation oddly allows calling this initializer, even though
        // such a task is quite broken -- it doesn't have a session. And calling
        // e.g. `taskIdentifier` will crash.
        //
        // We set up the bare minimum for init to work, but don't care too much
        // about things crashing later.
        session = _MissingURLSession()
        taskIdentifier = 0
        originalRequest = nil
        body = .none
        workQueue = DispatchQueue(label: "URLSessionTask.notused.0")
        taskAttributesIsolation = DispatchQueue(label: "URLSessionTask.notused.1")
        let fileName = NSTemporaryDirectory() + NSUUID().uuidString + ".tmp"
        _ = FileManager.default.createFile(atPath: fileName, contents: nil)
        self.tempFileURL = URL(fileURLWithPath: fileName)
        super.init()
    }
    /// Create a data task. If there is a httpBody in the URLRequest, use that as a parameter
    internal convenience init(session: URLSession, request: URLRequest, taskIdentifier: Int) {
        if let bodyData = request.httpBody {
            self.init(session: session, request: request, taskIdentifier: taskIdentifier, body: _Body.data(createDispatchData(bodyData)))
        } else {
            self.init(session: session, request: request, taskIdentifier: taskIdentifier, body: .none)
        }
    }
    internal init(session: URLSession, request: URLRequest, taskIdentifier: Int, body: _Body) {
        self.session = session
        self.workQueue = session.workQueue
        self.taskAttributesIsolation = session.taskAttributesIsolation
        self.taskIdentifier = taskIdentifier
        self.originalRequest = request
        self.body = body
        let fileName = NSTemporaryDirectory() + NSUUID().uuidString + ".tmp"
        _ = FileManager.default.createFile(atPath: fileName, contents: nil)
        self.tempFileURL = URL(fileURLWithPath: fileName)
        super.init()
        self.easyHandle = _EasyHandle(delegate: self)
    }
    deinit {
        //TODO: Can we ensure this somewhere else? This might run on the wrong
        // thread / queue.
        //if internalState.isEasyHandleAddedToMultiHandle {
        //    session.removeHandle(easyHandle)
        //}
    }
    
    open override func copy() -> Any {
        return copy(with: nil)
    }
    
    open func copy(with zone: NSZone?) -> Any {
        NSUnimplemented()
    }
    
    /// An identifier for this task, assigned by and unique to the owning session
    open let taskIdentifier: Int
    
    /// May be nil if this is a stream task
    /*@NSCopying*/ open let originalRequest: URLRequest?
    
    /// May differ from originalRequest due to http server redirection
    /*@NSCopying*/ open fileprivate(set) var currentRequest: URLRequest? {
        get {
            var r: URLRequest? = nil
            taskAttributesIsolation.sync { r = self._currentRequest }
            return r
        }
        //TODO: dispatch_barrier_async
        set { taskAttributesIsolation.async { self._currentRequest = newValue } }
    }
    fileprivate var _currentRequest: URLRequest? = nil
    /*@NSCopying*/ open fileprivate(set) var response: URLResponse? {
        get {
            var r: URLResponse? = nil
            taskAttributesIsolation.sync { r = self._response }
            return r
        }
        set { taskAttributesIsolation.async { self._response = newValue } }
    }
    fileprivate var _response: URLResponse? = nil
    
    /* Byte count properties may be zero if no body is expected,
     * or URLSessionTransferSizeUnknown if it is not possible
     * to know how many bytes will be transferred.
     */
    
    /// Number of body bytes already received
   open fileprivate(set) var countOfBytesReceived: Int64 {
        get {
            var r: Int64 = 0
            taskAttributesIsolation.sync { r = self._countOfBytesReceived }
            return r
        }
        set { taskAttributesIsolation.async { self._countOfBytesReceived = newValue } }
    }
    fileprivate var _countOfBytesReceived: Int64 = 0
    
    /// Number of body bytes already sent */
    open fileprivate(set) var countOfBytesSent: Int64 {
        get {
            var r: Int64 = 0
            taskAttributesIsolation.sync { r = self._countOfBytesSent }
            return r
        }
        set { taskAttributesIsolation.async { self._countOfBytesSent = newValue } }
    }

    fileprivate var _countOfBytesSent: Int64 = 0
    
    /// Number of body bytes we expect to send, derived from the Content-Length of the HTTP request */
    open fileprivate(set) var countOfBytesExpectedToSend: Int64 = 0
    
    /// Number of byte bytes we expect to receive, usually derived from the Content-Length header of an HTTP response. */
    open fileprivate(set) var countOfBytesExpectedToReceive: Int64 = 0
    
    /// The taskDescription property is available for the developer to
    /// provide a descriptive label for the task.
    open var taskDescription: String?
    
    /* -cancel returns immediately, but marks a task as being canceled.
     * The task will signal -URLSession:task:didCompleteWithError: with an
     * error value of { NSURLErrorDomain, NSURLErrorCancelled }.  In some
     * cases, the task may signal other work before it acknowledges the
     * cancelation.  -cancel may be sent to a task that has been suspended.
     */
    open func cancel() { NSUnimplemented() }
    
    /*
     * The current state of the task within the session.
     */
    open var state: URLSessionTask.State {
        get {
            var r: URLSessionTask.State = .suspended
            taskAttributesIsolation.sync { r = self._state }
            return r
        }
        set { taskAttributesIsolation.async { self._state = newValue } }
    }
    fileprivate var _state: URLSessionTask.State = .suspended
    
    /*
     * The error, if any, delivered via -URLSession:task:didCompleteWithError:
     * This property will be nil in the event that no error occured.
     */
    /*@NSCopying*/ open var error: NSError? { NSUnimplemented() }
    
    /// Suspend the task.
    ///
    /// Suspending a task will prevent the URLSession from continuing to
    /// load data.  There may still be delegate calls made on behalf of
    /// this task (for instance, to report data received while suspending)
    /// but no further transmissions will be made on behalf of the task
    /// until -resume is sent.  The timeout timer associated with the task
    /// will be disabled while a task is suspended. -suspend and -resume are
    /// nestable.
    open func suspend() {
        // suspend / resume is implemented simply by adding / removing the task's
        // easy handle fromt he session's multi-handle.
        //
        // This might result in slightly different behaviour than the Darwin Foundation
        // implementation, but it'll be difficult to get complete parity anyhow.
        // Too many things depend on timeout on the wire etc.
        //
        // TODO: It may be worth looking into starting over a task that gets
        // resumed. The Darwin Foundation documentation states that that's what
        // it does for anything but download tasks.
        
        // We perform the increment and call to `updateTaskState()`
        // synchronous, to make sure the `state` is updated when this method
        // returns, but the actual suspend will be done asynchronous to avoid
        // dead-locks.
        workQueue.sync {
            self.suspendCount += 1
            guard self.suspendCount < Int.max else { fatalError("Task suspended too many times \(Int.max).") }
            self.updateTaskState()
            
            if self.suspendCount == 1 {
                self.workQueue.async {
                    self.performSuspend()
                }
            }
        }
    }
    /// Resume the task.
    ///
    /// - SeeAlso: `suspend()`
    open func resume() {
        workQueue.sync {
            self.suspendCount -= 1
            guard 0 <= self.suspendCount else { fatalError("Resuming a task that's not suspended. Calls to resume() / suspend() need to be matched.") }
            self.updateTaskState()
            if self.suspendCount == 0 {
                self.workQueue.async {
                    self.performResume()
                }
            }
        }
    }
    
    /// The priority of the task.
    ///
    /// Sets a scaling factor for the priority of the task. The scaling factor is a
    /// value between 0.0 and 1.0 (inclusive), where 0.0 is considered the lowest
    /// priority and 1.0 is considered the highest.
    ///
    /// The priority is a hint and not a hard requirement of task performance. The
    /// priority of a task may be changed using this API at any time, but not all
    /// protocols support this; in these cases, the last priority that took effect
    /// will be used.
    ///
    /// If no priority is specified, the task will operate with the default priority
    /// as defined by the constant URLSessionTaskPriorityDefault. Two additional
    /// priority levels are provided: URLSessionTaskPriorityLow and
    /// URLSessionTaskPriorityHigh, but use is not restricted to these.
    open var priority: Float {
        get {
            var r: Float = 0
            taskAttributesIsolation.sync { r = self._priority }
            return r
        }
        set {
            taskAttributesIsolation.async { self._priority = newValue }
        }
    }
    fileprivate var _priority: Float = URLSessionTaskPriorityDefault
}

extension URLSessionTask {
    public enum State : Int {
        /// The task is currently being serviced by the session
        case running
        case suspended
        /// The task has been told to cancel.  The session will receive a URLSession:task:didCompleteWithError: message.
        case canceling
        /// The task has completed and the session will receive no more delegate notifications
        case completed
    }
}

fileprivate extension URLSessionTask {
    /// The calls to `suspend` can be nested. This one is only called when the
    /// task is not suspended and needs to go into suspended state.
    func performSuspend() {
        if case .transferInProgress(let transferState) = internalState {
            internalState = .transferReady(transferState)
        }
    }
    /// The calls to `resume` can be nested. This one is only called when the
    /// task is suspended and needs to go out of suspended state.
    func performResume() {
        if case .initial = internalState {
            guard let r = originalRequest else { fatalError("Task has no original request.") }
            startNewTransfer(with: r)
        }
        if case .transferReady(let transferState) = internalState {
            internalState = .transferInProgress(transferState)
        }
    }
}

internal extension URLSessionTask {
    /// The is independent of the public `state: URLSessionTask.State`.
    enum _InternalState {
        /// Task has been created, but nothing has been done, yet
        case initial
        /// The easy handle has been fully configured. But it is not added to
        /// the multi handle.
        case transferReady(_TransferState)
        /// The easy handle is currently added to the multi handle
        case transferInProgress(_TransferState)
        /// The transfer completed.
        ///
        /// The easy handle has been removed from the multi handle. This does
        /// not (necessarily mean the task completed. A task that gets
        /// redirected will do multiple transfers.
        case transferCompleted(response: HTTPURLResponse, bodyDataDrain: _TransferState._DataDrain)
        /// The transfer failed.
        ///
        /// Same as `.transferCompleted`, but without response / body data
        case transferFailed
        /// Waiting for the completion handler of the HTTP redirect callback.
        ///
        /// When we tell the delegate that we're about to perform an HTTP
        /// redirect, we need to wait for the delegate to let us know what
        /// action to take.
        case waitingForRedirectCompletionHandler(response: HTTPURLResponse, bodyDataDrain: _TransferState._DataDrain)
        /// Waiting for the completion handler of the 'did receive response' callback.
        ///
        /// When we tell the delegate that we received a response (i.e. when
        /// we received a complete header), we need to wait for the delegate to
        /// let us know what action to take. In this state the easy handle is
        /// paused in order to suspend delegate callbacks.
        case waitingForResponseCompletionHandler(_TransferState)
        /// The task is completed
        ///
        /// Contrast this with `.transferCompleted`.
        case taskCompleted
    }
}

fileprivate extension URLSessionTask._InternalState {
    var isEasyHandleAddedToMultiHandle: Bool {
        switch self {
        case .initial:                             return false
        case .transferReady:                       return false
        case .transferInProgress:                  return true
        case .transferCompleted:                   return false
        case .transferFailed:                      return false
        case .waitingForRedirectCompletionHandler: return false
        case .waitingForResponseCompletionHandler: return true
        case .taskCompleted:                       return false
        }
    }
    var isEasyHandlePaused: Bool {
        switch self {
        case .initial:                             return false
        case .transferReady:                       return false
        case .transferInProgress:                  return false
        case .transferCompleted:                   return false
        case .transferFailed:                      return false
        case .waitingForRedirectCompletionHandler: return false
        case .waitingForResponseCompletionHandler: return true
        case .taskCompleted:                       return false
        }
    }
}

internal extension URLSessionTask {
    /// Updates the (public) state based on private / internal state.
    ///
    /// - Note: This must be called on the `workQueue`.
    fileprivate func updateTaskState() {
        func calculateState() -> URLSessionTask.State {
            if case .taskCompleted = internalState {
                return .completed
            }
            if suspendCount == 0 {
                return .running
            } else {
                return .suspended
            }
        }
        state = calculateState()
    }
}

internal extension URLSessionTask {
    enum _Body {
        case none
        case data(DispatchData)
        /// Body data is read from the given file URL
        case file(URL)
        case stream(InputStream)
    }
}
fileprivate extension URLSessionTask._Body {
    enum _Error : Error {
        case fileForBodyDataNotFound
    }
    /// - Returns: The body length, or `nil` for no body (e.g. `GET` request).
    func getBodyLength() throws -> UInt64? {
        switch self {
        case .none:
            return 0
        case .data(let d):
            return UInt64(d.count)
        /// Body data is read from the given file URL
        case .file(let fileURL):
            guard let s = try FileManager.default.attributesOfItem(atPath: fileURL.path)[.size] as? NSNumber else {
                throw _Error.fileForBodyDataNotFound
            }
            return s.uint64Value
        case .stream:
            return nil
        }
    }
}

/// Easy handle related
fileprivate extension URLSessionTask {
    /// Start a new transfer
    func startNewTransfer(with request: URLRequest) {
        currentRequest = request
        guard let url = request.url else { fatalError("No URL in request.") }
        internalState = .transferReady(createTransferState(url: url))
        configureEasyHandle(for: request)
        if suspendCount < 1 {
            performResume()
        }
    }
    /// Creates a new transfer state with the given behaviour:
    func createTransferState(url: URL) -> URLSessionTask._TransferState {
        let drain = createTransferBodyDataDrain()
        switch body {
        case .none:
            return URLSessionTask._TransferState(url: url, bodyDataDrain: drain)
        case .data(let data):
            let source = _HTTPBodyDataSource(data: data)
            return URLSessionTask._TransferState(url: url, bodyDataDrain: drain, bodySource: source)
        case .file(let fileURL):
            let source = _HTTPBodyFileSource(fileURL: fileURL, workQueue: workQueue, dataAvailableHandler: { [weak self] in
                // Unpause the easy handle
                self?.easyHandle.unpauseSend()
                })
            return URLSessionTask._TransferState(url: url, bodyDataDrain: drain, bodySource: source)
        case .stream:
            NSUnimplemented()
        }
        
    }
    /// The data drain.
    ///
    /// This depends on what the delegate / completion handler need.
    fileprivate func createTransferBodyDataDrain() -> URLSessionTask._TransferState._DataDrain {
        switch session.behaviour(for: self) {
        case .noDelegate:
            return .ignore
        case .taskDelegate:
            // Data will be forwarded to the delegate as we receive it, we don't
            // need to do anything about it.
            return .ignore
        case .dataCompletionHandler:
            // Data needs to be concatenated in-memory such that we can pass it
            // to the completion handler upon completion.
            return .inMemory(nil)
        case .downloadCompletionHandler:
            // Data needs to be written to a file (i.e. a download task).
            let fileHandle = try! FileHandle(forWritingTo: tempFileURL)
            return .toFile(tempFileURL, fileHandle) 
        }
    }
    /// Set options on the easy handle to match the given request.
    ///
    /// This performs a series of `curl_easy_setopt()` calls.
    fileprivate func configureEasyHandle(for request: URLRequest) {
        // At this point we will call the equivalent of curl_easy_setopt()
        // to configure everything on the handle. Since we might be re-using
        // a handle, we must be sure to set everything and not rely on defaul
        // values.
        
        //TODO: We could add a strong reference from the easy handle back to
        // its URLSessionTask by means of CURLOPT_PRIVATE -- that would ensure
        // that the task is always around while the handle is running.
        // We would have to break that retain cycle once the handle completes
        // its transfer.
        
        // Behavior Options
        easyHandle.set(verboseModeOn: enableLibcurlDebugOutput)
        easyHandle.set(debugOutputOn: enableLibcurlDebugOutput, task: self)
        easyHandle.set(passHeadersToDataStream: false)
        easyHandle.set(progressMeterOff: true)
        easyHandle.set(skipAllSignalHandling: true)
        
        // Error Options:
        easyHandle.set(errorBuffer: nil)
        easyHandle.set(failOnHTTPErrorCode: false)
        
        // Network Options:
        guard let url = request.url else { fatalError("No URL in request.") }
        easyHandle.set(url: url)
        easyHandle.setAllowedProtocolsToHTTPAndHTTPS()
        easyHandle.set(preferredReceiveBufferSize: Int.max)
        do {
            switch (body, try body.getBodyLength()) {
            case (.none, _):
                set(requestBodyLength: .noBody)
            case (_, .some(let length)):
                set(requestBodyLength: .length(length))
            case (_, .none):
                set(requestBodyLength: .unknown)
            }
        } catch let e {
            // Fail the request here.
            // TODO: We have multiple options:
            //     NSURLErrorNoPermissionsToReadFile
            //     NSURLErrorFileDoesNotExist
            internalState = .transferFailed
            failWith(errorCode: errorCode(fileSystemError: e), request: request)
            return
        }
        
        // HTTP Options:
        easyHandle.set(followLocation: false)
        easyHandle.set(customHeaders: curlHeaders(for: request))

        //Options unavailable on Ubuntu 14.04 (libcurl 7.36)
        //TODO: Introduce something like an #if
        //easyHandle.set(waitForPipeliningAndMultiplexing: true)
        //easyHandle.set(streamWeight: priority)

        //set the request timeout
        //TODO: the timeout value needs to be reset on every data transfer
        let s = session as! URLSession
        easyHandle.set(timeout: Int(s.configuration.timeoutIntervalForRequest))

        easyHandle.set(automaticBodyDecompression: true)
        easyHandle.set(requestMethod: request.httpMethod ?? "GET")
        if request.httpMethod == "HEAD" {
            easyHandle.set(noBody: true)
        } else if ((request.httpMethod == "POST") && (request.value(forHTTPHeaderField: "Content-Type") == nil)) {
            easyHandle.set(customHeaders: ["Content-Type:application/x-www-form-urlencoded"])
        }
    }
}

fileprivate extension URLSessionTask {
    /// These are a list of headers that should be passed to libcurl.
    ///
    /// Headers will be returned as `Accept: text/html` strings for
    /// setting fields, `Accept:` for disabling the libcurl default header, or
    /// `Accept;` for a header with no content. This is the format that libcurl
    /// expects.
    ///
    /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
    func curlHeaders(for request: URLRequest) -> [String] {
        var result: [String] = []
        var names = Set<String>()
        if let hh = currentRequest?.allHTTPHeaderFields {
            hh.forEach {
                let name = $0.0.lowercased()
                guard !names.contains(name) else { return }
                names.insert(name)
                
                if $0.1.isEmpty {
                    result.append($0.0 + ";")
                } else {
                    result.append($0.0 + ": " + $0.1)
                }
            }
        }
        curlHeadersToSet.forEach {
            let name = $0.0.lowercased()
            guard !names.contains(name) else { return }
            names.insert(name)
            
            if $0.1.isEmpty {
                result.append($0.0 + ";")
            } else {
                result.append($0.0 + ": " + $0.1)
            }
        }
        curlHeadersToRemove.forEach {
            let name = $0.lowercased()
            guard !names.contains(name) else { return }
            names.insert(name)
            result.append($0 + ":")
        }
        return result
    }
    /// Any header values that should be passed to libcurl
    ///
    /// These will only be set if not already part of the request.
    /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
    var curlHeadersToSet: [(String,String)] {
        var result = [("Connection", "keep-alive"),
                      ("User-Agent", userAgentString),
                      ]
        if let language = NSLocale.current.languageCode {
            result.append(("Accept-Language", language))
        }
        return result
    }
    /// Any header values that should be removed from the ones set by libcurl
    /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
    var curlHeadersToRemove: [String] {
        if case .none = body {
            return []
        } else {
            return ["Expect"]
        }
    }
}

fileprivate var userAgentString: String = {
    // Darwin uses something like this: "xctest (unknown version) CFNetwork/760.4.2 Darwin/15.4.0 (x86_64)"
    let info = ProcessInfo.processInfo
    let name = info.processName
    let curlVersion = CFURLSessionCurlVersionInfo()
    //TODO: Should probably use sysctl(3) to get these:
    // kern.ostype: Darwin
    // kern.osrelease: 15.4.0
    //TODO: Use NSBundle to get the version number?
    return "\(name) (unknown version) curl/\(curlVersion.major).\(curlVersion.minor).\(curlVersion.patch)"
}()

fileprivate func errorCode(fileSystemError error: Error) -> Int {
    func fromCocoaErrorCode(_ code: Int) -> Int {
        switch code {
        case CocoaError.fileReadNoSuchFile.rawValue:
            return NSURLErrorFileDoesNotExist
        case CocoaError.fileReadNoPermission.rawValue:
            return NSURLErrorNoPermissionsToReadFile
        default:
            return NSURLErrorUnknown
        }
    }
    switch error {
    case let e as NSError where e.domain == NSCocoaErrorDomain:
        return fromCocoaErrorCode(e.code)
    default:
        return NSURLErrorUnknown
    }
}

fileprivate extension URLSessionTask {
    /// Set request body length.
    ///
    /// An unknown length
    func set(requestBodyLength length: URLSessionTask._RequestBodyLength) {
        switch length {
        case .noBody:
            easyHandle.set(upload: false)
            easyHandle.set(requestBodyLength: 0)
        case .length(let length):
            easyHandle.set(upload: true)
            easyHandle.set(requestBodyLength: Int64(length))
        case .unknown:
            easyHandle.set(upload: true)
            easyHandle.set(requestBodyLength: -1)
        }
    }
    enum _RequestBodyLength {
        case noBody
        ///
        case length(UInt64)
        /// Will result in a chunked upload
        case unknown
    }
}

extension URLSessionTask: _EasyHandleDelegate {
    func didReceive(data: Data) -> _EasyHandle._Action {
        guard case .transferInProgress(let ts) = internalState else { fatalError("Received body data, but no transfer in progress.") }
        guard ts.isHeaderComplete else { fatalError("Received body data, but the header is not complete, yet.") }
        notifyDelegate(aboutReceivedData: data)
        internalState = .transferInProgress(ts.byAppending(bodyData: data))
        return .proceed
    }

    fileprivate func notifyDelegate(aboutReceivedData data: Data) {
        if case .taskDelegate(let delegate) = session.behaviour(for: self),
            let dataDelegate = delegate as? URLSessionDataDelegate,
            let task = self as? URLSessionDataTask {
            // Forward to the delegate:
            guard let s = session as? URLSession else { fatalError() }
            s.delegateQueue.addOperation {
                dataDelegate.urlSession(s, dataTask: task, didReceive: data)
            }
        } else if case .taskDelegate(let delegate) = session.behaviour(for: self),
            let downloadDelegate = delegate as? URLSessionDownloadDelegate,
            let task = self as? URLSessionDownloadTask {
                guard let s = session as? URLSession else { fatalError() }
                let fileHandle = try! FileHandle(forWritingTo: tempFileURL)
                _ = fileHandle.seekToEndOfFile()
                fileHandle.write(data)
                self.totalDownloaded += data.count
            
                s.delegateQueue.addOperation {
                    downloadDelegate.urlSession(s, downloadTask: task, didWriteData: Int64(data.count), totalBytesWritten: Int64(self.totalDownloaded),
                        totalBytesExpectedToWrite: Int64(self.easyHandle.fileLength))
                }
                if Int(self.easyHandle.fileLength) == totalDownloaded {
                    fileHandle.closeFile()
                    s.delegateQueue.addOperation {
                        downloadDelegate.urlSession(s, downloadTask: task, didFinishDownloadingTo: self.tempFileURL)
                    }
                }
            
        }
    }

    func didReceive(headerData data: Data) -> _EasyHandle._Action {
        guard case .transferInProgress(let ts) = internalState else { fatalError("Received body data, but no transfer in progress.") }
        do {
            let newTS = try ts.byAppending(headerLine: data)
            internalState = .transferInProgress(newTS)
            let didCompleteHeader = !ts.isHeaderComplete && newTS.isHeaderComplete
            if didCompleteHeader {
                // The header is now complete, but wasn't before.
                didReceiveResponse()
            }
            return .proceed
        } catch {
            return .abort
        }
    }

    func fill(writeBuffer buffer: UnsafeMutableBufferPointer<Int8>) -> _EasyHandle._WriteBufferResult {
        guard case .transferInProgress(let ts) = internalState else { fatalError("Requested to fill write buffer, but transfer isn't in progress.") }
        guard let source = ts.requestBodySource else { fatalError("Requested to fill write buffer, but transfer state has no body source.") }
        switch source.getNextChunk(withLength: buffer.count) {
        case .data(let data):
            copyDispatchData(data, infoBuffer: buffer)
            let count = data.count
            assert(count > 0)
            return .bytes(count)
        case .done:
            return .bytes(0)
        case .retryLater:
            // At this point we'll try to pause the easy handle. The body source
            // is responsible for un-pausing the handle once data becomes
            // available.
            return .pause
        case .error:
            return .abort
        }
    }

    func transferCompleted(withErrorCode errorCode: Int?) {
        // At this point the transfer is complete and we can decide what to do.
        // If everything went well, we will simply forward the resulting data
        // to the delegate. But in case of redirects etc. we might send another
        // request.
        guard case .transferInProgress(let ts) = internalState else { fatalError("Transfer completed, but it wasn't in progress.") }
        guard let request = currentRequest else { fatalError("Transfer completed, but there's no currect request.") }
        guard errorCode == nil else {
            internalState = .transferFailed
            failWith(errorCode: errorCode!, request: request)
            return
        }
        
        guard let response = ts.response else { fatalError("Transfer completed, but there's no response.") }
        internalState = .transferCompleted(response: response, bodyDataDrain: ts.bodyDataDrain)
        
        let action = completionAction(forCompletedRequest: request, response: response)
        switch action {
        case .completeTask:
            completeTask()
        case .failWithError(let errorCode):
            internalState = .transferFailed
            failWith(errorCode: errorCode, request: request)
        case .redirectWithRequest(let newRequest):
            redirectFor(request: newRequest)
        }
    }
    func seekInputStream(to position: UInt64) throws {
        // We will reset the body sourse and seek forward.
        NSUnimplemented()
    }
    func updateProgressMeter(with propgress: _EasyHandle._Progress) {
        //TODO: Update progress. Note that a single URLSessionTask might
        // perform multiple transfers. The values in `progress` are only for
        // the current transfer.
    }
}

/// State Transfers
extension URLSessionTask {
    func completeTask() {
        guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = internalState else {
            fatalError("Trying to complete the task, but its transfer isn't complete.")
        }
        self.response = response

        //because we deregister the task with the session on internalState being set to taskCompleted
        //we need to do the latter after the delegate/handler was notified/invoked
        switch session.behaviour(for: self) {
        case .taskDelegate(let delegate):
            guard let s = session as? URLSession else { fatalError() }
            s.delegateQueue.addOperation {
                delegate.urlSession(s, task: self, didCompleteWithError: nil)
                self.internalState = .taskCompleted
            }
        case .noDelegate:
            internalState = .taskCompleted
        case .dataCompletionHandler(let completion):
            guard case .inMemory(let bodyData) = bodyDataDrain else {
                fatalError("Task has data completion handler, but data drain is not in-memory.")
            }

            guard let s = session as? URLSession else { fatalError() }

            var data = Data()
            if let body = bodyData {
                data = Data(bytes: body.bytes, count: body.length)
            }

            s.delegateQueue.addOperation {
                completion(data, response, nil)
                self.internalState = .taskCompleted
                self.session = nil
            }
        case .downloadCompletionHandler(let completion):
            guard case .toFile(let url, let fileHandle?) = bodyDataDrain else {
                fatalError("Task has data completion handler, but data drain is not a file handle.")
            }

            guard let s = session as? URLSession else { fatalError() }
            //The contents are already written, just close the file handle and call the handler
            fileHandle.closeFile()
            
            s.delegateQueue.addOperation {
                completion(url, response, nil) 
                self.internalState = .taskCompleted
                self.session = nil
            }
            
        }
    }
    func completeTask(withError error: NSError) {
        guard case .transferFailed = internalState else {
            fatalError("Trying to complete the task, but its transfer isn't complete / failed.")
        }
        switch session.behaviour(for: self) {
        case .taskDelegate(let delegate):
            guard let s = session as? URLSession else { fatalError() }
            s.delegateQueue.addOperation {
                delegate.urlSession(s, task: self, didCompleteWithError: error as Error)
                self.internalState = .taskCompleted
            }
        case .noDelegate:
            internalState = .taskCompleted
        case .dataCompletionHandler(let completion):
            guard let s = session as? URLSession else { fatalError() }
            s.delegateQueue.addOperation {
                completion(nil, nil, error)
                self.internalState = .taskCompleted
            }
        case .downloadCompletionHandler(let completion): 
            guard let s = session as? URLSession else { fatalError() }
            s.delegateQueue.addOperation {
                completion(nil, nil, error)
                self.internalState = .taskCompleted
            }
        }
    }
    func failWith(errorCode: Int, request: URLRequest) {
        //TODO: Error handling
        let userInfo: [String : Any]? = request.url.map {
            [
                NSURLErrorFailingURLErrorKey: $0,
                NSURLErrorFailingURLStringErrorKey: $0.absoluteString,
                ]
        }
        let error = NSError(domain: NSURLErrorDomain, code: errorCode, userInfo: userInfo)
        completeTask(withError: error)
    }
    func redirectFor(request: URLRequest) {
        //TODO: Should keep track of the number of redirects that this
        // request has gone through and err out once it's too large, i.e.
        // call into `failWith(errorCode: )` with NSURLErrorHTTPTooManyRedirects
        guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = internalState else {
            fatalError("Trying to redirect, but the transfer is not complete.")
        }
        
        switch session.behaviour(for: self) {
        case .taskDelegate(let delegate):
            // At this point we need to change the internal state to note
            // that we're waiting for the delegate to call the completion
            // handler. Then we'll call the delegate callback
            // (willPerformHTTPRedirection). The task will then switch out of
            // its internal state once the delegate calls the completion
            // handler.
            
            //TODO: Should the `public response: URLResponse` property be updated
            // before we call delegate API
            // `func urlSession(session: session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest?) -> Void)`
            // ?
            
            internalState = .waitingForRedirectCompletionHandler(response: response, bodyDataDrain: bodyDataDrain)
            // We need this ugly cast in order to be able to support `URLSessionTask.init()`
            guard let s = session as? URLSession else { fatalError() }
            s.delegateQueue.addOperation {
                delegate.urlSession(s, task: self, willPerformHTTPRedirection: response, newRequest: request) { [weak self] (request: URLRequest?) in
                    guard let task = self else { return }
                    task.workQueue.async {
                        task.didCompleteRedirectCallback(request)
                    }
                }
            }
        case .noDelegate, .dataCompletionHandler, .downloadCompletionHandler:
            // Follow the redirect.
            startNewTransfer(with: request)
        }
    }
    fileprivate func didCompleteRedirectCallback(_ request: URLRequest?) {
        guard case .waitingForRedirectCompletionHandler(response: let response, bodyDataDrain: let bodyDataDrain) = internalState else {
            fatalError("Received callback for HTTP redirection, but we're not waiting for it. Was it called multiple times?")
        }
        // If the request is `nil`, we're supposed to treat the current response
        // as the final response, i.e. not do any redirection.
        // Otherwise, we'll start a new transfer with the passed in request.
        if let r = request {
            startNewTransfer(with: r)
        } else {
            internalState = .transferCompleted(response: response, bodyDataDrain: bodyDataDrain)
            completeTask()
        }
    }
}


/// Response processing
fileprivate extension URLSessionTask {
    /// Whenever we receive a response (i.e. a complete header) from libcurl,
    /// this method gets called.
    func didReceiveResponse() {
        guard let dt = self as? URLSessionDataTask else { return }
        guard case .transferInProgress(let ts) = internalState else { fatalError("Transfer not in progress.") }
        guard let response = ts.response else { fatalError("Header complete, but not URL response.") }
        switch session.behaviour(for: self) {
        case .noDelegate:
            break
        case .taskDelegate(let delegate as URLSessionDataDelegate):
            //TODO: There's a problem with libcurl / with how we're using it.
            // We're currently unable to pause the transfer / the easy handle:
            // https://curl.haxx.se/mail/lib-2016-03/0222.html
            //
            // For now, we'll notify the delegate, but won't pause the transfer,
            // and we'll disregard the completion handler:
            guard let s = session as? URLSession else { fatalError() }
            s.delegateQueue.addOperation {
                delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { _ in
                    print("warning: Ignoring dispotion from completion handler.")
                })
            }
        case .taskDelegate:
            break
        case .dataCompletionHandler:
            break
        case .downloadCompletionHandler:
            break
        }
    }
    /// Give the delegate a chance to tell us how to proceed once we have a
    /// response / complete header.
    ///
    /// This will pause the transfer.
    func askDelegateHowToProceedAfterCompleteResponse(_ response: HTTPURLResponse, delegate: URLSessionDataDelegate) {
        // Ask the delegate how to proceed.
        
        // This will pause the easy handle. We need to wait for the
        // delegate before processing any more data.
        guard case .transferInProgress(let ts) = internalState else { fatalError("Transfer not in progress.") }
        internalState = .waitingForResponseCompletionHandler(ts)
        
        let dt = self as! URLSessionDataTask
        
        // We need this ugly cast in order to be able to support `URLSessionTask.init()`
        guard let s = session as? URLSession else { fatalError() }
        s.delegateQueue.addOperation {
            delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { [weak self] disposition in
                guard let task = self else { return }
                task.workQueue.async {
                    task.didCompleteResponseCallback(disposition: disposition)
                }
                })
        }
    }
    /// This gets called (indirectly) when the data task delegates lets us know
    /// how we should proceed after receiving a response (i.e. complete header).
    func didCompleteResponseCallback(disposition: URLSession.ResponseDisposition) {
        guard case .waitingForResponseCompletionHandler(let ts) = internalState else { fatalError("Received response disposition, but we're not waiting for it.") }
        switch disposition {
        case .cancel:
            //TODO: Fail the task with NSURLErrorCancelled
            NSUnimplemented()
        case .allow:
            // Continue the transfer. This will unpause the easy handle.
            internalState = .transferInProgress(ts)
        case .becomeDownload:
            /* Turn this request into a download */
            NSUnimplemented()
        case .becomeStream:
            /* Turn this task into a stream task */
            NSUnimplemented()
        }
    }
    
    /// Action to be taken after a transfer completes
    enum _CompletionAction {
        case completeTask
        case failWithError(Int)
        case redirectWithRequest(URLRequest)
    }
    
    /// What action to take
    func completionAction(forCompletedRequest request: URLRequest, response: HTTPURLResponse) -> _CompletionAction {
        // Redirect:
        if let request = redirectRequest(for: response, fromRequest: request) {
            return .redirectWithRequest(request)
        }
        return .completeTask
    }
    /// If the response is a redirect, return the new request
    ///
    /// RFC 7231 section 6.4 defines redirection behavior for HTTP/1.1
    ///
    /// - SeeAlso: <https://tools.ietf.org/html/rfc7231#section-6.4>
    func redirectRequest(for response: HTTPURLResponse, fromRequest: URLRequest) -> URLRequest? {
        //TODO: Do we ever want to redirect for HEAD requests?
        func methodAndURL() -> (String, URL)? {
            guard
                let location = response.value(forHeaderField: .location),
                let targetURL = URL(string: location)
                else {
                    // Can't redirect when there's no location to redirect to.
                    return nil
            }
            
            // Check for a redirect:
            switch response.statusCode {
            //TODO: Should we do this for 300 "Multiple Choices", too?
            case 301, 302, 303:
                // Change into "GET":
                return ("GET", targetURL)
            case 307:
                // Re-use existing method:
                return (fromRequest.httpMethod ?? "GET", targetURL)
            default:
                return nil
            }
        }
        guard let (method, targetURL) = methodAndURL() else { return nil }
        var request = fromRequest
        request.httpMethod = method
        request.url = targetURL
        return request
    }
}


fileprivate extension HTTPURLResponse {
    /// Type safe HTTP header field name(s)
    enum _Field: String {
        /// `Location`
        /// - SeeAlso: RFC 2616 section 14.30 <https://tools.ietf.org/html/rfc2616#section-14.30>
        case location = "Location"
    }
    func value(forHeaderField field: _Field) -> String? {
        return field.rawValue
    }
}

public let URLSessionTaskPriorityDefault: Float = 0.5
public let URLSessionTaskPriorityLow: Float = 0.25
public let URLSessionTaskPriorityHigh: Float = 0.75

/*
 * An URLSessionDataTask does not provide any additional
 * functionality over an URLSessionTask and its presence is merely
 * to provide lexical differentiation from download and upload tasks.
 */
open class URLSessionDataTask : URLSessionTask {
}

/*
 * An URLSessionUploadTask does not currently provide any additional
 * functionality over an URLSessionDataTask.  All delegate messages
 * that may be sent referencing an URLSessionDataTask equally apply
 * to URLSessionUploadTasks.
 */
open class URLSessionUploadTask : URLSessionDataTask {
}

/*
 * URLSessionDownloadTask is a task that represents a download to
 * local storage.
 */
open class URLSessionDownloadTask : URLSessionTask {
    
    internal var fileLength = -1.0
    
    /* Cancel the download (and calls the superclass -cancel).  If
     * conditions will allow for resuming the download in the future, the
     * callback will be called with an opaque data blob, which may be used
     * with -downloadTaskWithResumeData: to attempt to resume the download.
     * If resume data cannot be created, the completion handler will be
     * called with nil resumeData.
     */
    open func cancel(byProducingResumeData completionHandler: (NSData?) -> Void) { NSUnimplemented() }
}

/*
 * An URLSessionStreamTask provides an interface to perform reads
 * and writes to a TCP/IP stream created via URLSession.  This task
 * may be explicitly created from an URLSession, or created as a
 * result of the appropriate disposition response to a
 * -URLSession:dataTask:didReceiveResponse: delegate message.
 *
 * URLSessionStreamTask can be used to perform asynchronous reads
 * and writes.  Reads and writes are enquened and executed serially,
 * with the completion handler being invoked on the sessions delegate
 * queuee.  If an error occurs, or the task is canceled, all
 * outstanding read and write calls will have their completion
 * handlers invoked with an appropriate error.
 *
 * It is also possible to create InputStream and OutputStream
 * instances from an URLSessionTask by sending
 * -captureStreams to the task.  All outstanding read and writess are
 * completed before the streams are created.  Once the streams are
 * delivered to the session delegate, the task is considered complete
 * and will receive no more messsages.  These streams are
 * disassociated from the underlying session.
 */

open class URLSessionStreamTask : URLSessionTask {
    
    /* Read minBytes, or at most maxBytes bytes and invoke the completion
     * handler on the sessions delegate queue with the data or an error.
     * If an error occurs, any outstanding reads will also fail, and new
     * read requests will error out immediately.
     */
    open func readData(ofMinLength minBytes: Int, maxLength maxBytes: Int, timeout: TimeInterval, completionHandler: (NSData?, Bool, NSError?) -> Void) { NSUnimplemented() }
    
    /* Write the data completely to the underlying socket.  If all the
     * bytes have not been written by the timeout, a timeout error will
     * occur.  Note that invocation of the completion handler does not
     * guarantee that the remote side has received all the bytes, only
     * that they have been written to the kernel. */
    open func write(data: NSData, timeout: TimeInterval, completionHandler: (NSError?) -> Void) { NSUnimplemented() }
    
    /* -captureStreams completes any already enqueued reads
     * and writes, and then invokes the
     * URLSession:streamTask:didBecomeInputStream:outputStream: delegate
     * message. When that message is received, the task object is
     * considered completed and will not receive any more delegate
     * messages. */
    open func captureStreams() { NSUnimplemented() }
    
    /* Enqueue a request to close the write end of the underlying socket.
     * All outstanding IO will complete before the write side of the
     * socket is closed.  The server, however, may continue to write bytes
     * back to the client, so best practice is to continue reading from
     * the server until you receive EOF.
     */
    open func closeWrite() { NSUnimplemented() }
    
    /* Enqueue a request to close the read side of the underlying socket.
     * All outstanding IO will complete before the read side is closed.
     * You may continue writing to the server.
     */
    open func closeRead() { NSUnimplemented() }
    
    /*
     * Begin encrypted handshake.  The hanshake begins after all pending
     * IO has completed.  TLS authentication callbacks are sent to the
     * session's -URLSession:task:didReceiveChallenge:completionHandler:
     */
    open func startSecureConnection() { NSUnimplemented() }
    
    /*
     * Cleanly close a secure connection after all pending secure IO has
     * completed.
     */
    open func stopSecureConnection() { NSUnimplemented() }
}

/* Key in the userInfo dictionary of an NSError received during a failed download. */
public let URLSessionDownloadTaskResumeData: String = "" // NSUnimplemented


extension URLSession {
    static func printDebug(_ text: @autoclosure () -> String) {
        guard enableDebugOutput else { return }
        debugPrint(text())
    }
}

fileprivate let enableLibcurlDebugOutput: Bool = {
    return (ProcessInfo.processInfo.environment["URLSessionDebugLibcurl"] != nil)
}()
fileprivate let enableDebugOutput: Bool = {
    return (ProcessInfo.processInfo.environment["URLSessionDebug"] != nil)
}()
