# modules/Messaging.js

    import cLabs from './cLabs';

    /**
     * SSE Messaging
     * @param options {Object}
     * @constructor
     */
    export const Messaging = function (options) {
      /**
       * Messaging settings
       * @memberOf Messaging
       * @constant
       * @type { Object }
       */
      this.settings = {
        source: null,
        ajax: {
          url: null,
          apiKey: undefined,
          errorCallback: function () {
          }
        },
        sseUrl: null,
        heartbeat: null,
        lastHeartbeat: null,
        mainAjax: new cLabs.Ajax(),
        heartBeatAjax: new cLabs.Ajax(),
        heartWaitTime: 25000,
        messageQueue: [],
        messageInterval: 1000,
        startupCheck: true,
        active: false,
        debug: false,
        callback: function (data) {
        },
        onStartupError: function () {
        }
      };

      if (typeof options !== 'undefined') {
        for (var opt in options) {
          if (options.hasOwnProperty(opt)) {
            this.settings[opt] = options[opt];
          }
        }
      }

      this.intervalInstance = null;
      this.heartbeatIntervalInstance = null;

      this.lookupData = function () {
        var _this = this;

        if (_this.settings.messageQueue.length > 0) {
          var data = _this.settings.messageQueue[0];

          var index = _this.settings.messageQueue.indexOf(data);
          if (index > -1) {
            _this.settings.messageQueue.splice(index, 1);
          }

          if (typeof _this.settings.ajax.url === 'string' && _this.settings.ajax.url.length > 0) {
            _this.getData(data);
          } else {
            _this.settings.callback(data);
          }
        }
      };

      this.setInterval = function () {
        var _this = this;

        _this.intervalInstance = setInterval(function () {
          _this.lookupData();
        }, _this.settings.messageInterval);

        if (_this.settings.heartbeat !== null) {
          _this.settings.lastHeartbeat = new Date();
          _this.heartbeatIntervalInstance = setInterval(function () {
            var currentTime = new Date();
            var diff = _this.settings.lastHeartbeat.getTime() - currentTime.getTime();

            if (_this.settings.source.readyState === 0 && diff > _this.settings.heartWaitTime) {
              _this.closeChanel();
            }

            _this.hearBeatCheck();
          }, _this.settings.heartWaitTime);
        }
      };

      /**
       * Request a heartbeat
       * - if the request is failing close the connection
       * - if the request is successful but the connection is closed reopen and call for a heartbeat again
       *
       * @memberOf Messaging
       * @return {undefined}
       */
      this.hearBeatCheck = function () {
        var _this = this;

        var dataObj = {
          url: _this.settings.heartbeat,
          headers: _this.settings.ajax.apiKey,
          type: 'GET',
          success: function (response, dataObject, xhr) {
            if (xhr.status !== 200 && _this.settings.source.readyState === 0) {
              if (_this.settings.debug) console.log('[Msg] SSE Closing connection');
              _this.closeChanel();
            } else if (xhr.status === 200 && _this.settings.source.readyState === 2) {
              if (_this.settings.debug) console.log('[Msg] SSE Trying to re-open the connection');
              _this.openChanel();

              setTimeout(function () {
                _this.hearBeatCheck();
              }, 200);
            }
          }
        };

        if (typeof _this.settings.ajax.apiKey !== 'undefined') {
          dataObj.headers = _this.settings.ajax.apiKey;
        }

        _this.settings.heartBeatAjax.abort().getData(dataObj);
      };

      this.getData = function () {
        var _this = this;

        var dataObj = {
          url: _this.settings.ajax.url,
          type: 'GET',
          success: function (response, dataObject, xhr) {
            var json = {};
            try {
              json = JSON.parse(response);
            } catch (e) {
              if (_this.settings.debug) console.log(e, _this.settings);
            }
            if (xhr.status === 200) {
              _this.settings.callback(json);
            } else {
              _this.settings.ajax.errorCallback(json);
            }
          }
        };

        if (typeof _this.settings.ajax.apiKey !== 'undefined') {
          dataObj.headers = _this.settings.ajax.apiKey;
        }

        _this.settings.mainAjax.abort().getData(dataObj);
      };

      this.openChanel = function () {
        var _this = this;

        _this.settings.source = new EventSource(_this.settings.sseUrl, { withCredentials: true });

        _this.serverSideEventListeners(_this.settings.source);
      };

      this.serverSideEventListeners = function (source) {
        var _this = this;

        source.addEventListener('open', function (e) {
          _this.settings.active = true;
          if (_this.settings.debug) console.log('[Msg] connection opened', e);
        }, false);

        source.addEventListener('message', function (e) {
          if (_this.settings.debug) {
            console.log('[Msg] message check', _this.settings.sseUrl);
            console.log(e.data);
          }
          var data = e.data;
          var json = null;

          try {
            json = JSON.parse(data);
          } catch (e) {
          }

          if (_this.settings.heartbeat !== null) {
            _this.settings.lastHeartbeat = new Date();
          }

          if (json !== null && typeof json.heartbeat === 'undefined') {
            _this.settings.messageQueue.push(json);
          }
        }, false);

        source.addEventListener('error', function (e) {
          if (_this.settings.debug) {
            console.log('[Msg] error check', _this.settings.sseUrl);
          }

          /* eslint eqeqeq: "off" */
          if (e.readyState == EventSource.CLOSED) {
            if (_this.settings.debug) console.warn('[Msg] connection closed', e);
          } else {
            if (_this.settings.debug) console.log(e, e.readyState);
          }

          _this.closeChanel();

          _this.settings.startupCheck = false;
        }, false);
      };

      this.closeChanel = function () {
        this.settings.active = false;
        this.settings.source.close();
      };

      this.sseFailed = function () {
        var _this = this;

        _this.settings.heartbeat = null;
        _this.settings.active = false;

        if (_this.heartbeatIntervalInstance !== null) {
          clearInterval(_this.heartbeatIntervalInstance);
        }

        _this.settings.onStartupError(_this.settings);
      };

      this.windowActivity = function () {
        var _this = this;

        (function () {
          var hidden = 'hidden';

          // Standards:
          if (hidden in document) {
            document.addEventListener('visibilitychange', onchange);
          } else if ((hidden = 'mozHidden') in document) {
            document.addEventListener('mozvisibilitychange', onchange);
          } else if ((hidden = 'webkitHidden') in document) {
            document.addEventListener('webkitvisibilitychange', onchange);
          } else if ((hidden = 'msHidden') in document) {
            document.addEventListener('msvisibilitychange', onchange);
          } else if ('onfocusin' in document) { // IE 9 and lower:
            document.onfocusin = document.onfocusout = onchange;
          } else { // All others:
            window.onpageshow = window.onpagehide = window.onfocus = window.onblur = onchange;
          }

          function onchange (evt) {
            var status = '';
            var v = 'visible';
            var h = 'hidden';
            var evtMap = {
              focus: v, focusin: v, pageshow: v, blur: h, focusout: h, pagehide: h
            };

            evt = evt || window.event;
            if (evt.type in evtMap) {
              status = evtMap[evt.type];
            } else {
              status = this[hidden] ? 'hidden' : 'visible';
            }

            if (status === 'visible' && (_this.settings.source.readyState !== 0 && _this.settings.source.readyState !== 1)) {
              _this.openChanel();
            } else if (status === 'hidden' && (_this.settings.source.readyState === 0 || _this.settings.source.readyState === 1)) {
              _this.closeChanel();
            }
          }

          // set the initial state (but only if browser supports the Page Visibility API)
          if (document[hidden] !== undefined) {
            onchange({ type: document[hidden] ? 'blur' : 'focus' });
          }
        })();
      };

      this.init = function () {
        var _this = this;

        try {
          if (_this.settings.debug) console.log('[Msg] SSE starting', _this.settings.sseUrl, new Date());

          _this.openChanel();

          if (_this.settings.debug) console.log('[Msg] SSE started', _this.settings.sseUrl, new Date(), _this.settings.source.readyState);

          _this.setInterval();
          _this.windowActivity();

          setTimeout(function () {
            if (!_this.settings.startupCheck) {
              console.log('sse failed');
              _this.sseFailed();
            }
          }, 2000);

          window.addEventListener('unload', function (event) {
            if (_this.settings.debug) console.log('[Msg] closing messaging service', new Date());
            _this.settings.source.close();
            _this.settings.active = false;

            _this.settings.heartBeatAjax.abort();

            if (_this.intervalInstance) {
              clearInterval(_this.intervalInstance);
            }
          });
          window.addEventListener('beforeunload', function (event) {
            if (_this.settings.debug) console.log('[Msg] closing messaging service');
            _this.settings.source.close();

            _this.settings.heartBeatAjax.abort();

            if (_this.intervalInstance) {
              clearInterval(_this.intervalInstance);
            }
          });
        } catch (e) {
          if (_this.settings.debug) console.log('EventSource failed');
          _this.sseFailed();
        }
      };

      this.init();
    };