/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

var App = require('app');
var stringUtils = require('utils/string_utils');

App.ClusterController = Em.Controller.extend(App.ReloadPopupMixin, {
  name: 'clusterController',
  isLoaded: false,
  ambariProperties: null,
  clusterDataLoadedPercent: 'width:0', // 0 to 1

  isClusterNameLoaded: false,

  isAlertsLoaded: false,

  isComponentsStateLoaded: false,

  isHostsLoaded: false,

  isConfigsPropertiesLoaded: false,

  isComponentsConfigLoaded: false,

  isStackConfigsLoaded: false,

  isServiceMetricsLoaded: false,

  /**
   * Ambari uses custom jdk.
   * @type {Boolean}
   */
  isCustomJDK: false,

  isHostContentLoaded: function () {
    return this.get('isHostsLoaded') && this.get('isComponentsStateLoaded');
  }.property('isHostsLoaded', 'isComponentsStateLoaded'),

  isServiceContentFullyLoaded: function () {
    return this.get('isServiceMetricsLoaded') && this.get('isComponentsStateLoaded') && this.get('isComponentsConfigLoaded');
  }.property('isServiceMetricsLoaded', 'isComponentsStateLoaded', 'isComponentsConfigLoaded'),

  clusterName: function () {
    return App.get('clusterName');
  }.property('App.clusterName'),

  updateLoadStatus: function (item) {
    var loadList = this.get('dataLoadList');
    var loaded = true;
    var numLoaded = 0;
    var loadListLength = 0;
    loadList.set(item, true);
    for (var i in loadList) {
      if (loadList.hasOwnProperty(i)) {
        loadListLength++;
        if (!loadList[i] && loaded) {
          loaded = false;
        }
      }
      // calculate the number of true
      if (loadList.hasOwnProperty(i) && loadList[i]) {
        numLoaded++;
      }
    }
    this.set('isLoaded', loaded);
    this.set('clusterDataLoadedPercent', 'width:' + (Math.floor(numLoaded / loadListLength * 100)).toString() + '%');
  },

  dataLoadList: Em.Object.create({
    'stackComponents': false,
    'services': false
  }),

  /**
   * load cluster name
   */
  loadClusterName: function (reload, deferred) {
    var dfd = deferred || $.Deferred();

    if (App.get('clusterName') && !reload) {
      App.set('clusterName', this.get('clusterName'));
      this.set('isClusterNameLoaded', true);
      dfd.resolve();
    } else {
      App.ajax.send({
        name: 'cluster.load_cluster_name',
        sender: this,
        data: {
          reloadPopupText: Em.I18n.t('app.reloadPopup.noClusterName.text'),
          errorLogMessage: 'failed on loading cluster name',
          callback: this.loadClusterName,
          args: [reload, dfd],
          shouldUseDefaultHandler: true
        },
        success: 'reloadSuccessCallback',
        error: 'reloadErrorCallback',
        callback: function () {
          if (!App.get('currentStackVersion')) {
            App.set('currentStackVersion', App.defaultStackVersion);
          }
        }
      }).then(
        function () {
          dfd.resolve();
        },
        null
      );
    }
    return dfd.promise();
  },

  reloadSuccessCallback: function (data) {
    this._super();
    if (data.items && data.items.length > 0) {
      App.setProperties({
        clusterName: data.items[0].Clusters.cluster_name,
        currentStackVersion: data.items[0].Clusters.version,
        isKerberosEnabled: data.items[0].Clusters.security_type === 'KERBEROS'
      });
      this.set('isClusterNameLoaded', true);
    }
  },

  /**
   * load current server clock in milli-seconds
   */
  loadClientServerClockDistance: function () {
    var dfd = $.Deferred();
    this.getServerClock().done(function () {
      dfd.resolve();
    });
    return dfd.promise();
  },

  getServerClock: function () {
    return App.ajax.send({
      name: 'ambari.service',
      sender: this,
      data: {
        fields: '?fields=RootServiceComponents/server_clock'
      },
      success: 'getServerClockSuccessCallback',
      error: 'getServerClockErrorCallback'
    });
  },
  getServerClockSuccessCallback: function (data) {
    var clientClock = new Date().getTime();
    var serverClock = (data.RootServiceComponents.server_clock).toString();
    serverClock = serverClock.length < 13 ? serverClock + '000' : serverClock;
    App.set('clockDistance', serverClock - clientClock);
    App.set('currentServerTime', parseInt(serverClock));
    console.log('loading ambari server clock distance');
  },
  getServerClockErrorCallback: function () {
    console.log('Cannot load ambari server clock');
  },

  getUrl: function (testUrl, url) {
    return (App.get('testMode')) ? testUrl : App.get('apiPrefix') + '/clusters/' + App.get('clusterName') + url;
  },

  /**
   *  load all data and update load status
   */
  loadClusterData: function () {
    var self = this;
    this.getAllHostNames();
    this.loadAmbariProperties();
    if (!App.get('clusterName')) {
      return;
    }

    if (this.get('isLoaded')) { // do not load data repeatedly
      App.router.get('mainController').startPolling();
      return;
    }

    var clusterUrl = this.getUrl('/data/clusters/cluster.json', '?fields=Clusters');
    var hostsController = App.router.get('mainHostController');
    hostsController.set('isCountersUpdating', true);
    hostsController.updateStatusCounters();

    App.HttpClient.get(clusterUrl, App.clusterMapper, {
      complete: function (jqXHR, textStatus) {
      }
    }, function (jqXHR, textStatus) {
    });


    if (App.get('supports.stackUpgrade')) {
      self.restoreUpgradeState();
    }


    var updater = App.router.get('updateController');

    /**
     * Order of loading:
     * 1. load all created service components
     * 1. request for service components supported by stack
     * 2. load stack components to model
     * 3. request for services
     * 4. put services in cache
     * 5. request for hosts and host-components (single call)
     * 6. request for service metrics
     * 7. load host-components to model
     * 8. load services from cache with metrics to model
     */
    self.loadStackServiceComponents(function (data) {
      data.items.forEach(function (service) {
        service.StackServices.is_selected = true;
        service.StackServices.is_installed = false;
      }, self);
      App.stackServiceMapper.mapStackServices(data);
      App.config.setPreDefinedServiceConfigs(true);
      self.updateLoadStatus('stackComponents');
      updater.updateServices(function () {
        self.updateLoadStatus('services');

        //hosts should be loaded after services in order to properly populate host-component relation in App.cache.services
        updater.updateHost(function () {
          self.set('isHostsLoaded', true);
        });
        App.config.loadConfigsFromStack(App.Service.find().mapProperty('serviceName')).complete(function () {
          App.config.loadClusterConfigsFromStack().complete(function () {
            self.set('isConfigsPropertiesLoaded', true);
          });
        });
        // components state loading doesn't affect overall progress
        updater.updateComponentsState(function () {
          self.set('isComponentsStateLoaded', true);
          // service metrics should be loaded after components state for mapping service components to service in the DS model
          // service metrics loading doesn't affect overall progress
          updater.updateServiceMetric(function () {
            self.set('isServiceMetricsLoaded', true);
            // components config loading doesn't affect overall progress
            updater.updateComponentConfig(function () {
              self.set('isComponentsConfigLoaded', true);
            });
          });
        });
      });
    });

    //force clear filters  for hosts page to load all data
    App.db.setFilterConditions('mainHostController', null);

    // alerts loading doesn't affect overall progress
    console.time('Overall alerts loading time');
    updater.updateAlertGroups(function () {
      updater.updateAlertDefinitions(function () {
        updater.updateAlertDefinitionSummary(function () {
          updater.updateUnhealthyAlertInstances(function () {
            console.timeEnd('Overall alerts loading time');
            self.set('isAlertsLoaded', true);
          });
        });
      });
    });

    /*  Root service mapper maps all the data exposed under Ambari root service which includes ambari configurations i.e ambari-properties
     ** This is useful information but its not being used in the code anywhere as of now

     self.loadRootService().done(function (data) {
     App.rootServiceMapper.map(data);
     self.updateLoadStatus('rootService');
     });

     */
  },

  /**
   * restore upgrade status from server
   * and make call to get latest status from server
   */
  restoreUpgradeState: function () {
    return this.getAllUpgrades().done(function (data) {
      var upgradeController = App.router.get('mainAdminStackAndUpgradeController');
      var lastUpgradeData = data.items.sortProperty('Upgrade.request_id').pop();
      var dbUpgradeState = App.db.get('MainAdminStackAndUpgrade', 'upgradeState');

      if (!Em.isNone(dbUpgradeState)) {
        App.set('upgradeState', dbUpgradeState);
      }

      if (lastUpgradeData) {
        upgradeController.setDBProperties({
          upgradeId: lastUpgradeData.Upgrade.request_id,
          isDowngrade: lastUpgradeData.Upgrade.direction === 'DOWNGRADE',
          upgradeState: lastUpgradeData.Upgrade.request_status
        });
        upgradeController.loadRepoVersionsToModel().done(function () {
          upgradeController.setDBProperty('upgradeVersion', App.RepositoryVersion.find().findProperty('repositoryVersion', lastUpgradeData.Upgrade.to_version).get('displayName'));
          upgradeController.initDBProperties();
          upgradeController.loadUpgradeData(true);
        });
      } else {
        upgradeController.initDBProperties();
        upgradeController.loadUpgradeData(true);
      }
      upgradeController.loadStackVersionsToModel(true).done(function () {
        App.set('stackVersionsAvailable', App.StackVersion.find().content.length > 0);
      });
    });
  },

  loadRootService: function () {
    return App.ajax.send({
      name: 'service.ambari',
      sender: this
    });
  },

  requestHosts: function (realUrl, callback) {
    var testHostUrl = '/data/hosts/HDP2/hosts.json';
    var url = this.getUrl(testHostUrl, realUrl);
    App.HttpClient.get(url, App.hostsMapper, {
      complete: callback
    }, callback)
  },

  /**
   *
   * @param callback
   */
  loadStackServiceComponents: function (callback) {
    var callbackObj = {
      loadStackServiceComponentsSuccess: callback
    };
    App.ajax.send({
      name: 'wizard.service_components',
      data: {
        stackUrl: App.get('stackVersionURL'),
        stackVersion: App.get('currentStackVersionNumber')
      },
      sender: callbackObj,
      success: 'loadStackServiceComponentsSuccess'
    });
  },

  loadAmbariProperties: function () {
    return App.ajax.send({
      name: 'ambari.service',
      sender: this,
      success: 'loadAmbariPropertiesSuccess',
      error: 'loadAmbariPropertiesError'
    });
  },

  loadAmbariPropertiesSuccess: function (data) {
    console.log('loading ambari properties');
    this.set('ambariProperties', data.RootServiceComponents.properties);
    // Absence of 'jdk.name' and 'jce.name' properties says that ambari configured with custom jdk.
    this.set('isCustomJDK', App.isEmptyObject(App.permit(data.RootServiceComponents.properties, ['jdk.name', 'jce.name'])));
  },

  loadAmbariPropertiesError: function () {
    console.warn('can\'t get ambari properties');
  },

  updateClusterData: function () {
    var testUrl = '/data/clusters/HDP2/cluster.json';
    var clusterUrl = this.getUrl(testUrl, '?fields=Clusters');
    App.HttpClient.get(clusterUrl, App.clusterMapper, {
      complete: function () {
      }
    });
  },

  /**
   *
   * @returns {*|Transport|$.ajax|boolean|ServerResponse}
   */
  getAllHostNames: function () {
    return App.ajax.send({
      name: 'hosts.all',
      sender: this,
      success: 'getHostNamesSuccess',
      error: 'getHostNamesError'
    });
  },

  getHostNamesSuccess: function (data) {
    App.set("allHostNames", data.items.mapProperty("Hosts.host_name"));
  },

  getHostNamesError: function () {
    console.error('failed to load hostNames');
  },


  /**
   * puts kerberos admin credentials in the live cluster session
   * and resend ajax request
   * @param adminPrincipalValue
   * @param adminPasswordValue
   * @param ajaxOpt
   * @returns {$.ajax}
   */
  createKerberosAdminSession: function (adminPrincipalValue, adminPasswordValue, ajaxOpt) {
    return App.ajax.send({
      name: 'common.cluster.update',
      sender: this,
      data: {
        clusterName: App.get('clusterName'),
        data: [{
          session_attributes: {
            kerberos_admin: {principal: adminPrincipalValue, password: adminPasswordValue}
          }
        }]
      }
    }).success(function () {
      if (ajaxOpt) {
        $.ajax(ajaxOpt);
      }
    });
  },

  //TODO Replace this check with any other which is applicable to non-HDP stack
  /**
   * Check if HDP stack version is more or equal than 2.2.2 to determine if pluggable metrics for Storm are supported
   * @method checkDetailedRepoVersion
   * @returns {promise|*|promise|promise|HTMLElement|promise}
   */
  checkDetailedRepoVersion: function () {
    var dfd;
    var currentStackName = App.get('currentStackName');
    var currentStackVersionNumber = App.get('currentStackVersionNumber');
    if (currentStackName == 'HDP' && currentStackVersionNumber == '2.2') {
      dfd = App.ajax.send({
        name: 'cluster.load_detailed_repo_version',
        sender: this,
        success: 'checkDetailedRepoVersionSuccessCallback',
        error: 'checkDetailedRepoVersionErrorCallback'
      });
    } else {
      dfd = $.Deferred();
      App.set('isStormMetricsSupported', currentStackName != 'HDP' || stringUtils.compareVersions(currentStackVersionNumber, '2.2') == 1);
      dfd.resolve();
    }
    return dfd.promise();
  },

  checkDetailedRepoVersionSuccessCallback: function (data) {
    var items = data.items;
    var version;
    if (items && items.length) {
      var repoVersions = items[0].repository_versions;
      if (repoVersions && repoVersions.length) {
        version = Em.get(repoVersions[0], 'RepositoryVersions.repository_version');
      }
    }
    App.set('isStormMetricsSupported', stringUtils.compareVersions(version, '2.2.2') > -1 || !version);
  },
  checkDetailedRepoVersionErrorCallback: function () {
    App.set('isStormMetricsSupported', true);
  },

  /**
   * Load required data for all upgrades from API
   * @returns {$.ajax}
   */
  getAllUpgrades: function () {
    return App.ajax.send({
      name: 'cluster.load_last_upgrade',
      sender: this
    });
  }
});
