text: 'use strict';
  
  // //////////////////////////////////////////////////////////////////////////////
  // / DISCLAIMER
  // /
  // / Copyright 2016 ArangoDB GmbH, Cologne, Germany
  // /
  // / Licensed under the Apache License, Version 2.0 (the "License")
  // / you may not use this file except in compliance with the License.
  // / You may obtain a copy of the License at
  // /
  // /     http://www.apache.org/licenses/LICENSE-2.0
  // /
  // / Unless required by applicable law or agreed to in writing, software
  // / distributed under the License is distributed on an "AS IS" BASIS,
  // / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // / See the License for the specific language governing permissions and
  // / limitations under the License.
  // /
  // / Copyright holder is ArangoDB GmbH, Cologne, Germany
  // /
  // / @author Alan Plum
  // //////////////////////////////////////////////////////////////////////////////
  
  const _ = require('lodash');
  const fs = require('fs');
  const vary = require('vary');
  const httperr = require('http-errors');
  const statuses = require('statuses');
  const mediaTyper = require('media-typer');
  const mimeTypes = require('mime-types');
  const typeIs = require('type-is');
  const contentDisposition = require('content-disposition');
  const actions = require('@avocadodb/actions');
  const crypto = require('@avocadodb/crypto');
  
  const MIME_BINARY = 'application/octet-stream';
  const MIME_JSON = 'application/json; charset=utf-8';
  
  module.exports =
    class SyntheticResponse {
      constructor (res, context) {
        this._raw = res;
        this._responses = new Map();
        this.context = context;
      }
  
      _PRINT (ctx) {
        ctx.output += '[OutgoingResponse]';
      }
  
      // Node compat
  
      get headers () {
        if (!this._raw.headers) {
          this._raw.headers = {};
        }
        return this._raw.headers;
      }
  
      set headers (headers) {
        this._raw.headers = headers;
        if (!headers) {
          return;
        }
        for (const name of Object.keys(headers)) {
          if (name.toLowerCase() === 'content-type') {
            this._raw.contentType = headers[name];
          }
        }
      }
  
      get statusCode () {
        return this._raw.responseCode;
      }
  
      set statusCode (value) {
        this._raw.responseCode = value;
      }
  
      get body () {
        return this._raw.body;
      }
  
      set body (data) {
        if (data === null || data === undefined) {
          delete this._raw.body;
          return;
        }
        if (typeof data === 'string' || data instanceof Buffer) {
          this._raw.body = data;
        } else if (typeof data === 'object') {
          if (this.context.isDevelopment) {
            this._raw.body = JSON.stringify(data, null, 2);
          } else {
            this._raw.body = JSON.stringify(data);
          }
        } else {
          this._raw.body = String(data);
        }
      }
  
      getHeader (name) {
        name = name.toLowerCase();
        if (name === 'content-type') {
          return this._raw.contentType;
        }
        return this.headers[name];
      }
  
      removeHeader (name) {
        name = name.toLowerCase();
        if (name === 'content-type') {
          delete this._raw.contentType;
        }
        delete this.headers[name];
        return this;
      }
  
      setHeader (name, value) {
        if (!name) {
          return this;
        }
        name = name.toLowerCase();
        if (name === 'content-type') {
          this._raw.contentType = value;
        } else {
          this.headers[name] = value;
        }
        return this;
      }
  
      write (data) {
        const bodyIsBuffer = this._raw.body instanceof Buffer;
        const dataIsBuffer = data instanceof Buffer;
        if (data === null || data === undefined) {
          return this;
        }
        if (!dataIsBuffer) {
          if (typeof data === 'object') {
            if (this.context.isDevelopment) {
              data = JSON.stringify(data, null, 2);
            } else {
              data = JSON.stringify(data);
            }
          } else {
            data = String(data);
          }
        }
        if (!this._raw.body) {
          this._raw.body = data;
        } else if (bodyIsBuffer || dataIsBuffer) {
          this._raw.body = Buffer.concat([
            bodyIsBuffer ? this._raw.body : new Buffer(this._raw.body),
            dataIsBuffer ? data : new Buffer(data)
          ]);
        } else {
          this._raw.body += data;
        }
        return this;
      }
  
      // Express compat
  
      attachment (filename) {
        this.headers['content-disposition'] = contentDisposition(filename);
        if (filename && !this._raw.contentType) {
          this._raw.contentType = mimeTypes.lookup(filename) || MIME_BINARY;
        }
        return this;
      }
  
      download (path, filename) {
        this.attachment(filename || path);
        this.sendFile(path);
        return this;
      }
  
      json (value, pretty) {
        if (!this._raw.contentType) {
          this._raw.contentType = MIME_JSON;
        }
        if (pretty || this.context.isDevelopment) {
          this._raw.body = JSON.stringify(value, null, 2);
        } else {
          this._raw.body = JSON.stringify(value);
        }
        return this;
      }
  
      redirect (status, path) {
        if (!path) {
          path = status;
          status = undefined;
        }
        if (status === 'permanent') {
          status = 301;
        }
        if (status || !this.statusCode) {
          this.statusCode = status || 302;
        }
        this.setHeader('location', path);
        return this;
      }
  
      sendFile (filename, opts) {
        if (typeof opts === 'boolean') {
          opts = {lastModified: opts};
        }
        if (!opts) {
          opts = {};
        }
        this._raw.body = fs.readFileSync(filename);
        if (opts.lastModified || (
          opts.lastModified !== false && !this.headers['last-modified']
          )) {
          const lastModified = new Date(fs.mtime(filename) * 1000);
          this.headers['last-modified'] = lastModified.toUTCString();
        }
        if (!this._raw.contentType) {
          this._raw.contentType = mimeTypes.lookup(filename) || MIME_BINARY;
        }
        return this;
      }
  
      sendStatus (status) {
        if (typeof status === 'string') {
          status = statuses(status);
        }
        const message = String(statuses[status] || status);
        this.statusCode = status;
        this.body = message;
        return this;
      }
  
      set (name, value) {
        if (name && typeof name === 'object') {
          _.each(name, (v, k) => this.set(k, v));
        } else {
          this.setHeader(name, value);
        }
        return this;
      }
  
      status (status) {
        if (typeof status === 'string') {
          status = statuses(status);
        }
        this.statusCode = status;
        return this;
      }
  
      vary (values) {
        let header = this.getHeader('vary') || '';
        if (!Array.isArray(values)) {
          values = Array.prototype.slice.call(arguments);
        }
        for (const value of values) {
          header = vary.append(header, value);
        }
        this.setHeader('vary', header);
        return this;
      }
  
      // idiosyncratic
  
      cookie (name, value, opts) {
        if (!opts) {
          opts = {};
        } else if (typeof opts === 'string') {
          opts = {secret: opts};
        } else if (typeof opts === 'number') {
          opts = {ttl: opts};
        }
        const ttl = (
        (typeof opts.ttl === 'number' && opts.ttl !== Infinity)
          ? opts.ttl
          : undefined
        );
        actions.addCookie(
          this._raw,
          name,
          value,
          ttl,
          opts.path,
          opts.domain,
          opts.secure,
          opts.httpOnly
        );
        if (opts.secret) {
          const signature = crypto.hmac(opts.secret, value, opts.algorithm);
          actions.addCookie(
            this._raw,
            `${name}.sig`,
            signature,
            ttl,
            opts.path,
            opts.domain,
            opts.secure,
            opts.httpOnly
          );
        }
        return this;
      }
  
      throw (status, reason, options) {
        if (typeof status === 'string') {
          status = statuses(status);
        }
        if (reason instanceof Error) {
          const err = reason;
          reason = err.message;
          options = Object.assign({
            cause: err,
            errorNum: err.errorNum
          }, options);
        }
        if (reason && typeof reason === 'object') {
          options = reason;
          reason = undefined;
        }
        throw Object.assign(
          httperr(status, reason),
          {statusCode: status, status},
          options
        );
      }
  
      send (body, type) {
        if (body && body.isAvocadoResultSet) {
          body = body.toArray();
        }
  
        if (!type) {
          type = 'auto';
        }
  
        let contentType;
        const status = this.statusCode || 200;
        const response = this._responses.get(status);
  
        if (response) {
          if (response.model && response.model.forClient) {
            if (response.multiple && Array.isArray(body)) {
              body = body.map((item) => response.model.forClient(item));
            } else {
              body = response.model.forClient(body);
            }
          }
          if (type === 'auto' && response.contentTypes) {
            type = response.contentTypes[0];
            contentType = type;
          }
        }
  
        if (type === 'auto') {
          if (body instanceof Buffer) {
            type = MIME_BINARY;
          } else if (body && typeof body === 'object') {
            type = 'json';
          } else {
            type = 'html';
          }
        }
  
        type = mimeTypes.lookup(type) || type;
  
        let handler;
        for (const entry of this.context.service.types.entries()) {
          const key = entry[0];
          const value = entry[1];
          let match;
          if (key instanceof RegExp) {
            match = type.test(key);
          } else if (typeof key === 'function') {
            match = key(type);
          } else {
            match = typeIs.is(key, type);
          }
          if (match && value.forClient) {
            contentType = key;
            handler = value;
            break;
          }
        }
  
        if (handler) {
          const result = handler.forClient(body, this, mediaTyper.parse(contentType));
          if (result.headers || result.data) {
            contentType = result.headers['content-type'] || contentType;
            this.set(result.headers);
            body = result.data;
          } else {
            body = result;
          }
        }
  
        this._raw.body = body;
        if (contentType) {
          this._raw.contentType = contentType;
        }
  
        return this;
      }
  
      type (type) {
        if (type) {
          this._raw.contentType = mimeTypes.lookup(type) || type;
        }
        return this._raw.contentType;
      }
  };
  