text: 'use strict';
  
  // Including native modules.
  const { Readable } = require('stream');
  const zlib = require('zlib');
  
  // Including Lala's modules.
  const Processor = require('./Processor');
  const HTTPSerializer = require('../support/HTTPSerializer');
  const Context = require('../../Types/Context');
  const Logger = require('../../Logger/Logger');
  const ResponseProperties = require('../support/ResponseProperties');
  const ResolvedRoute = require('../../Routing/ResolvedRoute');
  const CORSOptions = require('../support/CORSOptions');
  const { generateUUID } = require('../../Helpers/helpers/BuiltInHelpers');
  const { VERSION } = require('../../constants');
  const {
      InvalidArgumentException,
      NotAcceptableHTTPException
  } = require('../../Exceptions');
  
  /**
   * @typedef {Object} CompressionAlgorithmMap Represents the usage settings defined for each supported compression algorithm.
   *
   * @property {boolean} [gzip=true] If set to "true" it means that if a client accepts GZip compression, it will be considered as compression algorithm for response, behind deflate and Brotli.
   * @property {boolean} [deflate=true] If set to "true" it means that if a client accepts Deflate compression, it will be considered behind the Brotli algorithm.
   * @property {boolean} [brotli=true] If set to "true" it means that if a client accepts Brotli compression it will be used as the first candidate, over Deflate and GZip.
   */
  
  /**
   * @typedef {Object} CompressionAlgorithmCandidate Represents the compression algorithm that is intended to be used and the related encoding name.
   *
   * @property {(BrotliCompress|Deflate|Gzip)} compressor An instance of the class representing the chosen stream.
   * @property {string} contentEncoding A string containing the encoding name corresponding to the compression algorithm that is going to be used.
   */
  
  /**
   * @typedef {Object} OutputProcessorConfiguration An object containing all the properties supported by this class.
   *
   * @property {?Context} context An instance of the class "Context" representing current client connection context containing both the request and response streams.
   * @property {boolean} [GZipCompressionEnabled=true] If set to "true" the Gzip compression algorithm can be used to compress client response.
   * @property {boolean} [deflateCompressionEnabled=true] If set to "true" the Deflate compression algorithm can be used to compress client response.
   * @property {boolean} [BrotliCompressionEnabled=true] If set to "true" the Brotli compression algorithm can be used to compress client response.
   * @property {boolean} [compression=true] If set to "true" and if at least one supported compression algorithm is given by the client, client response will be compressed.
   * @property {?Context} context An instance of the class "Context" representing current client request and containing both the request and the response object generated by the server.
   * @property {boolean} [stealth=false] If set to "true", identification headers such as "X-Powered-By" will be omitted in client response.
   * @property {?string[]} [compressionTypes] An array of strings containing the MIME type of the resources that should be compressed when served.
   * @property {?HeaderManager[]} [headerManagers] An array containing the header managers to execute when building the client response.
   */
  
  /**
   * This class allows to convert an arbitrary value into a readable stream that can be sent to the client.
   */
  class OutputProcessor extends Processor {
      /**
       * Returns all the properties supported by this class and their default values.
       *
       * @returns {OutputProcessorConfiguration} An object containing as key the property name and as value its default value.
       *
       * @override
       */
      static getDefaultConfiguration(){
          return {
              GZipCompressionEnabled: true,
              deflateCompressionEnabled: true,
              BrotliCompressionEnabled: true,
              compression: true,
              context: null,
              stealth: false,
              compressionTypes: [...OutputProcessor.DEFAULT_COMPRESSION_TYPES],
              headerManagers: []
          };
      }
  
      /**
       * Returns the available compression algorithms according to internal settings and current request context (protocol and content type).
       *
       * @returns {Object.<string, number>} An object having as key the algorithm name and as value the preference score.
       *
       * @protected
       */
      _getSupportedCompressionAlgorithms(){
          const supportedEncodings = {};
          if ( this._compression === true ){
              // Brotli requires the HTTPS protocol to be in use.
              if ( this._compressionAlgorithmMap.brotli === true && this._context.getRequest().secure === true ){
                  supportedEncodings.br = 1;
              }
              if ( this._compressionAlgorithmMap.deflate === true ){
                  supportedEncodings.deflate = 0.75;
              }
              if ( this._compressionAlgorithmMap.gzip === true ){
                  supportedEncodings.gzip = 0.5;
              }
          }
          return supportedEncodings;
      }
  
      /**
       * Returns the best compression algorithm that can be used according to client provided preferences, internal settings and current request context.
       *
       * @returns {?string} A string containing the name of the compression algorithm to use or null if no algorithm has been found.
       *
       * @protected
       */
      _getBestCompressionAlgorithm(){
          // Get all the supported algorithms according to internal settings and current request context.
          const supportedAlgorithms = this._getSupportedCompressionAlgorithms();
          // Extract a plain list from the object, list must be ordered by algorithm score.
          const supportedEncodingsList = Object.keys(supportedAlgorithms);
          const acceptedEncodings = [], acceptedEncodingsList = [], ignoreList = [];
          let wildcardEnabled = false, wildcardScore = null;
          let acceptEncoding = this._context.getRequest().headers['accept-encoding'];
          acceptEncoding = acceptEncoding !== '' && typeof acceptEncoding === 'string' ? acceptEncoding.replace(/\s/g, '') : null;
          // By default uncompressed output is always accepted, check if it has been disabled by the client.
          let identityAllowed = acceptEncoding === null || acceptEncoding.indexOf('identity;q=0') === -1;
          // Set the best algorithm available as the default choice.
          let algorithm = acceptEncoding !== null && supportedEncodingsList.length > 0 ? supportedEncodingsList[0] : null;
          if ( algorithm !== null && acceptEncoding !== null && acceptEncoding !== '*' ){
              // If an accept encoding string has been found, extract algorithms from it.
              const encodings = acceptEncoding.split(',');
              const length = encodings.length;
              for ( let i = 0 ; i < length ; i++ ){
                  // Separate the algorithm name from its score.
                  const index = encodings[i].indexOf(';');
                  const encoding = index === -1 ? encodings[i] : encodings[i].substr(0, index);
                  let score;
                  if ( index === -1 ){
                      // If current algorithm is not supported set its score to null in order to ignore it later.
                      score = supportedAlgorithms.hasOwnProperty(encoding) ? supportedAlgorithms[encoding] : null;
                  }else{
                      // Remove the "q=" prefix and get the score as a number.
                      score = parseFloat(encodings[i].substr(index + 3));
                  }
                  if ( encoding === '*' ){
                      // Current encoding is the "*" wildcard, if its score is zero then it means that algorithm selection must be strict.
                      wildcardEnabled = score > 0;
                      wildcardScore = score;
                  }else if ( score !== null && score <= 0 ){
                      // This algorithm's score is 0, it mustn't be used.
                      ignoreList.push(encoding);
                  }else if ( supportedAlgorithms.hasOwnProperty(encoding) && acceptedEncodingsList.indexOf(encoding) === -1 ){
                      // This is an algorithm accepted by both the client and the server side, add it to the candidate list.
                      acceptedEncodings.push([encoding, score]);
                      acceptedEncodingsList.push(encoding);
                  }
              }
              if ( wildcardEnabled ){
                  // Wildcard is enabled, server can pick the best algorithm but this choice will have the same score assigned to the wildcard.
                  let alternativeEncoding = null, alternativeEncodingScore = null, i = 0;
                  // Iterate all the supported algorithms ordered by their score.
                  while ( alternativeEncoding === null && i < supportedEncodingsList.length ){
                      if ( acceptedEncodingsList.indexOf(supportedEncodingsList[i]) === -1 && ignoreList.indexOf(supportedEncodingsList[i]) === -1 ){
                          // Current algorithm has not been suggested by the client nor banned by the client, use it as an alternative algorithm.
                          alternativeEncoding = supportedEncodingsList[i];
                          alternativeEncodingScore = supportedAlgorithms[supportedEncodingsList[i]];
                      }
                      i++;
                  }
                  if ( alternativeEncoding !== null ){
                      // An alternative algorithm has been found, add it to the list of accepted algorithms assigning the same score assigned to the wildcard.
                      const score = wildcardScore === null ? alternativeEncodingScore : wildcardScore;
                      acceptedEncodings.push([alternativeEncoding, score]);
                  }
              }
              // Order the list of all the accepted algorithms by their score, then pick the first one.
              algorithm = acceptedEncodings.length === 0 ? null : acceptedEncodings.sort((a, b) => {
                  return a[1] <= b[1] ? 1 : -1;
              })[0][0];
          }
          // If no algorithm has been found returns that no compression should be applied, is uncompressed output is disabled, return null.
          return identityAllowed && algorithm === null ? 'identity' : algorithm;
      }
  
      /**
       * Generates the stream to use to compress data to send to the client according to defined settings and client accepted algorithms.
       *
       * @returns {?CompressionAlgorithmCandidate} An object containing both the compression stream and the related Content-Encoding HTTP header, if no eligible algorithm is found, null is returned instead.
       *
       * @protected
       */
      _createCompressionStream(){
          let candidate = null;
          if ( !this._headers.hasOwnProperty('Accept-Ranges') && this._compressionTypes.indexOf(this._MIMEType) >= 0 ){
              switch ( this._getBestCompressionAlgorithm() ){
                  case 'br': {
                      candidate = {
                          compressor: zlib.createBrotliCompress(),
                          contentEncoding: 'br'
                      };
                  }break;
                  case 'deflate': {
                      candidate = {
                          compressor: zlib.createDeflate(),
                          contentEncoding: 'deflate'
                      };
                  }break;
                  case 'gzip': {
                      candidate = {
                          compressor: zlib.createGzip(),
                          contentEncoding: 'gzip'
                      };
                  }break;
                  case null: {
                      throw new NotAcceptableHTTPException('No available encoding found.', 1);
                  }
              }
          }
          return candidate;
      }
      
      static _parseContentTypeHeader(contentType){
          const components = {
              charset: null
          };
          const separatorIndex = contentType.indexOf(';');
          if ( separatorIndex === -1 ){
              components.MIMEType = contentType.trim();
          }else{
              components.MIMEType = contentType.substr(0, separatorIndex).trim();
              const remainingPart = contentType.substr(separatorIndex).trim();
              const index = remainingPart.indexOf('charset=');
              if ( index >= 0 ){
                  const escaped = remainingPart.charAt(index + 8) === '"' && remainingPart.charAt(remainingPart.length - 1) === '"';
                  components.charset = escaped ? remainingPart.substring(index + 9, remainingPart.length - 1) : remainingPart.substr(index + 8);
              }
          }
          return components;
      }
  
      /**
       * Generates the "content-type" HTTP header according to the content type and charset detected.
       *
       * @param {module:http.ServerResponse} response An instance of the built-in class "ServerResponse" representing the response that will be sent back to the client.
       *
       * @returns {?string} A string containing the content type or null if no content type header should be sent.
       *
       * @private
       */
      _processContentType(response){
          let contentType = OutputProcessor.DEFAULT_CONTENT_TYPE, charset = null;
          if ( response.hasHeader('content-type') ){
              // A content type header has been defined, extract the MIME type and the charset from it.
              const components = OutputProcessor._parseContentTypeHeader(response.getHeader('content-type'));
              this._MIMEType = contentType = components.MIMEType;
              //
              charset = response.charset !== '' && typeof response.charset === 'string' ? response.charset : components.charset;
          }else if ( response.contentType !== '' && typeof response.contentType === 'string' ){
              // A content type has been defined, extract the MIME type and the charset from it.
              const components = OutputProcessor._parseContentTypeHeader(response.contentType);
              this._MIMEType = contentType = components.MIMEType;
              charset = response.charset !== '' && typeof response.charset === 'string' ? response.charset : components.charset;
          }else{
              // Get the content type declared by the resource that has been serialized.
              const responseMIMEType = this._responseProperties === null ? null : this._responseProperties.getMIMEType();
              if ( responseMIMEType !== null ){
                  this._MIMEType = contentType = responseMIMEType;
                  // Check if an user defined charset is set, otherwise use the one declared by the resource that has been serialized.
                  charset = response.charset !== '' && typeof response.charset === 'string' ? response.charset : this._responseProperties.getCharset();
              }
          }
          if ( charset !== null ){
              // Generate the content-type HTTP header.
              contentType += '; charset="' + charset.replace(/"/g, '') + '"';
          }
          response.MIMEType = response.contentType = this._MIMEType;
          response.charset = charset;
          if ( contentType !== null ){
              this._headers['Content-Type'] = contentType;
          }
          return contentType;
      }
  
      /**
       * Executes the header managers defined for the server and both resolved route and route's router.
       *
       * @param {module:http.IncomingMessage} request An instance of the built-in class "IncomingMessage" containing all the connection properties.
       * @param {module:http.ServerResponse} response An instance of the built-in class "ServerResponse" representing the response that will be sent back to the client.
       *
       * @protected
       */
      _processHeaderManagers(request, response){
          // Process header managers defined at server level.
          let headerManagers, length = this._headerManagers.length;
          for ( let i = 0 ; i < length ; i++ ){
              // Merge generated headers.
              this._headers = Object.assign(this._headers, this._headerManagers[i].buildHeaders(request, response));
          }
          if ( request.resolvedRoute instanceof ResolvedRoute ){
              // Process header managers defined for the router the resolved route belongs to.
              headerManagers = request.resolvedRoute.getRouter().getHeaderManagers();
              length = headerManagers.length;
              for ( let i = 0 ; i < length ; i++ ){
                  this._headers = Object.assign(this._headers, headerManagers[i].buildHeaders(request, response));
              }
              // Process header managers defined for the resolved route.
              headerManagers = request.resolvedRoute.getRoute().getHeaderManagers();
              length = headerManagers.length;
              for ( let i = 0 ; i < length ; i++ ){
                  this._headers = Object.assign(this._headers, headerManagers[i].buildHeaders(request, response));
              }
          }
      }
  
      /**
       * Generates the "content-length" HTTP header.
       *
       * @param {module:http.ServerResponse} response An instance of the built-in class "ServerResponse" representing the response that will be sent back to the client.
       *
       * @protected
       */
      _processContentLength(response){
          if ( !response.hasHeader('content-length') ){
              let contentLength;
              if ( !isNaN(response.contentLength) && response.contentLength >= 0 ){
                  // A content length has been declared, use it.
                  contentLength = response.contentLength;
              }else{
                  // Get the content length obtained after the output serialization process.
                  contentLength = this._responseProperties.getContentLength();
              }
              if ( contentLength > 0 ){
                  this._headers['Content-Length'] = contentLength.toString();
              }
          }
      }
  
      /**
       * Generates the "last-modified" HTTP header.
       *
       * @param {module:http.ServerResponse} response An instance of the built-in class "ServerResponse" representing the response that will be sent back to the client.
       *
       * @protected
       */
      _processLastModified(response){
          if ( !response.hasHeader('last-modified') ){
              let lastModifiedDate;
              if ( response.lastModifiedDate === null || response.lastModifiedDate instanceof Date ){
                  // A date has been declared, use it.
                  lastModifiedDate = response.lastModifiedDate;
              }else{
                  // Get the last modified date obtained after response serialization.
                  lastModifiedDate = this._responseProperties.getLastModifiedDate();
              }
              if ( lastModifiedDate !== null ){
                  this._headers['Last-Modified'] = lastModifiedDate.toUTCString();
              }
          }
      }
  
      /**
       * Generates the "etag" HTTP header used for caching purposes.
       *
       * @param {module:http.ServerResponse} response An instance of the built-in class "ServerResponse" representing the response that will be sent back to the client.
       *
       * @protected
       */
      _processETag(response){
          if ( !response.hasHeader('etag') ){
              let eTag;
              if ( response.eTag === null || typeof response.eTag === 'string' ){
                  // An etag has been declared, use it.
                  eTag = response.eTag;
              }else{
                  // Get the etag obtained after response serialization.
                  eTag = this._responseProperties.getETag();
              }
              if ( eTag !== null && eTag !== '' ){
                  this._headers['ETag'] = eTag;
              }
          }
      }
  
      /**
       * Generates the headers related to the HTTP range request context.
       *
       * @param {module:http.ServerResponse} response An instance of the built-in class "ServerResponse" representing the response that will be sent back to the client.
       *
       * @private
       */
      _processRange(response){
          if ( !response.hasHeader('accept-ranges') ){
              const useRanges = typeof response.useRanges === 'boolean' ? response.useRanges : this._responseProperties.getRangedRequestSupported();
              if ( useRanges ){
                  // Declare to the client side that range requests are supported and accepted.
                  this._headers['Accept-Ranges'] = 'bytes';
              }
          }
          const contentRanges = this._responseProperties.getContentRanges();
          if ( contentRanges.length > 0 ){
              // At least one range is being served, add range request related headers.
  
              // TODO: Multiple ranges are currently not supported.
              const contentRanges = [contentRanges[0]];
  
              if ( contentRanges.length === 1 ){
                  // A single resource slice is being served.
                  this._headers['Content-Range'] = 'bytes ' + contentRanges[0].start + '-' + contentRanges[0].end;
                  this._headers['Content-Range'] += contentRanges[0].size > 0 ? ( '/' + contentRanges[0].size ) : '/*';
                  this._headers['Content-Length'] = contentRanges[0].length;
              }else{
                  // Multiple slices are being served, prepare a multipart response.
                  this._multipartByteRangesBoundary = generateUUID(4, false);
                  this._headers['Content-Type'] = 'multipart/byteranges; boundary=' + this._multipartByteRangesBoundary;
              }
          }
      }
  
      /**
       * Adds some additional headers to the client response according to current settings.
       *
       * @param {module:http.IncomingMessage} request An instance of the built-in class "IncomingMessage" containing all the connection properties.
       * @param {module:http.ServerResponse} response An instance of the built-in class "ServerResponse" representing the response that will be sent back to the client.
       *
       * @protected
       */
      _prepareHeaders(request, response){
          if ( this._stealth !== true ){
              this._headers['X-Powered-By'] = this._headers['Server'] = Logger.getDebug() ? ( 'Lala.js/' + VERSION ) : 'Lala.js';
          }
          this._processContentType(response);
          if ( this._responseProperties !== null ){
              this._processContentLength(response);
              this._processLastModified(response);
              this._processETag(response);
              this._processRange(response);
          }
          this._headers['Date'] = ( new Date() ).toUTCString();
          if ( request.CORSOptions instanceof CORSOptions ){
              // Adds CORS related headers according to CORS settings defined.
              this._headers = Object.assign(this._headers, request.CORSOptions.buildHeaders(request, response));
          }
          this._processHeaderManagers(request, response);
      }
  
      /**
       * Serializes given data into a stream that can be sent to the client as a response.
       *
       * @param {*} data Some data to serialize.
       *
       * @returns {Promise<?module:stream.internal.Readable[]>} An array containing the streams to serve represented as readable streams.
       *
       * @async
       * @protected
       */
      async _serialize(data){
          // Initialize the serializer.
          const serializer = new HTTPSerializer();
          // Setup current request context parameters to use if a callback is given.
          const callbackParameters = this._context === null ? [null, null] : [this._context.getRequest(), this._context.getResponse()];
          serializer.setContext(this._context).setCallbackParameters(callbackParameters);
          // Generate the serialized stream.
          const serialization = await serializer.serialize(data);
          this._responseProperties = serializer.getResponseProperties();
          return serialization;
      }
  
      /**
       * Serializes the output that has been declared.
       *
       * @param {module:http.ServerResponse} response An instance of the built-in class "ServerResponse" representing the response that will be sent back to the client.
       *
       * @return {Promise<?module:stream.internal.Readable[]>}
       *
       * @async
       * @protected
       */
      async _prepareOutput(response){
          let output;
          if ( response.hasOwnProperty('rawOutput') && response.rawOutput !== null ){
              output = await this._serialize(response.rawOutput);
          }else{
              this._responseProperties = new ResponseProperties();
              // Generate and empty stream.
              const stream = new Readable();
              stream.push(null);
              output = [stream];
          }
          return output;
      }
  
      /**
       * The class constructor.
       *
       * @param {?OutputProcessorConfiguration} [configuration=null] An object containing the values for class properties.
       */
      constructor(configuration = null){
          super(configuration);
  
          /**
           * @type {?Context} _context An object representing current client request and containing both the request and the response object generated by the server.
           *
           * @protected
           */
          this._context = null;
  
          /**
           * @type {CompressionAlgorithmMap} _compressionAlgorithmMap An object containing the usage settings for each the supported algorithms.
           *
           * @protected
           */
          this._compressionAlgorithmMap = {
              gzip: true,
              deflate: true,
              brotli: true
          };
  
          /**
           * @type {boolean} _compression If set to "true", a compression layer will be applied over the response stream.
           *
           * @protected
           */
          this._compression = true;
  
          /**
           * @type {string[]} _compressionTypes An array of strings containing the MIME type of the resources that should be compressed when served.
           *
           * @protected
           */
          this._compressionTypes = [];
  
          /**
           * @type {boolean} [_stealth=false] If set to "true", identification headers such as "X-Powered-By" will be omitted in client response.
           *
           * @protected
           */
          this._stealth = false;
  
          /**
           * @type {HeaderManager[]} _headerManagers An array containing the header managers to execute when building the client response.
           *
           * @protected
           */
          this._headerManagers = [];
  
          /**
           * @type {Object.<string, string>} _headers An object containing all the HTTP headers that will be sent to the client once the response is ready and right to be sent.
           *
           * @protected
           */
          this._headers = {};
  
          /**
           * @type {?ResponseProperties} [_responseProperties] Contains some properties to declare to the client as a set of HTTP headers.
           *
           * @protected
           */
          this._responseProperties = null;
  
          /**
           * @type {?string} [_MIMEType] A string containing the MIME type detected or declared according to the response to serve.
           *
           * @protected
           */
          this._MIMEType = null;
  
          /**
           * @type {?string} [_multipartByteRangesBoundary] A string containing the boundary to use when serving a multipart response.
           *
           * @protected
           */
          this._multipartByteRangesBoundary = null;
  
          if ( configuration !== null && typeof configuration === 'object' ){
              // Setup internal properties.
              this.configure(configuration);
          }
      }
  
      /**
       * Configures internal properties based on the configuration object given, this method is chainable.
       *
       * @param {OutputProcessorConfiguration} configuration An object containing the values for class properties.
       *
       * @returns {OutputProcessor}
       *
       * @throws {InvalidArgumentException} If an invalid configuration object is given.
       *
       * @override
       */
      configure(configuration){
          if ( configuration === null || typeof configuration !== 'object' ){
              throw new InvalidArgumentException('Invalid configuration object.', 1);
          }
          if ( configuration.hasOwnProperty('context') && configuration.context instanceof Context ){
              this._context = configuration.context;
          }
          this._compressionAlgorithmMap.gzip = !configuration.hasOwnProperty('GZipCompressionEnabled') || configuration.GZipCompressionEnabled !== false;
          this._compressionAlgorithmMap.deflate = !configuration.hasOwnProperty('deflateCompressionEnabled') || configuration.deflateCompressionEnabled !== false;
          this._compressionAlgorithmMap.brotli = !configuration.hasOwnProperty('BrotliCompressionEnabled') || configuration.BrotliCompressionEnabled !== false;
          this._compression = !configuration.hasOwnProperty('compression') || configuration.compression !== false;
          this._stealth = configuration.hasOwnProperty('stealth') && configuration.stealth === true;
          if ( configuration.hasOwnProperty('compressionTypes') && Array.isArray(configuration.compressionTypes) ){
              this._compressionTypes = configuration.compressionTypes;
          }
          if ( configuration.hasOwnProperty('headerManagers') && Array.isArray(configuration.headerManagers) ){
              this._headerManagers = configuration.headerManagers;
          }
          return this;
      }
  
      /**
       * Updates current request context by setting new streams for both client request and response, this method is chainable.
       *
       * @param {module:http.IncomingMessage} request An instance of the built-in class "IncomingMessage" containing all the connection properties.
       * @param {module:http.ServerResponse} response An instance of the built-in class "ServerResponse" representing the response that will be sent back to the client.
       *
       * @returns {OutputProcessor}
       *
       * @throws {InvalidArgumentException} If an invalid object is given as request object.
       * @throws {InvalidArgumentException} If an invalid object is given as response object.
       */
      updateContext(request, response){
          if ( request === null || typeof request !== 'object' ){
              throw new InvalidArgumentException('Invalid request object.', 1);
          }
          if ( response === null || typeof response !== 'object' ){
              throw new InvalidArgumentException('Invalid response object.', 2);
          }
          this._context = new Context(request, response);
          return this;
      }
  
      /**
       * Generates a readable stream from a string serialization obtained from some given data.
       *
       * @param {module:http.IncomingMessage} request An instance of the built-in class "IncomingMessage" containing all the connection properties.
       * @param {module:http.ServerResponse} response An instance of the built-in class "ServerResponse" representing the response that will be sent back to the client.
       *
       * @async
       */
      async process(request, response){
          let output = request.isPreflightRequest === true ? null : await this._prepareOutput(response);
          // Send the generated response to the client if no output has been made so far.
          if ( !response.headersSent ){
              const contentRanges = this._responseProperties === null ? [] : this._responseProperties.getContentRanges();
              // Add all the other HTTP headers required according to internal settings.
              this._prepareHeaders(request, response);
              // Get the compression algorithm to apply to the response.
              const compression = this._createCompressionStream();
              if ( compression !== null ){
                  // Add compression related HTTP headers, if compression is available.
                  this._headers['Vary'] = 'Accept-Encoding';
                  this._headers['Content-Encoding'] = compression.contentEncoding;
                  delete this._headers['Content-Length'];
              }
              let statusMessage = response.statusMessage, statusCode = response.statusCode;
              if ( response.statusCode === 200 ){
                  statusMessage = response.overrideStatusMessage === true ? response.statusMessage : 'Found';
                  if ( response.overrideStatusMessage !== true ){
                      if ( contentRanges.length > 0 ){
                          statusMessage = 'Partial Content';
                          statusCode = 206;
                      }else if ( output === null || output.length === 0 ){
                          if ( this._responseProperties !== null && this._responseProperties.getUnchanged() ){
                              statusMessage = 'Not Modified';
                              statusCode = 304;
                          }else{
                              statusMessage = 'No Content';
                              statusCode = 204;
                              this._headers['Content-Type'] = 'text/plain';
                              this._headers['Content-Length'] = '0';
                          }
                      }
                  }
              }
              // Write the response head.
              response.writeHead(statusCode, statusMessage, this._headers);
              if ( output === null || output.length === 0 ){
                  response.end();
              }else{
  
                  // TODO: Multiple ranges are currently not supported.
                  output = [output[0]];
  
                  if ( output.length === 1 ){
                      if ( compression !== null ){
                          // Apply compression.
                          output[0] = output[0].pipe(compression.compressor);
                      }
                      // Flush the output generated to the client.
                      output[0].pipe(response);
                  }else{
                      // TODO: Multiple ranges are currently not supported.
                  }
              }
          }
      }
  }
  
  /**
   * @constant Contains the default MIME type used for the Content-Type header.
   *
   * @type {string}
   * @default
   */
  Object.defineProperty(OutputProcessor, 'DEFAULT_CONTENT_TYPE', {
      value: 'text/html',
      writable: false,
      configurable: true,
      enumerable: true
  });
  
  /**
   * @constant Contains the list of MIME types to serve compressed by default.
   *
   * @type {string}
   * @default
   */
  Object.defineProperty(OutputProcessor, 'DEFAULT_COMPRESSION_TYPES', {
      value: [
          'text/xml',
          'image/svg+xml',
          'application/x-font-ttf',
          'image/vnd.microsoft.icon',
          'application/x-font-opentype',
          'application/json',
          'font/eot',
          'application/vnd.ms-fontobject',
          'application/javascript',
          'font/otf',
          'application/xml',
          'application/xhtml+xml',
          'text/javascript',
          'application/x-javascript',
          'text/plain',
          'application/x-font-truetype',
          'application/xml+rss',
          'image/x-icon',
          'font/opentype',
          'text/css',
          'image/x-win-bitmap',
          'text/html'
      ],
      writable: false,
      configurable: true,
      enumerable: true
  });
  
  module.exports = OutputProcessor;
  