/**
 * 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 stringUtils = require('utils/string_utils');
var timezoneUtils = require('utils/date/timezone');

/**
 * Remove spaces at beginning and ending of line.
 * @example
 *  var str = "  I'm a string  "
 *  str.trim() // return "I'm a string"
 * @method trim
 * @return {string}
 */
String.prototype.trim = function () {
  return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
};
/**
 * Determines whether string end within another string.
 *
 * @method endsWith
 * @param suffix {string}  substring for search
 * @return {boolean}
 */
String.prototype.endsWith = function(suffix) {
  return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
/**
 * Determines whether string start within another string.
 *
 * @method startsWith
 * @param prefix {string} substring for search
 * @return {boolean}
 */
String.prototype.startsWith = function (prefix){
  return this.indexOf(prefix) == 0;
};
/**
 * Determines whether string founded within another string.
 *
 * @method contains
 * @param substring {string} substring for search
 * @return {boolean}
 */
String.prototype.contains = function(substring) {
  return this.indexOf(substring) != -1;
};
/**
 * Capitalize the first letter of string.
 * @method capitalize
 * @return {string}
 */
String.prototype.capitalize = function () {
  return this.charAt(0).toUpperCase() + this.slice(1);
};

/**
 * Capitalize the first letter of string.
 * And set to lowercase other part of string
 * @method toCapital
 * @return {string}
 */
String.prototype.toCapital = function () {
  return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
};

/**
 * Finds the value in an object where this string is a key.
 * Optionally, the index of the key can be provided where the
 * value of the nth key in the hierarchy is returned.
 *
 * Example:
 *  var tofind = 'smart';
 *  var person = {'name': 'Bob Bob', 'smart': 'no', 'age': '28', 'personality': {'smart': 'yes', 'funny': 'yes', 'emotion': 'happy'} };
 *  tofind.findIn(person); // 'no'
 *  tofind.findIn(person, 0); // 'no'
 *  tofind.findIn(person, 1); // 'yes'
 *  tofind.findIn(person, 2); // null
 *
 *  @method findIn
 *  @param multi {object}
 *  @param index {number} Occurrence count of this key
 *  @return {*} Value of key at given index
 */
String.prototype.findIn = function(multi, index, _foundValues) {
  if (!index) {
    index = 0;
  }
  if (!_foundValues) {
    _foundValues = [];
  }
  multi = multi || '';
  var value = null;
  var str = this.valueOf();
  if (typeof multi == 'object') {
    for ( var key in multi) {
      if (value != null) {
        break;
      }
      if (key == str) {
        _foundValues.push(multi[key]);
      }
      if (_foundValues.length - 1 == index) {
        // Found the value
        return _foundValues[index];
      }
      if (typeof multi[key] == 'object') {
        value = value || this.findIn(multi[key], index, _foundValues);
      }
    }
  }
  return value;
};
/**
 * Replace {i} with argument. where i is number of argument to replace with.
 * @example
 *  var str = "{0} world{1}";
 *  str.format("Hello", "!") // return "Hello world!"
 *
 * @method format
 * @return {string}
 */
String.prototype.format = function () {
  var args = arguments;
  return this.replace(/{(\d+)}/g, function (match, number) {
    return typeof args[number] != 'undefined' ? args[number] : match;
  });
};
/**
 * Wrap words in string within template.
 *
 * @method highlight
 * @param {string[]} words - words to wrap
 * @param {string} [highlightTemplate="<b>{0}</b>"] - template for wrapping
 * @return {string}
 */
String.prototype.highlight = function (words, highlightTemplate) {
  var self = this;
  highlightTemplate = highlightTemplate ? highlightTemplate : "<b>{0}</b>";

  words.forEach(function (word) {
    var searchRegExp = new RegExp("\\b" + word + "\\b", "gi");
    self = self.replace(searchRegExp, function (found) {
      return highlightTemplate.format(found);
    });
  });

  return self;
};
/**
 * Convert time in milliseconds to object contained days, hours and minutes.
 * @typedef ConvertedTime
 *  @type {Object}
 *  @property {number} d - days
 *  @property {number} h - hours
 *  @property {string} m - minutes
 * @example
 *  var time = 1000000000;
 *  time.toDaysHoursMinutes() // {d: 11, h: 13, m: "46.67"}
 *
 * @method toDaysHoursMinutes
 * @return {object}
 */
Number.prototype.toDaysHoursMinutes = function () {
  var formatted = {},
    dateDiff = this,
    secK = 1000, //ms
    minK = 60 * secK, // sec
    hourK = 60 * minK, // sec
    dayK = 24 * hourK;

  dateDiff = parseInt(dateDiff);
  formatted.d = Math.floor(dateDiff / dayK);
  dateDiff -= formatted.d * dayK;
  formatted.h = Math.floor(dateDiff / hourK);
  dateDiff -= formatted.h * hourK;
  formatted.m = (dateDiff / minK).toFixed(2);

  return formatted;
};


/**
 *
 * @param bound1 {Number}
 * @param bound2 {Number}
 * @return {boolean}
 */
Number.prototype.isInRange = function (bound1, bound2) {
  var upperBound, lowerBound;
  upperBound = bound1 > bound2 ? bound1: bound2;
  lowerBound = bound1 < bound2 ? bound1: bound2;
  return this > lowerBound && this < upperBound;
};

/**
 Sort an array by the key specified in the argument.
 Handle only native js objects as element of array, not the Ember's object.

 Can be used as alternative to sortProperty method of Ember library
 in order to speed up executing on large data volumes

 @method sortBy
 @param {String} path name(s) to sort on
 @return {Array} The sorted array.
 */
Array.prototype.sortPropertyLight = function (path) {
  var realPath = (typeof path === "string") ? path.split('.') : [];
  this.sort(function (a, b) {
    var aProperty = a;
    var bProperty = b;
    realPath.forEach(function (key) {
      aProperty = aProperty[key];
      bProperty = bProperty[key];
    });
    if (aProperty > bProperty) return 1;
    if (aProperty < bProperty) return -1;
    return 0;
  });
  return this;
};

/**
 * Create map from array with executing provided callback for each array's item
 * Example:
 * <pre>
 *   var array = [{a: 1, b: 3}, {a: 2, b: 2}, {a: 3, b: 1}];
 *   var map = array.toMapByCallback('a', function (item) {
 *    return Em.get(item, 'b');
 *   });
 *   console.log(map); // {1: 3, 2: 2, 3: 1}
 * </pre>
 * <code>map[1]</code> is much more faster than <code>array.findProperty('a', 1).get('b')</code>
 *
 * @param {string} property
 * @param {Function} callback
 * @returns {object}
 * @method toMapByCallback
 */
Array.prototype.toMapByCallback = function (property, callback) {
  var ret = {};
  Em.assert('`property` can\'t be empty string', property.length);
  Em.assert('`callback` should be a function', 'function' === Em.typeOf(callback));
  this.forEach(function (item) {
    var key = Em.get(item, property);
    ret[key] = callback(item, property);
  });
  return ret;
};

/**
 * Create map from array
 * Example:
 * <pre>
 *   var array = [{a: 1}, {a: 2}, {a: 3}];
 *   var map = array.toMapByProperty('a'); // {1: {a: 1}, 2: {a: 2}, 3: {a: 3}}
 * </pre>
 * <code>map[1]</code> is much more faster than <code>array.findProperty('a', 1)</code>
 *
 * @param {string} property
 * @return {object}
 * @method toMapByProperty
 * @see toMapByCallback
 */
Array.prototype.toMapByProperty = function (property) {
  return this.toMapByCallback(property, function (item) {
    return item;
  });
};

/**
 * Create wick map from array
 * Example:
 * <pre>
 *   var array = [{a: 1}, {a: 2}, {a: 3}];
 *   var map = array.toWickMapByProperty('a'); // {1: true, 2: true, 3: true}
 * </pre>
 * <code>map[1]</code> works faster than <code>array.someProperty('a', 1)</code>
 *
 * @param {string} property
 * @return {object}
 * @method toWickMapByProperty
 * @see toMapByCallback
 */
Array.prototype.toWickMapByProperty = function (property) {
  return this.toMapByCallback(property, function () {
    return true;
  });
};

/**
 * Create wick map from array of primitives
 * Example:
 * <pre>
 *   var array = [1, 2, 3];
 *   var map = array.toWickMap(); // {1: true, 2: true, 3: true}
 * </pre>
 * <code>map[1]</code> works faster than <code>array.contains(1)</code>
 *
 * @returns {object}
 * @method toWickMap
 */
Array.prototype.toWickMap = function () {
  var ret = {};
  this.forEach(function (item) {
    ret[item] = true;
  });
  return ret;
};

if (!Array.prototype.findIndex) {
  Array.prototype.findIndex = function (predicate) {
    if (this == null) {
      throw new TypeError('"this" is null or not defined');
    }
    var o = Object(this),
      len = o.length >>> 0;
    if (typeof predicate !== 'function') {
      throw new TypeError('predicate must be a function');
    }
    var thisArg = arguments[1],
      k = 0;
    while (k < len) {
      var kValue = o[k];
      if (predicate.call(thisArg, kValue, k, o)) {
        return k;
      }
      k++;
    }
    return -1;
  };
}

/** @namespace Em **/
Em.CoreObject.reopen({
  t:function (key, attrs) {
    return Em.I18n.t(key, attrs)
  }
});

Em.TextArea.reopen(Em.I18n.TranslateableAttributes);

/** @namespace Em.Handlebars **/
Em.Handlebars.registerHelper('log', function (variable) {
  console.log(variable);
});

Em.Handlebars.registerHelper('warn', function (variable) {
  console.warn(variable);
});

Em.Handlebars.registerHelper('highlight', function (property, words, fn) {
  var context = (fn.contexts && fn.contexts[0]) || this;
  property = Em.Handlebars.getPath(context, property, fn);

  words = words.split(";");

//  if (highlightTemplate == undefined) {
  var highlightTemplate = "<b>{0}</b>";
//  }

  words.forEach(function (word) {
    var searchRegExp = new RegExp("\\b" + word + "\\b", "gi");
    property = property.replace(searchRegExp, function (found) {
      return highlightTemplate.format(found);
    });
  });

  return new Em.Handlebars.SafeString(property);
});

/**
 * Usage:
 *
 * <div {{QAAttr "someText"}}></div>
 * <div {{QAAttr "{someProperty}"}}></div>
 * <div {{QAAttr "someText-and-{someProperty}"}}></div>
 * <div {{QAAttr "{someProperty:some-text}"}}></div>
 * <div {{QAAttr "someText-and-{someProperty:some-text}"}}></div>
 * <div {{QAAttr "{someProperty:some-text:another-text}"}}></div>
 * <div {{QAAttr "someText-and-{someProperty:some-text:another-text}"}}></div>
 * <div {{QAAttr "{someProperty::another-text}"}}></div>
 * <div {{QAAttr "someText-and-{someProperty::another-text}"}}></div>
 *
 */
Em.Handlebars.registerHelper('QAAttr', function (text, options) {
  const textToReplace = text.match(/\{(.*?)\}/g);
  let attributes;
  if (textToReplace) {
    const id = ++Em.$.uuid,
      expressions = textToReplace.map((str) => {
        const parsed = Em.View._parsePropertyPath(str.slice(1, str.length - 1)),
          normalized = Ember.Handlebars.normalizePath(this, parsed.path, options.data),
          {classNames, className, falsyClassName} = parsed,
          {root, path} = normalized;
        return {src: str, classNames, className, falsyClassName, root, path};
      }),
      observer = () => {
        let dataQA = text;
        for (let i = expressions.length; i--;) {
          const el = Em.tryInvoke(options.data.view, '$', [`[${attributes}]`]);
          let e = expressions[i];
          if (!el || el.length === 0) {
            Em.removeObserver(e.root, e.path, invoker);
            break;
          }
          let value,
            sourceValue = Em.Handlebars.getPath(e.root, e.path, options.data);
          if (e.classNames) {
            value = sourceValue ? e.className : e.falsyClassName;
          } else {
            value = sourceValue;
          }
          if (Em.isNone(value)) {
            value = '';
          }
          dataQA = dataQA.replace(e.src, value);
          el.attr('data-qa', dataQA);
        }
      },
      invoker = () => Em.run.once(observer);
    attributes = `data-qa-bind-id="${id}"`;
    expressions.forEach((e) => {
      Em.addObserver(e.root, e.path, invoker);
    });
    Em.run.next(observer);
  } else {
    attributes = `data-qa="${text}"`;
  }
  return new Em.Handlebars.SafeString(attributes);
});

/**
 * Usage:
 *
 * <pre>
 *   {{#isAuthorized "SERVICE.TOGGLE_ALERTS"}}
 *     {{! some truly code }}
 *   {{else}}
 *     {{! some falsy code }}
 *   {{/isAuthorized}}
 * </pre>
 */
Em.Handlebars.registerHelper('isAuthorized', function (property, options) {
  var permission = Ember.Object.create({
    isAuthorized: function() {
      return App.isAuthorized(property);
    }.property('App.router.wizardWatcherController.isWizardRunning')
  });

  // wipe out contexts so boundIf uses `this` (the permission) as the context
  options.contexts = null;
  return Ember.Handlebars.helpers.boundIf.call(permission, "isAuthorized", options);
});

/**
 * Usage:
 *
 * <pre>
 *   {{#havePermissions "SERVICE.TOGGLE_ALERTS"}}
 *     {{! some truly code }}
 *   {{else}}
 *     {{! some falsy code }}
 *   {{/havePermissions}}
 * </pre>
 */
Em.Handlebars.registerHelper('havePermissions', function (property, options) {
  var permission = Ember.Object.create({
    havePermissions: function() {
      return App.havePermissions(property);
    }.property()
  });

  // wipe out contexts so boundIf uses `this` (the permission) as the context
  options.contexts = null;
  return Ember.Handlebars.helpers.boundIf.call(permission, "havePermissions", options);
});

/**
 * @namespace App
 */
App = require('app');

/**
 * Certain variables can have JSON in string
 * format, or in JSON format itself.
 *
 * @memberof App
 * @function parseJson
 * @param {string|object}
 * @return {object}
 */
App.parseJSON = function (value) {
  if (typeof value == "string") {
    return jQuery.parseJSON(value);
  }
  return value;
};
/**
 * Check for empty <code>Object</code>, built in Em.isEmpty()
 * doesn't support <code>Object</code> type
 *
 * @memberof App
 * @method isEmptyObject
 * @param obj {Object}
 * @return {Boolean}
 */
App.isEmptyObject = function(obj) {
  var empty = true;
  for (var prop in obj) { if (obj.hasOwnProperty(prop)) {empty = false; break;} }
  return empty;
};

/**
 * Convert object under_score keys to camelCase
 *
 * @param {Object} object
 * @return {Object}
 **/
App.keysUnderscoreToCamelCase = function(object) {
  var tmp = {};
  for (var key in object) {
    tmp[stringUtils.underScoreToCamelCase(key)] = object[key];
  }
  return tmp;
};

/**
 * Convert dotted keys to camelcase
 *
 * @param {Object} object
 * @return {Object}
 **/
App.keysDottedToCamelCase = function(object) {
  var tmp = {};
  for (var key in object) {
    tmp[key.split('.').reduce(function(p, c) { return p + c.capitalize()})] = object[key];
  }
  return tmp;
};
/**
 * Returns object with defined keys only.
 *
 * @memberof App
 * @method permit
 * @param {Object} obj - input object
 * @param {String|Array} keys - allowed keys
 * @return {Object}
 */
App.permit = function(obj, keys) {
  var result = {};
  if (typeof obj !== 'object' || App.isEmptyObject(obj)) return result;
  if (typeof keys == 'string') keys = Array(keys);
  keys.forEach(function(key) {
    if (obj.hasOwnProperty(key))
      result[key] = obj[key];
  });
  return result;
};
/**
 *
 * @namespace App
 * @namespace App.format
 */
App.format = {
  /**
   * @memberof App.format
   * @type {object}
   * @property components
   */
  components: {
    'API': 'API',
    'DECOMMISSION_DATANODE': 'Update Exclude File',
    'DRPC': 'DRPC',
    'FLUME_HANDLER': 'Flume',
    'GLUSTERFS': 'GLUSTERFS',
    'HBASE': 'HBase',
    'HBASE_REGIONSERVER': 'RegionServer',
    'HCAT': 'HCat Client',
    'HDFS': 'HDFS',
    'HISTORYSERVER': 'History Server',
    'HIVE_SERVER': 'HiveServer2',
    'JCE': 'JCE',
    'MAPREDUCE2': 'MapReduce2',
    'MYSQL': 'MySQL',
    'REST': 'REST',
    'SECONDARY_NAMENODE': 'SNameNode',
    'STORM_REST_API': 'Storm REST API Server',
    'WEBHCAT': 'WebHCat',
    'YARN': 'YARN',
    'UI': 'UI',
    'ZKFC': 'ZKFailoverController',
    'ZOOKEEPER': 'ZooKeeper',
    'ZOOKEEPER_QUORUM_SERVICE_CHECK': 'ZK Quorum Service Check',
    'HAWQ': 'HAWQ',
    'PXF': 'PXF'
  },

  /**
   * @memberof App.format
   * @property command
   * @type {object}
   */
  command: {
    'INSTALL': 'Install',
    'UNINSTALL': 'Uninstall',
    'START': 'Start',
    'STOP': 'Stop',
    'EXECUTE': 'Execute',
    'ABORT': 'Abort',
    'UPGRADE': 'Upgrade',
    'RESTART': 'Restart',
    'SERVICE_CHECK': 'Check',
    'SET_KEYTAB': 'Set Keytab:',
    'Excluded:': 'Decommission:',
    'Included:': 'Recommission:'
  },

  /**
   * cached map of service names
   * @type {object}
   */
  stackServiceRolesMap: {},

  /**
   * cached map of component names
   * @type {object}
   */
  stackComponentRolesMap: {},

  /**
   * convert role to readable string
   *
   * @memberof App.format
   * @method role
   * @param {string} role
   * @param {boolean} isServiceRole
   * return {string}
   */
  role: function (role, isServiceRole) {

    if (isServiceRole) {
      var model = App.StackService;
      var map = this.stackServiceRolesMap;
    } else {
      var model = App.StackServiceComponent;
      var map = this.stackComponentRolesMap;
    }

    this.initializeStackRolesMap(map, model);

    if (map[role]) {
      return map[role];
    }
    return this.normalizeName(role);
  },

  initializeStackRolesMap: function (map, model) {
    if (App.isEmptyObject(map)) {
      model.find().forEach(function (item) {
        map[item.get('id')] = item.get('displayName');
      });
    }
  },

  /**
   * Try to format non predefined names to readable format.
   *
   * @method normalizeNameBySeparator
   * @param name {String} - name to format
   * @param separators {String} - token use to split the string
   * @return {String}
   */
  normalizeNameBySeparators: function(name, separators) {
    if (!name || typeof name != 'string') return '';
    name = name.toLowerCase();
    if (!separators || separators.length == 0) {
      console.debug("No separators specified. Use default separator '_' instead");
      separators = ["_"];
    }

    for (var i = 0; i < separators.length; i++){
      var separator = separators[i];
      if (new RegExp(separator, 'g').test(name)) {
        name = name.split(separator).map(function(singleName) {
          return this.normalizeName(singleName.toUpperCase());
        }, this).join(' ');
        break;
      }
    }
    return name.capitalize();
  },


  /**
   * Try to format non predefined names to readable format.
   *
   * @method normalizeName
   * @param name {String} - name to format
   * @return {String}
   */
  normalizeName: function(name) {
    if (!name || typeof name != 'string') return '';
    if (this.components[name]) return this.components[name];
    name = name.toLowerCase();
    var suffixNoSpaces = ['node','tracker','manager'];
    var suffixRegExp = new RegExp('(\\w+)(' + suffixNoSpaces.join('|') + ')', 'gi');
    if (/_/g.test(name)) {
      name = name.split('_').map(function(singleName) {
        return this.normalizeName(singleName.toUpperCase());
      }, this).join(' ');
    } else if(suffixRegExp.test(name)) {
      suffixRegExp.lastIndex = 0;
      var matches = suffixRegExp.exec(name);
      name = matches[1].capitalize() + matches[2].capitalize();
    }
    return name.capitalize();
  },

  /**
   * convert command_detail to readable string, show the string for all tasks name
   *
   * @memberof App.format
   * @method commandDetail
   * @param {string} command_detail
   * @param {string} request_inputs
   * @return {string}
   */
  commandDetail: function (command_detail, request_inputs, ops_display_name) {
    var detailArr = command_detail.split(' ');
    var self = this;
    var result = '';
    var isIncludeExcludeFiles = false;
    //if an optional operation display name has been specified in the service metainfo.xml
    if (ops_display_name != null && ops_display_name.length > 0) {
      result = result + ' ' + ops_display_name;
    } else {
    detailArr.forEach( function(item) {
      // if the item has the pattern SERVICE/COMPONENT, drop the SERVICE part
      if (item.contains('/') && !isIncludeExcludeFiles) {
        item = item.split('/')[1];
      }
      if (item === 'DECOMMISSION,') {
        // ignore text 'DECOMMISSION,'( command came from 'excluded/included'), here get the component name from request_inputs
        var parsedInputs = jQuery.parseJSON(request_inputs);
        item = (parsedInputs) ? (parsedInputs.slave_type || '') : '';
        isIncludeExcludeFiles = (parsedInputs) ? parsedInputs.is_add_or_delete_slave_request === 'true' : false;
      }
      if (self.components[item]) {
        result = result + ' ' + self.components[item];
      } else if (self.command[item]) {
        result = result + ' ' + self.command[item];
      } else if (isIncludeExcludeFiles) {
        result = result + ' ' + item;
      } else {
        result = result + ' ' + self.role(item, false);
      }
    });
    }

    if (result.indexOf('Decommission:') > -1 || result.indexOf('Recommission:') > -1) {
      // for Decommission command, make sure the hostname is in lower case
       result = result.split(':')[0] + ': ' + result.split(':')[1].toLowerCase();
    }
    //TODO check if UI use this
    if (result === ' Nagios Update Ignore Actionexecute') {
       result = Em.I18n.t('common.maintenance.task');
    }
    if (result.indexOf('Install Packages Actionexecute') != -1) {
      result = Em.I18n.t('common.installRepo.task');
    }
    if (result === ' Rebalancehdfs NameNode') {
       result = Em.I18n.t('services.service.actions.run.rebalanceHdfsNodes.title');
    }
    if (result === " Startdemoldap Knox Gateway") {
      result = Em.I18n.t('services.service.actions.run.startLdapKnox.title');
    }
    if (result === " Stopdemoldap Knox Gateway") {
      result = Em.I18n.t('services.service.actions.run.stopLdapKnox.title');
    }
    if (result === ' Refreshqueues ResourceManager') {
      result = Em.I18n.t('services.service.actions.run.yarnRefreshQueues.title');
    }
 // HAWQ custom commands on back Ops page.
    if (result === ' Resync Hawq Standby HAWQ Standby Master') {
      result = Em.I18n.t('services.service.actions.run.resyncHawqStandby.label');
    }
    if (result === ' Immediate Stop Hawq Service HAWQ Master') {
      result = Em.I18n.t('services.service.actions.run.immediateStopHawqService.label');
    }
    if (result === ' Immediate Stop Hawq Segment HAWQ Segment') {
      result = Em.I18n.t('services.service.actions.run.immediateStopHawqSegment.label');
    }
    if(result === ' Activate Hawq Standby HAWQ Standby Master') {
      result = Em.I18n.t('admin.activateHawqStandby.button.enable');
    }
    if(result === ' Hawq Clear Cache HAWQ Master') {
      result = Em.I18n.t('services.service.actions.run.clearHawqCache.label');
    }
    if(result === ' Run Hawq Check HAWQ Master') {
      result = Em.I18n.t('services.service.actions.run.runHawqCheck.label');
    }
    //<---End HAWQ custom commands--->
    return result;
  },

  /**
   * Convert uppercase status name to lowercase.
   * <br>
   * <br>PENDING - Not queued yet for a host
   * <br>QUEUED - Queued for a host
   * <br>IN_PROGRESS - Host reported it is working
   * <br>COMPLETED - Host reported success
   * <br>FAILED - Failed
   * <br>TIMEDOUT - Host did not respond in time
   * <br>ABORTED - Operation was abandoned
   *
   * @memberof App.format
   * @method taskStatus
   * @param {string} _taskStatus
   * @return {string}
   *
   */
  taskStatus:function (_taskStatus) {
    return _taskStatus.toLowerCase();
  },

  /**
   * simplify kdc error msg
   * @param {string} message
   * @param {boolean} strict if this flag is true ignore not defined msgs return null
   *  else return input msg as is;
   * @returns {*}
   */
  kdcErrorMsg: function(message, strict) {
    /**
     * Error messages for KDC administrator credentials error
     * is used for checking if error message is caused by bad KDC credentials
     * @type {{missingKDC: string, invalidKDC: string}}
     */
    var specialMsg = {
      "missingKDC": "Missing KDC administrator credentials.",
      "invalidKDC": "Invalid KDC administrator credentials.",
      "missingRDCForRealm": "Failed to find a KDC for the specified realm - kadmin"
    };

    for (var m in specialMsg) {
      if (specialMsg.hasOwnProperty(m) && message.contains(specialMsg[m]))
        return specialMsg[m];
    }
    return strict ? null : message;
  }
};

/**
 * wrapper to bootstrap popover
 * fix issue when popover stuck on view routing
 *
 * @memberof App
 * @method popover
 * @param {DOMElement} self
 * @param {object} options
 */
App.popover = function (self, options) {
  var opts = $.extend(true, {
    container: 'body',
    html: true
  }, options || {});
  if (!self) return;
  self.popover(opts);
  self.on("remove", function () {
    $(this).trigger('mouseleave').off().removeData('bs.popover');
  });
  self = null;
};

/**
 * wrapper to bootstrap tooltip
 * fix issue when tooltip stuck on view routing
 * @memberof App
 * @method tooltip
 * @param {DOMElement} self
 * @param {object} options
 */
App.tooltip = function (self, options) {
  var opts = $.extend(true, {
    container: 'body'
  }, options || {});
  if (!self || !self.tooltip) return;
  self.tooltip(opts);
  /* istanbul ignore next */
  self.on("remove", function () {
    $(this).trigger('mouseleave').off().removeData('bs.tooltip');
  });
  self = null
};

/**
 * wrapper to Date().getTime()
 * fix issue when client clock and server clock not sync
 *
 * @memberof App
 * @method dateTime
 * @return {Number} timeStamp of current server clock
 */
App.dateTime = function() {
  return new Date().getTime() + App.clockDistance;
};

/**
 *
 * @param {number} [x] timestamp
 * @returns {number}
 */
App.dateTimeWithTimeZone = function (x) {
  var timezone = App.router.get('userSettingsController.userSettings.timezone');
  if (timezone) {
    var tz = Em.getWithDefault(timezone, 'zones.0.value', '');
    return moment(moment.tz(x ? new Date(x) : new Date(), tz).toArray()).toDate().getTime();
  }
  return x || new Date().getTime();
};

App.formatDateTimeWithTimeZone = function (timeStamp, format) {
  var timezone = App.router.get('userSettingsController.userSettings.timezone'),
    time;
  if (timezone) {
    var tz = Em.getWithDefault(timezone, 'zones.0.value', '');
    time = moment.tz(timeStamp, tz);
  } else {
    time = moment(timeStamp);
  }
  return moment(time).format(format);
};

App.getTimeStampFromLocalTime = function (time) {
  var timezone = App.router.get('userSettingsController.userSettings.timezone'),
    offsetString = '',
    date = moment(time).format('YYYY-MM-DD HH:mm:ss');
  if (timezone) {
    var offset = timezone.utcOffset;
    offsetString = moment().utcOffset(offset).format('Z');
  }
  return moment(date + offsetString).toDate().getTime();
};

/**
 * Ambari overrides the default date transformer.
 * This is done because of the non-standard data
 * sent. For example Nagios sends date as "12345678".
 * The problem is that it is a String and is represented
 * only in seconds whereas Javascript's Date needs
 * milliseconds representation.
 */
DS.attr.transforms.date = {
  from: function (serialized) {
    var type = typeof serialized;
    if (type === Em.I18n.t('common.type.string')) {
      serialized = parseInt(serialized);
      type = typeof serialized;
    }
    if (type === Em.I18n.t('common.type.number')) {
      if (!serialized ){  //serialized timestamp = 0;
        return 0;
      }
      // The number could be seconds or milliseconds.
      // If seconds, then the length is 10
      // If milliseconds, the length is 13
      if (serialized.toString().length < 13) {
        serialized = serialized * 1000;
      }
      return new Date(serialized);
    } else if (serialized === null || serialized === undefined) {
      // if the value is not present in the data,
      // return undefined, not null.
      return serialized;
    } else {
      return null;
    }
  },
  to: function (deserialized) {
    if (deserialized instanceof Date) {
      return deserialized.getTime();
    } else if (deserialized === undefined) {
      return undefined;
    } else {
      return null;
    }
  }
};

DS.attr.transforms.object = {
  from: function(serialized) {
    return Ember.none(serialized) ? null : Object(serialized);
  },

  to: function(deserialized) {
    return Ember.none(deserialized) ? null : Object(deserialized);
  }
};

/**
 * Allows EmberData models to have array properties.
 *
 * Declare the property as <code>
 *  operations: DS.attr('array'),
 * </code> and
 * during load provide a JSON array for value.
 *
 * This transform simply assigns the same array in both directions.
 */
DS.attr.transforms.array = {
  from : function(serialized) {
    return serialized;
  },
  to : function(deserialized) {
    return deserialized;
  }
};

/**
 *  Utility method to delete all existing records of a DS.Model type from the model's associated map and
 *  store's persistence layer (recordCache)
 * @param type DS.Model Class
 */
App.resetDsStoreTypeMap = function(type) {
  var allRecords = App.get('store.recordCache');  //This fetches all records in the ember-data persistence layer
  var typeMaps = App.get('store.typeMaps');
  var guidForType = Em.guidFor(type);
  var typeMap = typeMaps[guidForType];
  if (typeMap) {
    var idToClientIdMap = typeMap.idToCid;
    for (var id in idToClientIdMap) {
      if (idToClientIdMap.hasOwnProperty(id) && idToClientIdMap[id] && allRecords[idToClientIdMap[id]] !== undefined) {
        delete allRecords[idToClientIdMap[id]];  // deletes the cached copy of the record from the store
      }
    }
    typeMaps[guidForType] = {
      idToCid: {},
      clientIds: [],
      cidToHash: {},
      recordArrays: []
    };
  }
};

/**
 * Clean store from already loaded data.
 * @param {DS.Model[]} models to clear
 **/
App.clearModels = function (models) {
  models.forEach(function (model) {
    var records = App.get('store').findAll(model).filterProperty('id');
    records.forEach(function (rec) {
      Ember.run(this, function () {
        rec.deleteRecord();
        App.store.fastCommit();
      });
    }, this);
  }, this);
};

App.logger = function() {

  var timers = {};

  return {

    maxAllowedLoadingTime: 1000,

    setTimer: function(name) {
      if (!App.get('enableLogger')) return;
      timers[name] = window.performance.now();
    },

    logTimerIfMoreThan: function(name, loadingTime) {
      if (!App.get('enableLogger')) return;
      this.maxAllowedLoadingTime = loadingTime || this.maxAllowedLoadingTime;
      if (timers[name]) {
        var diff = window.performance.now() - timers[name];
        if (diff > this.maxAllowedLoadingTime) {
          console.debug(name + ': ' + diff.toFixed(3) + 'ms');
        }
        delete timers[name];
      }
    }
  };

}();

/**
 * Replce cruuent window location.
 * `window.location.replace` is a native function and cannot be intercepted by Sinon,
 * it needs to be intercepted manually. See https://stackoverflow.com/a/52141931/14792586.
 * 
 * Simple function, no test cases available.
 * 
 * @param {string} location new location
 **/
App.replaceWindowLocation = function(location) {
  window.location.replace(location);
}