//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Dispatch
import Foundation
import _SwiftNetworkOverlayShims

/// An NWConnection is an object that represents a bi-directional data pipe between
/// a local endpoint and a remote endpoint.
///
/// A connection handles establishment of any transport, security, or application-level protocols
/// required to transmit and receive user data. Multiple protocol instances may be attempted during
/// the establishment phase of the connection.
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
public final class NWConnection : CustomDebugStringConvertible {

	public var debugDescription: String {
		return String("\(self.nw)")
	}

	/// States a connection may be in
	public enum State : Equatable {
		/// The initial state prior to start
		case setup
		/// Waiting connections have not yet been started, or do not have a viable network
		case waiting(NWError)
		/// Preparing connections are actively establishing the connection
		case preparing
		/// Ready connections can send and receive data
		case ready
		/// Failed connections are disconnected and can no longer send or receive data
		case failed(NWError)
		/// Cancelled connections have been invalidated by the client and will send no more events
		case cancelled

		internal init(_ nw: nw_connection_state_t, _ err: nw_error_t?) {
			switch nw {
			case Network.nw_connection_state_invalid:
				self = .setup
			case Network.nw_connection_state_waiting:
				if let error = NWError(err) {
					self = .waiting(error)
				} else {
					self = .waiting(NWError.posix(.ENETDOWN))
				}
			case Network.nw_connection_state_preparing:
				self = .preparing
			case Network.nw_connection_state_ready:
				self = .ready
			case Network.nw_connection_state_failed:
				if let error = NWError(err) {
					self = .failed(error)
				} else {
					self = .failed(NWError.posix(.EINVAL))
				}
			case Network.nw_connection_state_cancelled:
				self = .cancelled
			default:
				self = .cancelled
			}
		}
	}

	internal let nw : nw_connection_t

	private var _state = NWConnection.State.setup

	/// Access the current state of the connection
	public var state: NWConnection.State {
		return _state
	}

	private var _stateUpdateHandler: ((_ state: NWConnection.State) -> Void)?

	/// Set a block to be called when the connection's state changes, which may be called
	/// multiple times until the connection is cancelled.
	public var stateUpdateHandler: ((_ state: NWConnection.State) -> Void)? {
		set {
			self._stateUpdateHandler = newValue
			if let newValue = newValue {
				nw_connection_set_state_changed_handler(self.nw) { (state, error) in
					self._state = NWConnection.State(state, error)
					newValue(self._state)
				}
			} else {
				nw_connection_set_state_changed_handler(self.nw) { (state, error) in
					self._state = NWConnection.State(state, error)
				}
			}
		}
		get {
			return self._stateUpdateHandler
		}
	}

	private var _currentPath: NWPath? = nil

	/// Current path for the connection, which can be used to extract interface and effective endpoint information
	public var currentPath: NWPath? {
		get {
			if let path = nw_connection_copy_current_path(self.nw) {
				return NWPath(path)
			}
			return nil
		}
	}

	private var _pathUpdateHandler: ((_ newPath: NWPath) -> Void)?

	/// Set a block to be called when the connection's path has changed, which may be called
	/// multiple times until the connection is cancelled.
	public var pathUpdateHandler: ((_ newPath: NWPath) -> Void)? {
		set {
			self._pathUpdateHandler = newValue
			if let newValue = newValue {
				nw_connection_set_path_changed_handler(self.nw) { (nwPath) in
					let newPath = NWPath(nwPath)
					self._currentPath = newPath
					newValue(newPath)
				}
			} else {
				nw_connection_set_path_changed_handler(self.nw, nil)
			}
			
		}
		get {
			return self._pathUpdateHandler
		}
	}

	private var _viabilityUpdateHandler: ((_ isViable: Bool) -> Void)?

	/// Set a block to be called when the connection's viability changes, which may be called
	/// multiple times until the connection is cancelled.
	///
	/// Connections that are not currently viable do not have a route, and packets will not be
	/// sent or received. There is a possibility that the connection will become viable
	/// again when network connectivity changes.
	public var viabilityUpdateHandler: ((_ isViable: Bool) -> Void)? {
		set {
			self._viabilityUpdateHandler = newValue
			if let newValue = newValue {
				nw_connection_set_viability_changed_handler(self.nw) { (isViable) in
					newValue(isViable)
				}
			} else {
				nw_connection_set_viability_changed_handler(self.nw, nil)
			}

		}
		get {
			return self._viabilityUpdateHandler
		}
	}

	private var _betterPathUpdateHandler: ((_ betterPathAvailable: Bool) -> Void)?

	/// A better path being available indicates that the system thinks there is a preferred path or
	/// interface to use, compared to the one this connection is actively using. As an example, the
	/// connection is established over an expensive cellular interface and an unmetered Wi-Fi interface
	/// is now available.
	///
	/// Set a block to be called when a better path becomes available or unavailable, which may be called
	/// multiple times until the connection is cancelled.
	///
	/// When a better path is available, if it is possible to migrate work from this connection to a new connection,
	/// create a new connection to the endpoint. Continue doing work on this connection until the new connection is
	/// ready. Once ready, transition work to the new connection and cancel this one.
	public var betterPathUpdateHandler: ((_ betterPathAvailable: Bool) -> Void)? {
		set {
			self._betterPathUpdateHandler = newValue
			if let newValue = newValue {
				nw_connection_set_better_path_available_handler(self.nw) { (betterPathAvailable) in
					newValue(betterPathAvailable)
				}
			} else {
				nw_connection_set_better_path_available_handler(self.nw, nil)
			}

		}
		get {
			return self._betterPathUpdateHandler
		}
	}

	/// The remote endpoint of the connection
	public let endpoint: NWEndpoint

	/// The set of parameters with which the connection was created
	public let parameters: NWParameters

	private init(to: NWEndpoint, using: NWParameters, connection: nw_connection_t) {
		self.endpoint = to
		self.parameters = using
		self.nw = connection
		nw_connection_set_state_changed_handler(self.nw) { (state, error) in
			self._state = NWConnection.State(state, error)
		}
	}

	convenience internal init?(using: NWParameters, inbound: nw_connection_t) {
		guard let peer = NWEndpoint(nw_connection_copy_endpoint(inbound)) else {
			return nil
		}
		self.init(to: peer, using: using, connection: inbound)
	}

	/// Create a new outbound connection to an endpoint, with parameters.
	/// The parameters determine the protocols to be used for the connection, and their options.
	///
	/// - Parameter to: The remote endpoint to which to connect.
	/// - Parameter using: The parameters define which protocols and path to use.
	public init(to: NWEndpoint, using: NWParameters) {
		self.endpoint = to
		self.parameters = using
		self.nw = nw_connection_create(to.nw!, using.nw)
		nw_connection_set_state_changed_handler(self.nw) { (state, error) in
			self._state = NWConnection.State(state, error)
		}
	}

	/// Create a new outbound connection to a hostname and port, with parameters.
	/// The parameters determine the protocols to be used for the connection, and their options.
	///
	/// - Parameter host: The remote hostname to which to connect.
	/// - Parameter port: The remote port to which to connect.
	/// - Parameter using: The parameters define which protocols and path to use.
	public convenience init(host: NWEndpoint.Host, port: NWEndpoint.Port, using: NWParameters) {
		self.init(to: .hostPort(host: host, port: port), using: using)
	}

	/// Start the connection and provide a dispatch queue for callback blocks.
	///
	/// Starts the connection, which will cause the connection to evaluate its path, do resolution and try to become
	/// ready (connected). NWConnection establishment is asynchronous. A stateUpdateHandler may be used to determine
	/// when the state changes. If the connection can not be established, the state will transition to waiting with
	/// an associated error describing the reason. If an unrecoverable error is encountered, the state will
	/// transition to failed with an associated NWError value. If the connection is established, the state will
	/// transition to ready.
 	///
	/// Start should only be called once on a connection, and multiple calls to start will
	/// be ignored.
	public func start(queue: DispatchQueue) {
		self._queue = queue
		nw_connection_set_queue(self.nw, queue)
		nw_connection_start(self.nw)
	}

	private var _queue: DispatchQueue?

	/// Get queue used for delivering block handlers, such as stateUpdateHandler, pathUpdateHandler,
	/// viabilityUpdateHandler, betterPathUpdateHandler, and completions for send and receive.
	/// If the connection has not yet been started, the queue will be nil. Once the connection has been
	/// started, the queue will be valid.
	public var queue: DispatchQueue? {
		get {
			return self._queue
		}
	}

	/// Cancel the connection, and cause all update handlers to be cancelled.
	///
	/// Cancel is asynchronous. The last callback will be to the stateUpdateHandler with the cancelled state. After
	/// the stateUpdateHandlers are called with the cancelled state, all blocks are released to break retain cycles.
    ///
    /// Calls to cancel after the first one are ignored.
	public func cancel() {
		nw_connection_cancel(self.nw)
	}

	/// A variant of cancel that performs non-graceful closure of the transport.
	public func forceCancel() {
		nw_connection_force_cancel(self.nw)
	}

	/// Cancel the currently connected endpoint, causing the connection to fall through to the next endpoint if
	/// available, or to go to the waiting state if no more endpoints are available.
	public func cancelCurrentEndpoint() {
		nw_connection_cancel_current_endpoint(self.nw)
	}

	/// NWConnections will normally re-attempt on network changes. This function causes a connection that is in
	/// the waiting state to re-attempt even without a network change.
	public func restart() {
		nw_connection_restart(self.nw)
	}

	/// Content Context controls options for how data is sent, and access for metadata to send or receive.
	/// All properties of Content Context are valid for sending. For received Content Context, only the protocolMetadata
	/// values will be set.
	public class ContentContext {
        internal let nw: nw_content_context_t

        /// A string description of the content, used for logging and debugging.
        public let identifier: String

		/// An expiration in milliseconds after scheduling a send, after which the content may be dropped.
		/// Defaults to 0, which implies no expiration. Used only when sending.
		public let expirationMilliseconds: UInt64

		/// A numeric value between 0.0 and 1.0 to specify priority of this data/message. Defaults to 0.5.
		/// Used only when sending.
		public let relativePriority: Double

		/// Any content marked as an antecedent must be sent prior to this content being sent. Defaults to nil.
		/// Used only when sending.
		public let antecedent: NWConnection.ContentContext?

		/// A boolean indicating if this context is the final context in a given direction on this connection. Defaults to false.
		public let isFinal: Bool

		/// An array of protocol metadata to send (to inform the protocols of per-data options) or receive (to receive per-data options or statistics).
        public var protocolMetadata: [NWProtocolMetadata] {
            get {
                var metadataArray: [NWProtocolMetadata] = []
                nw_content_context_foreach_protocol_metadata(self.nw) { (definition, metadata) in
                    if nw_protocol_metadata_is_tcp(metadata) {
                       metadataArray.append(NWProtocolTCP.Metadata(metadata))
                    } else if nw_protocol_metadata_is_udp(metadata) {
                        metadataArray.append(NWProtocolUDP.Metadata(metadata))
                    } else if nw_protocol_metadata_is_ip(metadata) {
                        metadataArray.append(NWProtocolIP.Metadata(metadata))
					} else if nw_protocol_metadata_is_tls(metadata) {
						metadataArray.append(NWProtocolTLS.Metadata(metadata))
					}
                }
                return metadataArray
            }
        }

		/// Access the metadata for a specific protocol from a context. The metadata may be nil.
        public func protocolMetadata(definition: NWProtocolDefinition) -> NWProtocolMetadata? {
			if let metadata = nw_content_context_copy_protocol_metadata(self.nw, definition.nw) {
				if nw_protocol_metadata_is_tcp(metadata) {
					return NWProtocolTCP.Metadata(metadata)
				} else if nw_protocol_metadata_is_udp(metadata) {
					return NWProtocolUDP.Metadata(metadata)
				} else if nw_protocol_metadata_is_ip(metadata) {
					return NWProtocolIP.Metadata(metadata)
				} else if nw_protocol_metadata_is_tls(metadata) {
					return NWProtocolTLS.Metadata(metadata)
				}
			}
			return nil
        }

		/// Create a context for sending, that optionally can set expiration (default 0),
		/// priority (default 0.5), antecedent (default nil), and protocol metadata (default []]).
		public init(identifier: String, expiration: UInt64 = 0, priority: Double = 0.5, isFinal: Bool = false, antecedent: NWConnection.ContentContext? = nil, metadata: [NWProtocolMetadata]? = []) {
            self.nw = nw_content_context_create(identifier)

            self.identifier = identifier

			self.expirationMilliseconds = expiration
            nw_content_context_set_expiration_milliseconds(self.nw, expiration)

			self.relativePriority = priority
            nw_content_context_set_relative_priority(self.nw, priority)

			self.isFinal = isFinal
			nw_content_context_set_is_final(self.nw, isFinal)

			self.antecedent = antecedent
            if let otherContext = antecedent {
                nw_content_context_set_antecedent(self.nw, otherContext.nw)
            }

			if let metadataArray = metadata {
				for singleMetadata in metadataArray {
					nw_content_context_set_metadata_for_protocol(self.nw, singleMetadata.nw)
				}
			}
		}

		/// Use the default message context to send content with all default properties:
		/// default priority, no expiration, and not the final message. Marking this context
		/// as complete with a send indicates that the message content is now complete and any
		/// other messages that were blocked may be scheduled, but will not close the underlying
		/// connection. Use this context for any lightweight sends of datagrams or messages on
		/// top of a stream that do not require special properties.
		/// This context does not support overriding any properties.
		public static let defaultMessage : NWConnection.ContentContext = NWConnection.ContentContext(_swift_nw_content_context_default_message())!

		/// Use the final message context to indicate that no more sends are expected
		/// once this context is complete. Like .defaultMessage, all properties are default.
		/// Marking a send as complete when using this context will close the sending side of the
		/// underlying connection. This is the equivalent of sending a FIN on a TCP stream.
		/// This context does not support overriding any properties.
		public static let finalMessage : NWConnection.ContentContext = NWConnection.ContentContext(_swift_nw_content_context_final_message())!

		/// Use the default stream context to indicate that this sending context is
		/// the one that represents the entire connection. All context properties are default.
		/// This context behaves in the same way as .finalMessage, such that marking the
		/// context complete by sending isComplete will close the sending side of the
		/// underlying connection (a FIN for a TCP stream).
		/// Note that this context is a convenience for sending a single, final context.
		/// If the protocol used by the connection is a stream (such as TCP), the caller
		/// may still use .defaultMessage, .finalMessage, or a custom context with priorities
		/// and metadata to set properties of a particular chunk of stream data relative
		/// to other data on the stream.
		/// This context does not support overriding any properties.
		public static let defaultStream : NWConnection.ContentContext = NWConnection.ContentContext(_swift_nw_content_context_default_stream())!

		internal init?(_ nw: nw_content_context_t?) {
			guard let nw = nw else {
				return nil
			}

            self.nw = nw

			// Received content context doesn't have expiration, priority, or antecedents
			self.expirationMilliseconds = 0
			self.relativePriority = 0
			self.antecedent = nil
			self.isFinal = nw_content_context_get_is_final(nw)
            self.identifier = String(cString: nw_content_context_get_identifier(nw))
		}
	}

	/// Receive data from a connection. This may be called before the connection
	/// is ready, in which case the receive request will be queued until the
	/// connection is ready. The completion handler will be invoked exactly
	/// once for each call, so the client must call this function multiple
	/// times to receive multiple chunks of data. For protocols that
	/// support flow control, such as TCP, calling receive opens the receive
	/// window. If the client stops calling receive, the receive window will
	/// fill up and the remote peer will stop sending.
	/// - Parameter minimumIncompleteLength: The minimum length to receive from the connection,
	///   until the content is complete.
	/// - Parameter maximumLength: The maximum length to receive from the connection in a single completion.
	/// - Parameter completion: A receive completion is invoked exactly once for a call to receive(...).
	///   The completion indicates that the requested content has been received (in which case
	///   the content is delivered), or else an error has occurred. Parameters to the completion are:
	///
	///   - content: The received content, as constrained by the minimum and maximum length. This may
	///     be nil if the message or stream is complete (without any more data to deliver), or if
	///     an error was encountered.
	///
	///   - contentContext: Content context describing the received content. This includes protocol metadata
	///     that lets the caller introspect information about the received content (such as flags on a packet).
	///
	///   - isComplete: An indication that this context (a message or stream, for example) is now complete. For
	///     protocols such as TCP, this will be marked when the entire stream has be closed in the
	///     reading direction. For protocols such as UDP, this will be marked when the end of a
	///     datagram has been reached.
	///
	///   - error: An error will be sent if the receive was terminated before completing. There may still
	///     be content delivered along with the error, but this content may be shorter than the requested
	///     ranges. An error will be sent for any outstanding receives when the connection is cancelled.
	public func receive(minimumIncompleteLength: Int, maximumLength: Int,
						completion: @escaping (_ content: Data?,
											   _ contentContext: NWConnection.ContentContext?,
											   _ isComplete: Bool, _ error: NWError?) -> Void) {
		nw_connection_receive(self.nw, UInt32(minimumIncompleteLength), UInt32(maximumLength)) {
			(content, context, complete, nwError) in
			completion(NWCreateNSDataFromDispatchData(content) as Data?, ContentContext(context), complete, NWError(nwError));
		}
	}

	/// Receive complete message content from the connection, waiting for the content to be marked complete
	/// (or encounter an error) before delivering the callback. This is useful for datagram or message-based
	/// protocols like UDP. See receive(minimumIncompleteLength:, maximumLength:, completion:) for a description
	/// of the completion handler.
	public func receiveMessage(completion: @escaping (_ completeContent: Data?,
                                                      _ contentContext: NWConnection.ContentContext?,
                                                      _ isComplete: Bool, _ error: NWError?) -> Void) {
		nw_connection_receive_message(self.nw) { (content, context, complete, nwError) in
			completion(NWCreateNSDataFromDispatchData(content) as Data?, ContentContext(context), complete, NWError(nwError))
		}
	}

	/// A type representing a wrapped completion handler invoked when send content has been consumed by the protocol stack, or the lack of a completion handler because the content is idempotent.
	public enum SendCompletion {
		/// Completion handler to be invoked when send content has been successfully processed, or failed to send due to an error.
		/// Note that this does not guarantee that the data was sent out over the network, or acknowledge, but only that
		/// it has been consumed by the protocol stack.
		case contentProcessed((_ error: NWError?) -> Void)
		/// Idempotent content may be sent multiple times when opening up a 0-RTT connection, so there is no completion block
		case idempotent
	}

	/// Send data on a connection. This may be called before the connection is ready,
	/// in which case the send will be enqueued until the connection is ready to send.
	/// This is an asynchronous send and the completion block can be used to
	/// determine when the send is complete. There is nothing preventing a client
	/// from issuing an excessive number of outstanding sends. To minimize memory
	/// footprint and excessive latency as a consequence of buffer bloat, it is
	/// advisable to keep a low number of outstanding sends. The completion block
	/// can be used to pace subsequent sends.
	/// - Parameter content: The data to send on the connection. May be nil if this send marks its context as complete, such
	///   as by sending .finalMessage as the context and marking isComplete to send a write-close.
	/// - Parameter contentContext: The context associated with the content, which represents a logical message
	///   to be sent on the connection. All content sent within a single context will
	///   be sent as an in-order unit, up until the point that the context is marked
	///   complete (see isComplete). Once a context is marked complete, it may be re-used
	///   as a new logical message. Protocols like TCP that cannot send multiple
	///   independent messages at once (serial streams) will only start processing a new
	///   context once the prior context has been marked complete. Defaults to .defaultMessage.
	/// - Parameter isComplete: A flag indicating if the caller's sending context (logical message) is now complete.
	///   Until a context is marked complete, content sent for other contexts may not
	///   be sent immediately (if the protocol requires sending bytes serially, like TCP).
	///   For datagram protocols, like UDP, isComplete indicates that the content represents
	///   a complete datagram.
	///   When sending using streaming protocols like TCP, isComplete can be used to mark the end
    ///   of a single message on the stream, of which there may be many. However, it can also
	///   indicate that the connection should send a "write close" (a TCP FIN) if the sending
	///   context is the final context on the connection. Specifically, to send a "write close",
	///   pass .finalMessage or .defaultStream for the context (or create a custom context and
	///   set .isFinal), and pass true for isComplete.
	/// - Parameter completion: A completion handler (.contentProcessed) to notify the caller when content has been processed by
	///   the connection, or a marker that this data is idempotent (.idempotent) and may be sent multiple times as fast open data.
	public func send(content: Data?, contentContext: NWConnection.ContentContext = .defaultMessage, isComplete: Bool = true, completion: SendCompletion) {
		switch completion {
		case .idempotent:
			_swift_nw_connection_send_idempotent(self.nw, NWCreateDispatchDataFromNSData(content), contentContext.nw, isComplete)
		case .contentProcessed(let handler):
			_swift_nw_connection_send(self.nw, NWCreateDispatchDataFromNSData(content), contentContext.nw, isComplete) { (error) in
				handler(NWError(error))
			}
		}
	}

	/// Batching allows multiple send or receive calls provides a hint to the connection that the operations
	/// should be coalesced to improve efficiency. Calls other than send and receive will not be affected.
	public func batch(_ block: () -> Void) {
		nw_connection_batch(self.nw, block)
	}

	/// Access connection-wide protocol metadata on the connection. This allows access to state for protocols
	/// like TCP and TLS that have long-term state.
	public func metadata(definition: NWProtocolDefinition) -> NWProtocolMetadata? {
		if let metadata = nw_connection_copy_protocol_metadata(self.nw, definition.nw) {
			if nw_protocol_metadata_is_tcp(metadata) {
				return NWProtocolTCP.Metadata(metadata)
			} else if nw_protocol_metadata_is_udp(metadata) {
				return NWProtocolUDP.Metadata(metadata)
			} else if nw_protocol_metadata_is_ip(metadata) {
				return NWProtocolIP.Metadata(metadata)
			} else if nw_protocol_metadata_is_tls(metadata) {
				return NWProtocolTLS.Metadata(metadata)
			}
			return NWProtocolMetadata(metadata)
		} else {
			return nil
		}
	}
}
