import blobToBuffer from 'blob-to-buffer';
import EventEmitter2 from 'eventemitter2';

let tcpPolyfillOptions = {
  path: '/',
  secure: false,
  wsProtocols: undefined,
  simulatedLatencyMs: undefined,
};

const notImpl = name => () => {
  throw new Error('Not implemented in TcpPolyfill: ' + name);
};

export function configureTcpPolyfill(options) {
  tcpPolyfillOptions.path = options.path;
  tcpPolyfillOptions.secure = options.secure;
  tcpPolyfillOptions.wsProtocols = options.wsProtocols;
  tcpPolyfillOptions.simulatedLatencyMs = options.simulatedLatencyMs;
}

export function Socket(options) {
  if (!(this instanceof Socket)) {
    return new Socket(options);
  }

  const emitter = new EventEmitter2({});
  ['on', 'once', 'removeListener', 'emit', 'addListener', 'removeAllListeners', 'setMaxListeners', 'listeners'].forEach(method => {
    this[method] = emitter[method].bind(emitter);
  });

  let ws = null;

  this.connect = (port, host, connectListener) => {
    this._simulatedLatencyMs = tcpPolyfillOptions.simulatedLatencyMs;
    const protocol = tcpPolyfillOptions.secure ? 'wss' : 'ws';
    const path = tcpPolyfillOptions.path;
    const url = `${protocol}://${host}:${port}${path}`;
    ws = new WebSocket(url, tcpPolyfillOptions.wsProtocols);
    if (connectListener) {
      emitter.on('connect', connectListener);
    }

    ws.onopen = event => {
      emitter.emit('connect');
    };

    ws.onclose = event => {
      emitter.emit('end');
      emitter.emit('close');
    };

    ws.onerror = event => {
      emitter.emit('error', event);
    };

    ws.onmessage = event => {
      const data = event.data;
      if (typeof Blob !== 'undefined' && data instanceof Blob) {
        blobToBuffer(data, (err, buffer) => {
          if (err) {
            throw err;
          }
          emitter.emit('data', buffer);
        });
      } else if (typeof data === 'string' && ws.protocol === 'base64') {
        emitter.emit('data', new Buffer(data, 'base64'));
      } else {
        emitter.emit('data', data);
      }
    };
  };

  this.end = data => {
    if (data !== undefined) {
      this.write(data);
    }
    ws.close();
  };

  this.destroy = () => {
    ws.close();
  };

  this.write = data => {
    // Convert data (string or node.js Buffer) to ArrayBuffer or base64 string
    // for WebSocket
    let outgoingMessage = null;
    if (ws.protocol === 'base64') {
      outgoingMessage = (new Buffer(data)).toString('base64');
    } else {
      const arrayBuffer = new ArrayBuffer(data.length);
      const view = new Uint8Array(arrayBuffer);
      for (let i = 0; i < data.length; ++i) {
        view[i] = data[i];
      }
      outgoingMessage = arrayBuffer;
    }
    const delay = this._simulatedLatencyMs;
    if (typeof delay === 'number' && delay > 0) {
      setTimeout(() => ws.send(outgoingMessage), delay);
    } else {
      ws.send(outgoingMessage);
    }
  };

  this.setNoDelay = noDelay => {};
  this.setKeepAlive = (enable, initialDelay) => {};

  const notImplMethods = ['setEncoding', 'pause', 'resume', 'setTimeout', 'address', 'unref', 'ref'];
  notImplMethods.forEach(name => {
    this[name] = notImpl(name);
  });
}

export function connect(...args) {
  const opts = {};
  if (args[0] && typeof args[0] === 'object') {
    opts.port = args[0].port;
    opts.host = args[0].host;
    opts.connectListener = args[1];
  } else if (Number(args[0]) > 0) {
    opts.port = args[0];
    opts.host = args[1];
    opts.connectListener = args[2];
  } else {
    throw new Error('Unsupported arguments for net.connect');
  }
  const socket = new Socket();
  socket.connect(opts.port, opts.host, opts.connectListener);
  return socket;
}

export const createConnection = connect;

export const createServer = notImpl('createServer');

// This is wrong, but irrelevant for connecting via websocket
export const isIPv4 = input => true;
export const isIPv6 = input => false;
export const isIP = input => isIPv4(input) ? 4 : isIPv6(input) ? 6 : 0;

@@ -1632,16 +1632,1141 @@
 t.data;%0A
+%0A      // Handle incoming message as base64 if ws.protocol is set to base64%0A      // or if it's not set at all and we are in a problematic React Native%0A      // environment per:%0A      // %0A      // https://tools.ietf.org/html/rfc6455#section-11.3.4%0A      // https://tools.ietf.org/html/rfc3864%0A      // %0A      // We check if this process is running inside React Native by using navigator.product:%0A      // %0A      // https://github.com/facebook/react-native/commit/3c65e62183ce05893be0822da217cb803b121c61%0A%0A      const base64SetByWsClient = (%0A        typeof data === 'string' &&%0A        ws.protocol === 'base64'%0A      );%0A%0A      const base64SetByUser = (%0A        tcpPolyfillOptions.wsProtocols &&%0A        tcpPolyfillOptions.wsProtocols.constructor === Array &&%0A        tcpPolyfillOptions.wsProtocols.indexOf('base64') %3E= 0%0A      );%0A%0A      const isRN = (%0A        typeof navigator !== 'undefined' &&%0A        navigator.product === 'ReactNative'%0A      );%0A%0A      const base64Workaround = (%0A        isRN &&%0A        base64SetByUser &&%0A        !ws.protocol%0A      );%0A%0A      const handleBase64 = base64SetByWsClient %7C%7C base64Workaround;%0A%0A
       if
@@ -2997,60 +2997,20 @@
 if (
-typeof data === 'string' && ws.protocol === 'b
+handleB
 ase64
-'
 ) %7B%0A

patch to handle a problematic RN environment