"use strict";

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

// Provides native APIs for RPCWebSocket transport.
//
// Because we're passing through some native APIs, e.g. net, we recast its API
// to something more browser-safe, so don't assume the APIs are 1:1 or behave
// exactly like the native APIs.
var _require = require('./safeEmitter'),
    createSafeEmitter = _require.createSafeEmitter;

var http = require('http');

var ws = require('ws');

var origInstanceMap = new Map();
var nextInstanceId = 1; // converts Node.js Buffer to ArrayBuffer

function toArrayBuffer(buffer) {
  return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
}

function recastWSSocket(socket, req) {
  var emitter = createSafeEmitter();
  socket.on('error', function (err) {
    return emitter.emit('error', err);
  });
  socket.on('close', function (code, message) {
    return emitter.emit('close', code, message);
  });
  socket.on('message', function (data) {
    if (data instanceof Buffer) {
      data = toArrayBuffer(data);
    }

    emitter.emit('message', data);
  });
  return _objectSpread({
    upgradeReq: function upgradeReq() {
      return {
        url: req.url,
        headers: {
          origin: req.headers.origin
        }
      };
    },
    send: function send(data) {
      var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

      if (opts.binary) {
        data = Buffer.from(data);
      }

      try {
        socket.send(data, opts);
      } catch (e) {
        // ws shouldn't be throwing when CLOSED or CLOSING
        // currently being addressed in https://github.com/websockets/ws/pull/1532
        if (!e.message.match(/CLOS(ED|ING)/)) {
          throw e;
        }
      }
    },
    close: function close(code, message) {
      return socket.close(code, message);
    }
  }, emitter);
}

function createWrappedWSServer(opts) {
  // opts.server that comes in is our remapped server, so we
  // get the original
  if (opts.instanceId) {
    opts.server = origInstanceMap.get(opts.instanceId);
  }

  var wss = new ws.Server(opts);
  var emitter = createSafeEmitter();
  wss.on('connection', function (socket, req) {
    return emitter.emit('connection', recastWSSocket(socket, req));
  });
  return _objectSpread({}, emitter);
}

function recastHTTPReq(req) {
  var attached = false;
  var emitter = createSafeEmitter();
  return {
    url: function url() {
      return req.url;
    },
    method: function method() {
      return req.method;
    },
    headers: function headers() {
      return req.headers;
    },
    on: function on(name, listener) {
      // We need to attach listeners for data only on data event, which sets the
      // request to flowing mode.
      if (name === 'data' && !attached) {
        req.on('error', function (err) {
          return emitter.emit('error', err);
        });
        req.on('end', function () {
          return emitter.emit('end');
        });
        req.on('data', function (data) {
          // force cast the data to a string
          // this is because we only deal with string data on http requests so far
          emitter.emit('data', '' + data);
        });
        attached = true;
      }

      emitter.on(name, listener);
    }
  };
}

function recastHTTPRes(res) {
  return {
    setHeader: function setHeader(header, value) {
      return res.setHeader(header, value);
    },
    writeHead: function writeHead(status, headers) {
      return res.writeHead(status, headers);
    },
    end: function end(body) {
      return res.end(body);
    }
  };
}

function createWrappedHTTPServer() {
  var server = http.createServer();
  var emitter = createSafeEmitter();
  server.on('error', function (err) {
    return emitter.emit('error', err);
  });
  server.on('request', function (req, res) {
    return emitter.emit('request', recastHTTPReq(req), recastHTTPRes(res));
  });

  var recast = _objectSpread({
    address: function address() {
      return server.address();
    },
    listening: function listening() {
      return server.listening;
    },
    listen: function listen(port, host, callback) {
      return server.listen(port, host, callback);
    },
    instanceId: nextInstanceId
  }, emitter);

  origInstanceMap.set(nextInstanceId, server);
  nextInstanceId += 1;
  return recast;
}

module.exports = {
  ws: {
    Server: createWrappedWSServer
  },
  http: {
    createServer: createWrappedHTTPServer
  }
};
