'use strict';

var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');

var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);

var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

var _createClass2 = require('babel-runtime/helpers/createClass');

var _createClass3 = _interopRequireDefault(_createClass2);

var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');

var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);

var _inherits2 = require('babel-runtime/helpers/inherits');

var _inherits3 = _interopRequireDefault(_inherits2);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/* globals $Values */

var tls = require('tls');
var crypto = require('crypto');
var DuplexPair = require('native-duplexpair');

var _require = require('events'),
    EventEmitter = _require.EventEmitter;

var _require2 = require('./packet'),
    TYPE = _require2.TYPE;

var Message = require('./message');
var IncomingMessageStream = require('./incoming-message-stream');
var OutgoingMessageStream = require('./outgoing-message-stream');

module.exports = function (_EventEmitter) {
  (0, _inherits3.default)(MessageIO, _EventEmitter);

  function MessageIO(socket, packetSize, debug) {
    (0, _classCallCheck3.default)(this, MessageIO);

    var _this = (0, _possibleConstructorReturn3.default)(this, (MessageIO.__proto__ || (0, _getPrototypeOf2.default)(MessageIO)).call(this));

    _this.socket = socket;
    _this.debug = debug;

    _this.tlsNegotiationComplete = false;

    _this.incomingMessageStream = new IncomingMessageStream(_this.debug);
    _this.incomingMessageStream.on('data', function (message) {
      message.on('data', function (chunk) {
        _this.emit('data', chunk);
      });
      message.on('end', function () {
        _this.emit('message');
      });
    });

    _this.outgoingMessageStream = new OutgoingMessageStream(_this.debug, { packetSize: packetSize });

    _this.socket.pipe(_this.incomingMessageStream);
    _this.outgoingMessageStream.pipe(_this.socket);
    return _this;
  }

  (0, _createClass3.default)(MessageIO, [{
    key: 'packetSize',
    value: function packetSize() {
      if (arguments.length > 0) {
        var packetSize = arguments.length <= 0 ? undefined : arguments[0];
        this.debug.log('Packet size changed from ' + this.outgoingMessageStream.packetSize + ' to ' + packetSize);
        this.outgoingMessageStream.packetSize = packetSize;
      }
      return this.outgoingMessageStream.packetSize;
    }
  }, {
    key: 'startTls',
    value: function startTls(credentialsDetails, hostname, trustServerCertificate) {
      var _this2 = this;

      var credentials = tls.createSecureContext ? tls.createSecureContext(credentialsDetails) : crypto.createCredentials(credentialsDetails);

      var duplexpair = new DuplexPair();
      var securePair = this.securePair = {
        cleartext: tls.connect({
          socket: duplexpair.socket1,
          servername: hostname,
          secureContext: credentials,
          rejectUnauthorized: !trustServerCertificate
        }),
        encrypted: duplexpair.socket2
      };

      // If an error happens in the TLS layer, there is nothing we can do about it.
      // Forward the error to the socket so the connection gets properly cleaned up.
      securePair.cleartext.on('error', function (err) {
        // Streams in node.js versions before 8.0.0 don't support `.destroy`
        if (typeof securePair.encrypted.destroy === 'function') {
          securePair.encrypted.destroy();
        }
        _this2.socket.destroy(err);
      });

      securePair.cleartext.on('secureConnect', function () {
        var cipher = securePair.cleartext.getCipher();
        if (cipher) {
          _this2.debug.log('TLS negotiated (' + cipher.name + ', ' + cipher.version + ')');
        }
        _this2.emit('secure', securePair.cleartext);
        _this2.encryptAllFutureTraffic();
      });

      securePair.encrypted.on('data', function (data) {
        _this2.sendMessage(TYPE.PRELOGIN, data, false);
      });
    }
  }, {
    key: 'encryptAllFutureTraffic',
    value: function encryptAllFutureTraffic() {
      this.securePair.encrypted.removeAllListeners('data');

      this.outgoingMessageStream.unpipe(this.socket);
      this.socket.unpipe(this.incomingMessageStream);

      this.socket.pipe(this.securePair.encrypted);
      this.securePair.encrypted.pipe(this.socket);

      this.securePair.cleartext.pipe(this.incomingMessageStream);
      this.outgoingMessageStream.pipe(this.securePair.cleartext);

      this.tlsNegotiationComplete = true;
    }
  }, {
    key: 'tlsHandshakeData',
    value: function tlsHandshakeData(data) {
      this.securePair.encrypted.write(data);
    }

    // TODO listen for 'drain' event when socket.write returns false.
    // TODO implement incomplete request cancelation (2.2.1.6)

  }, {
    key: 'sendMessage',
    value: function sendMessage(packetType, data, resetConnection) {
      var message = new Message({ type: packetType, resetConnection: resetConnection });
      message.end(data);
      this.outgoingMessageStream.write(message);
    }

    // Temporarily suspends the flow of incoming packets.

  }, {
    key: 'pause',
    value: function pause() {
      this.incomingMessageStream.pause();
    }

    // Resumes the flow of incoming packets.

  }, {
    key: 'resume',
    value: function resume() {
      this.incomingMessageStream.resume();
    }
  }]);
  return MessageIO;
}(EventEmitter);