/*
 * 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.
 */

import {ParagraphStatus} from '../notebook/paragraph/paragraph.status';

angular.module('zeppelinWebApp').controller('InterpreterCtrl', InterpreterCtrl);

function InterpreterCtrl($rootScope, $scope, $http, baseUrlSrv, ngToast, $timeout, $route) {
  'ngInject';

  let interpreterSettingsTmp = [];
  $scope.interpreterSettings = [];
  $scope.availableInterpreters = {};
  $scope.showAddNewSetting = false;
  $scope.showRepositoryInfo = false;
  $scope.searchInterpreter = '';
  $scope._ = _;
  $scope.interpreterPropertyTypes = [];
  ngToast.dismiss();

  $scope.openPermissions = function() {
    $scope.showInterpreterAuth = true;
  };

  $scope.closePermissions = function() {
    $scope.showInterpreterAuth = false;
  };

  let getSelectJson = function() {
    let selectJson = {
      tags: true,
      minimumInputLength: 3,
      multiple: true,
      tokenSeparators: [',', ' '],
      ajax: {
        url: function(params) {
          if (!params.term) {
            return false;
          }
          return baseUrlSrv.getRestApiBase() + '/security/userlist/' + params.term;
        },
        delay: 250,
        processResults: function(data, params) {
          let results = [];

          if (data.body.users.length !== 0) {
            let users = [];
            for (let len = 0; len < data.body.users.length; len++) {
              users.push({
                'id': data.body.users[len],
                'text': data.body.users[len],
              });
            }
            results.push({
              'text': 'Users :',
              'children': users,
            });
          }
          if (data.body.roles.length !== 0) {
            let roles = [];
            for (let len = 0; len < data.body.roles.length; len++) {
              roles.push({
                'id': data.body.roles[len],
                'text': data.body.roles[len],
              });
            }
            results.push({
              'text': 'Roles :',
              'children': roles,
            });
          }
          return {
            results: results,
            pagination: {
              more: false,
            },
          };
        },
        cache: false,
      },
    };
    return selectJson;
  };

  $scope.togglePermissions = function(intpName) {
    angular.element('#' + intpName + 'Owners').select2(getSelectJson());
    if ($scope.showInterpreterAuth) {
      $scope.closePermissions();
    } else {
      $scope.openPermissions();
    }
  };

  $scope.$on('ngRenderFinished', function(event, data) {
    for (let setting = 0; setting < $scope.interpreterSettings.length; setting++) {
      angular.element('#' + $scope.interpreterSettings[setting].name + 'Owners').select2(getSelectJson());
    }
  });

  let getInterpreterSettings = function() {
    $http.get(baseUrlSrv.getRestApiBase() + '/interpreter/setting')
      .then(function(res) {
        $scope.interpreterSettings = res.data.body;
        checkDownloadingDependencies();
      }).catch(function(res) {
        if (res.status === 401) {
          ngToast.danger({
            content: 'You don\'t have permission on this page',
            verticalPosition: 'bottom',
            timeout: '3000',
          });
          setTimeout(function() {
            window.location = baseUrlSrv.getBase();
          }, 3000);
        }
        console.log('Error %o %o', res.status, res.data ? res.data.message : '');
      });
  };

  const checkDownloadingDependencies = function() {
    let isDownloading = false;
    for (let index = 0; index < $scope.interpreterSettings.length; index++) {
      let setting = $scope.interpreterSettings[index];
      if (setting.status === 'DOWNLOADING_DEPENDENCIES') {
        isDownloading = true;
      }

      if (setting.status === ParagraphStatus.ERROR || setting.errorReason) {
        ngToast.danger({content: 'Error setting properties for interpreter \'' +
        setting.group + '.' + setting.name + '\': ' + setting.errorReason,
          verticalPosition: 'top',
          dismissOnTimeout: false,
        });
      }
    }

    if (isDownloading) {
      $timeout(function() {
        if ($route.current.$$route.originalPath === '/interpreter') {
          getInterpreterSettings();
        }
      }, 2000);
    }
  };

  let getAvailableInterpreters = function() {
    $http.get(baseUrlSrv.getRestApiBase() + '/interpreter').then(function(res) {
      $scope.availableInterpreters = res.data.body;
    }).catch(function(res) {
      console.log('Error %o %o', res.status, res.data ? res.data.message : '');
    });
  };

  let getAvailableInterpreterPropertyWidgets = function() {
    $http.get(baseUrlSrv.getRestApiBase() + '/interpreter/property/types')
      .then(function(res) {
        $scope.interpreterPropertyTypes = res.data.body;
      }).catch(function(res) {
        console.log('Error %o %o', res.status, res.data ? res.data.message : '');
      });
  };

  let emptyNewProperty = function(object) {
    angular.extend(object, {propertyValue: '', propertyKey: '', propertyType: $scope.interpreterPropertyTypes[0]});
  };

  let emptyNewDependency = function(object) {
    angular.extend(object, {depArtifact: '', depExclude: ''});
  };

  let removeTMPSettings = function(index) {
    interpreterSettingsTmp.splice(index, 1);
  };

  $scope.copyOriginInterpreterSettingProperties = function(settingId) {
    let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
    interpreterSettingsTmp[index] = angular.copy($scope.interpreterSettings[index]);
  };

  $scope.setPerNoteOption = function(settingId, sessionOption) {
    let option;
    if (settingId === undefined) {
      option = $scope.newInterpreterSetting.option;
    } else {
      let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
      let setting = $scope.interpreterSettings[index];
      option = setting.option;
    }

    if (sessionOption === 'isolated') {
      option.perNote = sessionOption;
      option.session = false;
      option.process = true;
    } else if (sessionOption === 'scoped') {
      option.perNote = sessionOption;
      option.session = true;
      option.process = false;
    } else {
      option.perNote = 'shared';
      option.session = false;
      option.process = false;
    }
  };

  $scope.defaultValueByType = function(setting) {
    if (setting.propertyType === 'checkbox') {
      setting.propertyValue = false;
      return;
    }

    setting.propertyValue = '';
  };

  $scope.setPerUserOption = function(settingId, sessionOption) {
    let option;
    if (settingId === undefined) {
      option = $scope.newInterpreterSetting.option;
    } else {
      let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
      let setting = $scope.interpreterSettings[index];
      option = setting.option;
    }

    if (sessionOption === 'isolated') {
      option.perUser = sessionOption;
      option.session = false;
      option.process = true;
    } else if (sessionOption === 'scoped') {
      option.perUser = sessionOption;
      option.session = true;
      option.process = false;
    } else {
      option.perUser = 'shared';
      option.session = false;
      option.process = false;
    }
  };

  $scope.getPerNoteOption = function(settingId) {
    let option;
    if (settingId === undefined) {
      option = $scope.newInterpreterSetting.option;
    } else {
      let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
      let setting = $scope.interpreterSettings[index];
      option = setting.option;
    }

    if (option.perNote === 'scoped') {
      return 'scoped';
    } else if (option.perNote === 'isolated') {
      return 'isolated';
    } else {
      return 'shared';
    }
  };

  $scope.getPerUserOption = function(settingId) {
    let option;
    if (settingId === undefined) {
      option = $scope.newInterpreterSetting.option;
    } else {
      let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
      let setting = $scope.interpreterSettings[index];
      option = setting.option;
    }

    if (option.perUser === 'scoped') {
      return 'scoped';
    } else if (option.perUser === 'isolated') {
      return 'isolated';
    } else {
      return 'shared';
    }
  };

  $scope.getInterpreterRunningOption = function(settingId) {
    let sharedModeName = 'shared';

    let globallyModeName = 'Globally';
    let perNoteModeName = 'Per Note';
    let perUserModeName = 'Per User';

    let option;
    if (settingId === undefined) {
      option = $scope.newInterpreterSetting.option;
    } else {
      let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
      let setting = $scope.interpreterSettings[index];
      option = setting.option;
    }

    let perNote = option.perNote;
    let perUser = option.perUser;

    // Globally == shared_perNote + shared_perUser
    if (perNote === sharedModeName && perUser === sharedModeName) {
      return globallyModeName;
    }

    if ($rootScope.ticket.ticket === 'anonymous' && $rootScope.ticket.roles === '[]') {
      if (perNote !== undefined && typeof perNote === 'string' && perNote !== '') {
        return perNoteModeName;
      }
    } else if ($rootScope.ticket.ticket !== 'anonymous') {
      if (perNote !== undefined && typeof perNote === 'string' && perNote !== '') {
        if (perUser !== undefined && typeof perUser === 'string' && perUser !== '') {
          return perUserModeName;
        }
        return perNoteModeName;
      }
    }

    option.perNote = sharedModeName;
    option.perUser = sharedModeName;
    return globallyModeName;
  };

  $scope.setInterpreterRunningOption = function(settingId, isPerNoteMode, isPerUserMode) {
    let option;
    if (settingId === undefined) {
      option = $scope.newInterpreterSetting.option;
    } else {
      let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
      let setting = $scope.interpreterSettings[index];
      option = setting.option;
    }
    option.perNote = isPerNoteMode;
    option.perUser = isPerUserMode;
  };

  $scope.updateInterpreterSetting = function(form, settingId) {
    const thisConfirm = BootstrapDialog.confirm({
      closable: false,
      closeByBackdrop: false,
      closeByKeyboard: false,
      title: '',
      message: 'Do you want to update this interpreter and restart with new settings?',
      callback: function(result) {
        if (result) {
          let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
          let setting = $scope.interpreterSettings[index];
          if (setting.propertyKey !== '' || setting.propertyKey) {
            $scope.addNewInterpreterProperty(settingId);
          }
          if (setting.depArtifact !== '' || setting.depArtifact) {
            $scope.addNewInterpreterDependency(settingId);
          }
          // add missing field of option
          if (!setting.option) {
            setting.option = {};
          }
          if (setting.option.isExistingProcess === undefined) {
            setting.option.isExistingProcess = false;
          }
          if (setting.option.setPermission === undefined) {
            setting.option.setPermission = false;
          }
          if (setting.option.isUserImpersonate === undefined) {
            setting.option.isUserImpersonate = false;
          }
          if (!($scope.getInterpreterRunningOption(settingId) === 'Per User' &&
            $scope.getPerUserOption(settingId) === 'isolated')) {
            setting.option.isUserImpersonate = false;
          }
          if (setting.option.remote === undefined) {
            // remote always true for now
            setting.option.remote = true;
          }
          setting.option.owners = angular.element('#' + setting.name + 'Owners').val();
          for (let i = 0; i < setting.option.owners.length; i++) {
            setting.option.owners[i] = setting.option.owners[i].trim();
          }

          let request = {
            option: angular.copy(setting.option),
            properties: angular.copy(setting.properties),
            dependencies: angular.copy(setting.dependencies),
          };

          thisConfirm.$modalFooter.find('button').addClass('disabled');
          thisConfirm.$modalFooter.find('button:contains("OK")')
            .html('<i class="fa fa-circle-o-notch fa-spin"></i> Saving Setting');

          $http.put(baseUrlSrv.getRestApiBase() + '/interpreter/setting/' + settingId, request)
            .then(function(res) {
              $scope.interpreterSettings[index] = res.data.body;
              removeTMPSettings(index);
              checkDownloadingDependencies();
              thisConfirm.close();
            })
            .catch(function(res) {
              const message = res.data ? res.data.message : 'Could not connect to server.';
              console.log('Error %o %o', res.status, message);
              ngToast.danger({content: message, verticalPosition: 'bottom'});
              form.$show();
              thisConfirm.close();
            });
          return false;
        } else {
          form.$show();
        }
      },
    });
  };

  $scope.resetInterpreterSetting = function(settingId) {
    let index = _.findIndex($scope.interpreterSettings, {'id': settingId});

    // Set the old settings back
    $scope.interpreterSettings[index] = angular.copy(interpreterSettingsTmp[index]);
    removeTMPSettings(index);
  };

  $scope.removeInterpreterSetting = function(settingId) {
    BootstrapDialog.confirm({
      closable: true,
      title: '',
      message: 'Do you want to delete this interpreter setting?',
      callback: function(result) {
        if (result) {
          $http.delete(baseUrlSrv.getRestApiBase() + '/interpreter/setting/' + settingId)
            .then(function(res) {
              let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
              $scope.interpreterSettings.splice(index, 1);
            }).catch(function(res) {
              console.log('Error %o %o', res.status, res.data ? res.data.message : '');
            });
        }
      },
    });
  };

  $scope.newInterpreterGroupChange = function() {
    let el = _.pluck(_.filter($scope.availableInterpreters, {'name': $scope.newInterpreterSetting.group}),
      'properties');
    let properties = {};
    for (let i = 0; i < el.length; i++) {
      let intpInfo = el[i];
      for (let key in intpInfo) {
        if (intpInfo.hasOwnProperty(key)) {
          properties[key] = {
            value: intpInfo[key].defaultValue,
            description: intpInfo[key].description,
            type: intpInfo[key].type,
          };
        }
      }
    }
    $scope.newInterpreterSetting.properties = properties;
  };

  $scope.restartInterpreterSetting = function(settingId) {
    BootstrapDialog.confirm({
      closable: true,
      title: '',
      message: 'Do you want to restart this interpreter?',
      callback: function(result) {
        if (result) {
          $http.put(baseUrlSrv.getRestApiBase() + '/interpreter/setting/restart/' + settingId)
            .then(function(res) {
              let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
              $scope.interpreterSettings[index] = res.data.body;
              ngToast.info('Interpreter stopped. Will be lazily started on next run.');
            }).catch(function(res) {
              let errorMsg = (res.data !== null) ? res.data.message : 'Could not connect to server.';
              console.log('Error %o %o', res.status, errorMsg);
              ngToast.danger(errorMsg);
            });
        }
      },
    });
  };

  $scope.addNewInterpreterSetting = function() {
    // user input validation on interpreter creation
    if (!$scope.newInterpreterSetting.name ||
        !$scope.newInterpreterSetting.name.trim() ||
        !$scope.newInterpreterSetting.name.match(/^[-_a-zA-Z0-9]+$/g)) {
      BootstrapDialog.alert({
        closable: true,
        title: 'Add interpreter',
        message: 'Interpreter name shouldn\'t be empty, and can consist only of: -_a-zA-Z0-9',
      });
      return;
    }

    if (!$scope.newInterpreterSetting.group) {
      BootstrapDialog.alert({
        closable: true,
        title: 'Add interpreter',
        message: 'Please choose an interpreter group',
      });
      return;
    }

    if (_.findIndex($scope.interpreterSettings, {'name': $scope.newInterpreterSetting.name}) >= 0) {
      BootstrapDialog.alert({
        closable: true,
        title: 'Add interpreter',
        message: 'Name ' + _.escape($scope.newInterpreterSetting.name) + ' already exists',
      });
      return;
    }

    let newSetting = $scope.newInterpreterSetting;
    if (newSetting.propertyKey !== '' || newSetting.propertyKey) {
      $scope.addNewInterpreterProperty();
    }
    if (newSetting.depArtifact !== '' || newSetting.depArtifact) {
      $scope.addNewInterpreterDependency();
    }
    if (newSetting.option.setPermission === undefined) {
      newSetting.option.setPermission = false;
    }
    newSetting.option.owners = angular.element('#newInterpreterOwners').val();

    let request = angular.copy($scope.newInterpreterSetting);

    // Change properties to proper request format
    let newProperties = {};

    for (let p in newSetting.properties) {
      if (newSetting.properties.hasOwnProperty(p)) {
        newProperties[p] = {
          value: newSetting.properties[p].value,
          type: newSetting.properties[p].type,
          description: newSetting.properties[p].description,
          name: p,
        };
      }
    }

    request.properties = newProperties;

    $http.post(baseUrlSrv.getRestApiBase() + '/interpreter/setting', request)
      .then(function(res) {
        $scope.resetNewInterpreterSetting();
        getInterpreterSettings();
        $scope.showAddNewSetting = false;
        checkDownloadingDependencies();
      }).catch(function(res) {
        const errorMsg = res.data ? res.data.message : 'Could not connect to server.';
        console.log('Error %o %o', res.status, errorMsg);
        ngToast.danger({content: errorMsg, verticalPosition: 'bottom'});
      });
  };

  $scope.cancelInterpreterSetting = function() {
    $scope.showAddNewSetting = false;
    $scope.resetNewInterpreterSetting();
  };

  $scope.resetNewInterpreterSetting = function() {
    $scope.newInterpreterSetting = {
      name: undefined,
      group: undefined,
      properties: {},
      dependencies: [],
      option: {
        remote: true,
        isExistingProcess: false,
        setPermission: false,
        session: false,
        process: false,

      },
    };
    emptyNewProperty($scope.newInterpreterSetting);
  };

  $scope.removeInterpreterProperty = function(key, settingId) {
    if (settingId === undefined) {
      delete $scope.newInterpreterSetting.properties[key];
    } else {
      let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
      delete $scope.interpreterSettings[index].properties[key];
    }
  };

  $scope.removeInterpreterDependency = function(artifact, settingId) {
    if (settingId === undefined) {
      $scope.newInterpreterSetting.dependencies = _.reject($scope.newInterpreterSetting.dependencies,
        function(el) {
          return el.groupArtifactVersion === artifact;
        });
    } else {
      let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
      $scope.interpreterSettings[index].dependencies = _.reject($scope.interpreterSettings[index].dependencies,
        function(el) {
          return el.groupArtifactVersion === artifact;
        });
    }
  };

  $scope.addNewInterpreterProperty = function(settingId) {
    if (settingId === undefined) {
      // Add new property from create form
      if (!$scope.newInterpreterSetting.propertyKey || $scope.newInterpreterSetting.propertyKey === '') {
        return;
      }
      $scope.newInterpreterSetting.properties[$scope.newInterpreterSetting.propertyKey] = {
        value: $scope.newInterpreterSetting.propertyValue,
        type: $scope.newInterpreterSetting.propertyType,
      };
      emptyNewProperty($scope.newInterpreterSetting);
    } else {
      // Add new property from edit form
      let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
      let setting = $scope.interpreterSettings[index];

      if (!setting.propertyKey || setting.propertyKey === '') {
        return;
      }

      setting.properties[setting.propertyKey] = {name: setting.propertyKey, value: setting.propertyValue,
        type: setting.propertyType, description: setting.description};

      emptyNewProperty(setting);
    }
  };

  $scope.addNewInterpreterDependency = function(settingId) {
    if (settingId === undefined) {
      // Add new dependency from create form
      if (!$scope.newInterpreterSetting.depArtifact || $scope.newInterpreterSetting.depArtifact === '') {
        return;
      }

      // overwrite if artifact already exists
      let newSetting = $scope.newInterpreterSetting;
      for (let d in newSetting.dependencies) {
        if (newSetting.dependencies[d].groupArtifactVersion === newSetting.depArtifact) {
          newSetting.dependencies[d] = {
            'groupArtifactVersion': newSetting.depArtifact,
            'exclusions': newSetting.depExclude,
          };
          newSetting.dependencies.splice(d, 1);
        }
      }

      newSetting.dependencies.push({
        'groupArtifactVersion': newSetting.depArtifact,
        'exclusions': (newSetting.depExclude === '') ? [] : newSetting.depExclude,
      });
      emptyNewDependency(newSetting);
    } else {
      // Add new dependency from edit form
      let index = _.findIndex($scope.interpreterSettings, {'id': settingId});
      let setting = $scope.interpreterSettings[index];
      if (!setting.depArtifact || setting.depArtifact === '') {
        return;
      }

      // overwrite if artifact already exists
      for (let dep in setting.dependencies) {
        if (setting.dependencies[dep].groupArtifactVersion === setting.depArtifact) {
          setting.dependencies[dep] = {
            'groupArtifactVersion': setting.depArtifact,
            'exclusions': setting.depExclude,
          };
          setting.dependencies.splice(dep, 1);
        }
      }

      setting.dependencies.push({
        'groupArtifactVersion': setting.depArtifact,
        'exclusions': (setting.depExclude === '') ? [] : setting.depExclude,
      });
      emptyNewDependency(setting);
    }
  };

  $scope.resetNewRepositorySetting = function() {
    $scope.newRepoSetting = {
      id: '',
      url: '',
      snapshot: false,
      username: '',
      password: '',
      proxyProtocol: 'HTTP',
      proxyHost: '',
      proxyPort: null,
      proxyLogin: '',
      proxyPassword: '',
    };
  };

  let getRepositories = function() {
    $http.get(baseUrlSrv.getRestApiBase() + '/interpreter/repository')
      .success(function(data, status, headers, config) {
        $scope.repositories = data.body;
      }).error(function(data, status, headers, config) {
        console.log('Error %o %o', status, data.message);
      });
  };

  $scope.addNewRepository = function() {
    let request = angular.copy($scope.newRepoSetting);

    $http.post(baseUrlSrv.getRestApiBase() + '/interpreter/repository', request)
      .then(function(res) {
        getRepositories();
        $scope.resetNewRepositorySetting();
        angular.element('#repoModal').modal('hide');
      }).catch(function(res) {
        console.log('Error %o %o', res.headers, res.config);
      });
  };

  $scope.removeRepository = function(repoId) {
    BootstrapDialog.confirm({
      closable: true,
      title: '',
      message: 'Do you want to delete this repository?',
      callback: function(result) {
        if (result) {
          $http.delete(baseUrlSrv.getRestApiBase() + '/interpreter/repository/' + repoId)
            .then(function(res) {
              let index = _.findIndex($scope.repositories, {'id': repoId});
              $scope.repositories.splice(index, 1);
            }).catch(function(res) {
              console.log('Error %o %o', res.status, res.data ? res.data.message : '');
            });
        }
      },
    });
  };

  $scope.isDefaultRepository = function(repoId) {
    if (repoId === 'central' || repoId === 'local') {
      return true;
    } else {
      return false;
    }
  };

  $scope.showErrorMessage = function(setting) {
    BootstrapDialog.show({
      title: 'Error downloading dependencies',
      message: _.escape(setting.errorReason),
    });
  };

  let init = function() {
    getAvailableInterpreterPropertyWidgets();

    $scope.resetNewInterpreterSetting();
    $scope.resetNewRepositorySetting();

    getInterpreterSettings();
    getAvailableInterpreters();
    getRepositories();
  };

  $scope.getInterpreterBindingModeDocsLink = function() {
    const currentVersion = $rootScope.zeppelinVersion;
    return `https://zeppelin.apache.org/docs/${currentVersion}/usage/interpreter/interpreter_binding_mode.html`;
  };

  init();
}
