text: const WebSocketClient = require('websocket').client;
  
  const MessageHeader = require('./Message/Header.js');
  const MessageText = require('./Message/Text.js');
  const MessageFile = require('./Message/File.js');
  const MessageEventValue = require('./Message/EventValue.js');
  const MessageEventText = require('./Message/EventText.js');
  const MessageEventDaytimer = require('./Message/EventDaytimer.js');
  const MessageEventWeather = require('./Message/EventWeather.js');
  
  /*
      Events:
          close
          close_failed
          connect
          connect_failed
          connection_error
          send
          message_header
          message_text
          message_file
          message_event_table_values
          message_event_table_text
          message_event_table_daytimer
          message_event_table_weather
          message_invalid
          keepalive
  */
  
  var Connection = function (host, keepalive) {
      this._host = host;
      this._keepalive = (keepalive === undefined ? 30000 : keepalive);
      this._keepalive_interval = undefined;
      this._keepalive_time = undefined;
      this._reconnect_interval = undefined;
      this._ws_url = 'ws://'+host+'/ws/rfc6455';
      this._ws = {
          'client': new WebSocketClient(),
          'connection': undefined,
      };
      this._connection_data = {};
      this.register_client();
  };
  
  Connection.prototype.__proto__ = require('events').EventEmitter.prototype;
  
  Connection.prototype.connect = function() {
      this._ws.client.connect(this._ws_url, 'remotecontrol');
  };
  
  Connection.prototype.close = function() {
      if (this._ws.connection !== undefined){
          clearInterval(this._keepalive_interval);
          this._ws.connection.close();
      }else{
          this.emit('close_failed');
      }
  };
  
  Connection.prototype.register_client = function() {
      var that = this;
      this._ws.client.on('connectFailed', function(error) {
          console.log("WebSocket error: ", this._ws.connection.closeDescription, this._ws.connection.closeReasonCode);
          let reason = that._ws.connection.closeDescription + ' (' + that._ws.connection.closeReasonCode + ')';
          that.emit('connect_failed', error, reason);
      }.bind(this));
  
      this._ws.client.on('connect', function(connection) {
          that.register_connection(connection);
          that.emit('connect');
      });
  };
  
  Connection.prototype.register_connection = function(connection) {
      var that = this;
      this._ws.connection = connection;
      this._connection_data = {
          'message_state': 'header',
          'status_update_subscription': false,
      };
      this._keepalive_interval = setInterval( function() {
          if (that._ws.connection !== undefined){
              that._ws.connection.sendUTF('keepalive');
          } else {
              clearInterval(that._keepalive_interval);
          }
      }, this._keepalive);
  
      this._reconnect_interval = setInterval(function () {
          if(that._keepalive_time !== undefined) {
              const timeSinceLastKeepalive = new Date().getTime() - that._keepalive_time.getTime();
              if(timeSinceLastKeepalive > parseInt(that._keepalive) + 2000) {
                  that.emit('close', true, 'timeout');
                  clearInterval(that._reconnect_interval)
              }
          }
      }, 500);
  
      /*
      this._ws.connection.on('close', function() {
          that._ws.connection = undefined;
          clearInterval(that._keepalive_interval);
          if (that._connection_data.last_header && that._connection_data.last_header.identifier == '5'){
              that.emit('close', true);
          }else{
              that.emit('close', false);
          }
      });
  
      this._ws.connection.on('error', function(error) {
          clearInterval(that._keepalive_interval);
          that.emit('connection_error', error);
      });
      */
  
      this._ws.connection.on('close', function(_reason, _desc) {
          console.log("Websocket Connection closed: ", _reason, _desc);
          let reason = _desc + ' (' + _reason + ')';
          that._ws.connection = undefined;
          clearInterval(that._keepalive_interval);
          clearInterval(that._reconnect_interval);
          if (that._connection_data.last_header && that._connection_data.last_header.identifier == '5'){
              that.emit('close', true, reason);
          }else{
              that.emit('close', false, reason);
          }
      });
  
      this._ws.connection.on('error', function(error) {
          console.log("WebSocket error: ", this._ws.connection.closeDescription, this._ws.connection.closeReasonCode);
          clearInterval(that._keepalive_interval);
          clearInterval(that._reconnect_interval);
          let reason = this._ws.connection.closeDescription + ' (' + this._ws.connection.closeReasonCode + ')';
          that.emit('connection_error', error, reason);
      }.bind(this));
  
      this._ws.connection.on('message', function(message) {
          that.handle_message(message);
      });
  };
  
  Connection.prototype.send = function(message) {
      if (this._ws.connection !== undefined){
          this._connection_data.last_request = message;
          this._ws.connection.sendUTF(message);
          this.emit('send', message);
      }
  };
  
  Connection.prototype.handle_message = function(message) {
      var that = this;
      this.emit('handle_message', message);
      switch(this._connection_data.message_state) {
          case 'header':
              if (message.type !== 'binary'){
                  this.emit('message_invalid', message);
                  break;
              }
              this._connection_data.last_header = new MessageHeader(message.binaryData);
              this._connection_data.message_state = this._connection_data.last_header.next_state();
              if (this._connection_data.last_header.identifier == 6){
                  that._keepalive_time = new Date();
                  this.emit('keepalive', new Date().getTime() - this._keepalive_time.getTime());
              }
              this.emit('message_header', this._connection_data.last_header);
              break;
          case 'text':
              if (message.type === 'binary'){
                  this.emit('message_invalid', message);
                  break;
              }
              this.emit('message_text', new MessageText(message.utf8Data));
              this._connection_data.message_state = 'header';
              break;
          case 'binary_file':
              this.emit('message_file', new MessageFile(message, that._connection_data.last_request));
              this._connection_data.message_state = 'header';
              break;
          case 'etable_values':
              if (message.type !== 'binary'){
                  this.emit('message_invalid', message);
                  break;
              }
              var event_table = [];
              var buffer_index = 0;
              while (buffer_index < message.binaryData.length) {
                  var event_item = new MessageEventValue(message.binaryData, buffer_index);
                  event_table.push(event_item);
                  buffer_index+=event_item.data_length;
              }
              this.emit('message_event_table_values', event_table);
              this._connection_data.message_state = 'header';
              break;
          case 'etable_text':
              if (message.type !== 'binary'){
                  this.emit('message_invalid', message);
                  break;
              }
              var event_table = [];
              var buffer_index = 0;
              while (buffer_index < message.binaryData.length) {
                  var event_item = new MessageEventText(message.binaryData, buffer_index);
                  event_table.push(event_item);
                  buffer_index+=event_item.data_length;
              }
              this.emit('message_event_table_text', event_table);
              this._connection_data.message_state = 'header';
              break;
          case 'etable_daytimer':
              if (message.type !== 'binary'){
                  this.emit('message_invalid', message);
                  break;
              }
              var event_table = [];
              var buffer_index = 0;
              while (buffer_index < message.binaryData.length) {
                  var event_item = new MessageEventDaytimer(message.binaryData, buffer_index);
                  event_table.push(event_item);
                  buffer_index+=event_item.data_length;
              }
              this.emit('message_event_table_daytimer', event_table);
              this._connection_data.message_state = 'header';
              break;
          case 'etable_weather':
              if (message.type !== 'binary'){
                  this.emit('message_invalid', message);
                  break;
              }
              var event_table = [];
              var buffer_index = 0;
              while (buffer_index < message.binaryData.length) {
                  var event_item = new MessageEventWeather(message.binaryData, buffer_index);
                  event_table.push(event_item);
                  buffer_index+=event_item.data_length;
              }
              this.emit('message_event_table_weather', event_table);
              this._connection_data.message_state = 'header';
              break;
          default:
              this.emit('unknown_message_state', message);
              this._connection_data.message_state = 'header';
      }
  };
  
  module.exports = Connection;
  