/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

angular.module('zeppelinWebApp').factory('websocketEvents', WebsocketEventFactory);

function WebsocketEventFactory($rootScope, $websocket, $location, baseUrlSrv, saveAsService, ngToast) {
  'ngInject';

  let websocketCalls = {};
  let pingIntervalId;
  const uniqueClientId = Math.random().toString(36).substring(2, 7);
  let lastMsgIdSeqSent = 0;

  websocketCalls.ws = $websocket(baseUrlSrv.getWebsocketUrl());
  websocketCalls.ws.reconnectIfNotNormalClose = true;

  websocketCalls.ws.onOpen(function() {
    console.log('Websocket created');
    $rootScope.$broadcast('setConnectedStatus', true);
    pingIntervalId = setInterval(function() {
      websocketCalls.sendNewEvent({op: 'PING'});
    }, 10000);
  });

  websocketCalls.sendNewEvent = function(data) {
    if ($rootScope.ticket !== undefined) {
      data.principal = $rootScope.ticket.principal;
      data.ticket = $rootScope.ticket.ticket;
      data.roles = $rootScope.ticket.roles;
    } else {
      data.principal = '';
      data.ticket = '';
      data.roles = '';
    }

    data.msgId = uniqueClientId + '-' + ++lastMsgIdSeqSent;
    console.log('Send >> %o, %o, %o, %o, %o', data.op, data.principal, data.ticket, data.roles, data);
    return websocketCalls.ws.send(JSON.stringify(data));
  };

  websocketCalls.isConnected = function() {
    return (websocketCalls.ws.socket.readyState === 1);
  };

  websocketCalls.ws.onMessage(function(event) {
    let payload;
    if (event.data) {
      payload = angular.fromJson(event.data);
    }

    console.log('Receive << %o, %o', payload.op, payload);

    let op = payload.op;
    let data = payload.data;
    let msgId = payload.msgId;
    const uniqueClientId = msgId ? msgId.split('-')[0] : undefined;
    const msgIdSeqReceived = msgId ? parseInt(msgId.split('-')[1]) : undefined;
    const isResponseForRequestFromThisClient = uniqueClientId === uniqueClientId;

    if (op === 'NOTE') {
      $rootScope.$broadcast('setNoteContent', data.note);
    } else if (op === 'NEW_NOTE') {
      $location.path('/notebook/' + data.note.id);
    } else if (op === 'IMPORT_NOTE') {
      $rootScope.$broadcast('noteImported', data.note);
    } else if (op === 'NOTES_INFO') {
      $rootScope.$broadcast('setNoteMenu', data.notes);
    } else if (op === 'NOTE_RUNNING_STATUS') {
      $rootScope.$broadcast('noteRunningStatus', data.status);
    } else if (op === 'LIST_NOTE_JOBS') {
      $rootScope.$emit('jobmanager:set-jobs', data.noteJobs);
    } else if (op === 'LIST_UPDATE_NOTE_JOBS') {
      $rootScope.$emit('jobmanager:update-jobs', data.noteRunningJobs);
    } else if (op === 'JOB_MANAGER_DISABLED') {
      $rootScope.$broadcast('jobManagerDisabled', data.errorMessage);
    } else if (op === 'AUTH_INFO') {
      let btn = [];
      if ($rootScope.ticket.roles === '[]') {
        btn = [{
          label: 'Close',
          action: function(dialog) {
            dialog.close();
          },
        }];
      } else {
        btn = [{
          label: 'Login',
          action: function(dialog) {
            dialog.close();
            angular.element('#loginModal').modal({
              show: 'true',
            });
          },
        }, {
          label: 'Cancel',
          action: function(dialog) {
            dialog.close();
            // using $rootScope.apply to trigger angular digest cycle
            // changing $location.path inside bootstrap modal wont trigger digest
            $rootScope.$apply(function() {
              $location.path('/');
            });
          },
        }];
      }

      BootstrapDialog.show({
        closable: false,
        closeByBackdrop: false,
        closeByKeyboard: false,
        title: 'Insufficient privileges',
        message: _.escape(data.info.toString()),
        buttons: btn,
      });
    } else if (op === 'PARAGRAPH') {
      if (isResponseForRequestFromThisClient &&
          lastMsgIdSeqSent > msgIdSeqReceived
      ) {
        // paragraph is already updated by short circuit.
        console.log('PARAPGRAPH is already updated by shortcircuit');
      } else {
        $rootScope.$broadcast('updateParagraph', data);
      }
    } else if (op === 'PATCH_PARAGRAPH') {
      $rootScope.$broadcast('patchReceived', data);
    } else if (op === 'COLLABORATIVE_MODE_STATUS') {
      $rootScope.$broadcast('collaborativeModeStatus', data);
    } else if (op === 'RUN_PARAGRAPH_USING_SPELL') {
      $rootScope.$broadcast('runParagraphUsingSpell', data);
    } else if (op === 'PARAGRAPH_APPEND_OUTPUT') {
      $rootScope.$broadcast('appendParagraphOutput', data);
    } else if (op === 'PARAGRAPH_UPDATE_OUTPUT') {
      $rootScope.$broadcast('updateParagraphOutput', data);
    } else if (op === 'PROGRESS') {
      $rootScope.$broadcast('updateProgress', data);
    } else if (op === 'COMPLETION_LIST') {
      $rootScope.$broadcast('completionList', data);
    } else if (op === 'EDITOR_SETTING') {
      $rootScope.$broadcast('editorSetting', data);
    } else if (op === 'ANGULAR_OBJECT_UPDATE') {
      $rootScope.$broadcast('angularObjectUpdate', data);
    } else if (op === 'ANGULAR_OBJECT_REMOVE') {
      $rootScope.$broadcast('angularObjectRemove', data);
    } else if (op === 'APP_APPEND_OUTPUT') {
      $rootScope.$broadcast('appendAppOutput', data);
    } else if (op === 'APP_UPDATE_OUTPUT') {
      $rootScope.$broadcast('updateAppOutput', data);
    } else if (op === 'APP_LOAD') {
      $rootScope.$broadcast('appLoad', data);
    } else if (op === 'APP_STATUS_CHANGE') {
      $rootScope.$broadcast('appStatusChange', data);
    } else if (op === 'LIST_REVISION_HISTORY') {
      $rootScope.$broadcast('listRevisionHistory', data);
    } else if (op === 'NOTE_REVISION') {
      $rootScope.$broadcast('noteRevision', data);
    } else if (op === 'NOTE_REVISION_FOR_COMPARE') {
      $rootScope.$broadcast('noteRevisionForCompare', data);
    } else if (op === 'INTERPRETER_BINDINGS') {
      $rootScope.$broadcast('interpreterBindings', data);
    } else if (op === 'SAVE_NOTE_FORMS') {
      $rootScope.$broadcast('saveNoteForms', data);
    } else if (op === 'ERROR_INFO') {
      BootstrapDialog.show({
        closable: false,
        closeByBackdrop: false,
        closeByKeyboard: false,
        title: 'Details',
        message: _.escape(data.info.toString()),
        buttons: [{
          // close all the dialogs when there are error on running all paragraphs
          label: 'Close',
          action: function() {
            BootstrapDialog.closeAll();
          },
        }],
      });
    } else if (op === 'SESSION_LOGOUT') {
      $rootScope.$broadcast('session_logout', data);
    } else if (op === 'CONFIGURATIONS_INFO') {
      $rootScope.$broadcast('configurationsInfo', data);
    } else if (op === 'INTERPRETER_SETTINGS') {
      $rootScope.$broadcast('interpreterSettings', data);
    } else if (op === 'PARAGRAPH_ADDED') {
      $rootScope.$broadcast('addParagraph', data.paragraph, data.index);
    } else if (op === 'PARAGRAPH_REMOVED') {
      $rootScope.$broadcast('removeParagraph', data.id);
    } else if (op === 'PARAGRAPH_MOVED') {
      $rootScope.$broadcast('moveParagraph', data.id, data.index);
    } else if (op === 'NOTE_UPDATED') {
      $rootScope.$broadcast('updateNote', data.name, data.config, data.info);
    } else if (op === 'SET_NOTE_REVISION') {
      $rootScope.$broadcast('setNoteRevisionResult', data);
    } else if (op === 'PARAS_INFO') {
      $rootScope.$broadcast('updateParaInfos', data);
    } else if (op === 'CONVERTED_NOTE_NBFORMAT') {
      saveAsService.saveAs(data.nbformat, data.noteName, '.ipynb');
    } else if (op === 'INTERPRETER_INSTALL_STARTED') {
      ngToast.info(data.message);
    } else if (op === 'INTERPRETER_INSTALL_RESULT') {
      ngToast.info(data.message);
    } else if (op === 'NOTICE') {
      ngToast.info(data.notice);
    } else {
      console.error(`unknown websocket op: ${op}`);
    }
  });

  websocketCalls.ws.onError(function(event) {
    console.log('error message: ', event);
    $rootScope.$broadcast('setConnectedStatus', false);
  });

  websocketCalls.ws.onClose(function(event) {
    console.log('close message: ', event);
    if (pingIntervalId !== undefined) {
      clearInterval(pingIntervalId);
      pingIntervalId = undefined;
    }
    $rootScope.$broadcast('setConnectedStatus', false);
  });

  return websocketCalls;
}
