/**
 * 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');
const {isValidFloat} = require('utils/validator');

/**
 * Thresholds manager for sliders with single value
 * Usage:
 * <pre>
 * SingleHandler.create({
 *    thresholdMin: 12,
 *    minValue: 10,
 *    maxValue: 100
 * });
 * </pre>
 *
 * @class SingleHandler
 */
const SingleHandler = Em.Object.extend({

  /**
   * @type {number}
   */
  thresholdMin: null,

  /**
   * @type {number}
   * @default 0
   */
  minValue: 0,

  /**
   * @type {number}
   */
  maxValue: null,

  /**
   * Is <code>thresholdMin</code> invalid
   * true - invalid
   * false - valid
   *
   * @type {boolean}
   */
  thresholdMinError: Em.computed.bool('thresholdMinErrorMessage'),

  /**
   * Alias for <code>thresholdMinError</code>
   *
   * @type {boolean}
   */
  hasErrors: Em.computed.alias('thresholdMinError'),

  /**
   * Error message for <code>thresholdMin</code>
   * <ul>
   *  <li>Value is not a number</li>
   *  <li>Value is out of range <code>(minValue - maxValue)</code></li>
   * </ul>
   * Empty message means that <code>thresholdMin</code> has valid value
   *
   * @type {string}
   */
  thresholdMinErrorMessage: function () {
    var thresholdMin = this.get('thresholdMin');
    var maxValue = this.get('maxValue');
    var minValue = this.get('minValue');
    if (!isValidFloat(thresholdMin) || thresholdMin > maxValue || thresholdMin < minValue) {
      return Em.I18n.t('dashboard.widgets.error.invalid').format(minValue, maxValue);
    }
    return '';
  }.property('thresholdMin', 'maxValue'),

  /**
   * Formatted threshold value
   *
   * @type {number[]}
   */
  preparedThresholds: function () {
    return [parseFloat(this.get('thresholdMin'))];
  }.property('thresholdMin'),

  /**
   * Force set new values for threshold
   *
   * @param {number[]} newValues
   */
  updateThresholds(newValues) {
    this.set('thresholdMin', newValues[0]);
  }

});

/**
 * Thresholds manager for sliders with double values
 * Usage:
 * <pre>
 * SingleHandler.create({
 *    thresholdMin: 12,
 *    thresholdMax: 40,
 *    minValue: 10,
 *    maxValue: 100
 * });
 * </pre>
 *
 * @class DoubleHandlers
 */
const DoubleHandlers = SingleHandler.extend({

  /**
   * @type {number}
   */
  thresholdMax: null,

  /**
   * Is <code>thresholdMax</code> invalid
   * true - invalid
   * false - valid
   *
   * @type {boolean}
   */
  thresholdMaxError: Em.computed.bool('thresholdMaxErrorMessage'),

  /**
   * Is some threshold invalid
   * true - some one is invalid
   * false - thresholds are valid
   *
   * @type {boolean}
   */
  hasErrors: Em.computed.or('thresholdMinError', 'thresholdMaxError'),

  /**
   * Error message for <code>thresholdMin</code>
   * <ul>
   *  <li>Value is not a number</li>
   *  <li>Value is out of range <code>(minValue - maxValue)</code></li>
   *  <li><code>thresholdMin</code>-value greater than <code>thresholdMax</code>-value</li>
   * </ul>
   * Empty message means that <code>thresholdMin</code> has valid value
   *
   * @type {string}
   */
  thresholdMinErrorMessage: function () {
    var thresholdMin = Number(this.get('thresholdMin'));
    var thresholdMax = Number(this.get('thresholdMax'));
    var maxValue = Number(this.get('maxValue'));
    var minValue = Number(this.get('minValue'));
    if (!isValidFloat(thresholdMin) || thresholdMin > maxValue || thresholdMin < minValue) {
      return Em.I18n.t('dashboard.widgets.error.invalid').format(minValue, maxValue);
    }
    if (this.get('thresholdMaxError') === false && thresholdMax <= thresholdMin) {
      return Em.I18n.t('dashboard.widgets.error.smaller');
    }
    return '';
  }.property('thresholdMin', 'thresholdMax'),

  /**
   * Error message for <code>thresholdMax</code>
   * <ul>
   *  <li>Value is not a number</li>
   *  <li>Value is out of range <code>(minValue - maxValue)</code></li>
   * </ul>
   * Empty message means that <code>thresholdMax</code> has valid value
   *
   * @type {string}
   */
  thresholdMaxErrorMessage: function () {
    var thresholdMax = this.get('thresholdMax');
    var maxValue = this.get('maxValue');
    var minValue = this.get('minValue');
    if (!isValidFloat(thresholdMax) || thresholdMax > maxValue || thresholdMax < minValue) {
      return Em.I18n.t('dashboard.widgets.error.invalid').format(minValue, maxValue);
    }
    return '';
  }.property('thresholdMax'),

  /**
   * Threshold values ready to save
   *
   * @type {number[]}
   */
  preparedThresholds: function () {
    return [parseFloat(this.get('thresholdMin')), parseFloat(this.get('thresholdMax'))];
  }.property('thresholdMin', 'thresholdMax'),

  /**
   * Force set new values for threshold
   *
   * @param {number[]} newValues
   */
  updateThresholds(newValues) {
    this.set('thresholdMin', newValues[0]);
    this.set('thresholdMax', newValues[1]);
  }

});

/**
 * Common body-view for popup with sliders
 *
 * @class EditDashboardWidgetPopupBody
 */
const EditDashboardWidgetPopupBody = Em.View.extend({
  templateName: require('templates/main/dashboard/edit_widget_popup')
});

/**
 * Popup with slider to edit dashboard widget
 * Usage:
 * <pre>
 *   App.EditDashboardWidgetPopup.show({
 *    widgetView: this,
 *    sliderHandlersManager: App.EditDashboardWidgetPopup.DoubleHandlers.create({
 *      maxValue: 100,
 *      thresholdMin: this.get('thresholdMin'),
 *      thresholdMax: this.get('thresholdMax')
 *    })
 *  });
 * </pre>
 *
 * <code>widgetView</code> should be set to view with widget.
 * Usually you will use <code>App.EditDashboardWidgetPopup</code> inside of some <code>App.DashboardWidgetView</code> instance,
 * so <code>widgetView</code> may be set to <code>this</code>
 * <code>sliderHandlersManager</code> should be set to some of the <code>App.EditDashboardWidgetPopup.SingleHandler</code>
 * or <code>App.EditDashboardWidgetPopup.DoubleHandler</code>
 *
 * You can't use <code>App.EditDashboardWidgetPopup</code> without setting this two properties!
 *
 * @class App.EditDashboardWidgetPopup
 */
App.EditDashboardWidgetPopup = App.ModalPopup.extend({

  header: Em.I18n.t('dashboard.widgets.popupHeader'),
  classNames: ['modal-edit-widget'],
  modalDialogClasses: ['modal-lg'],
  primary: Em.I18n.t('common.apply'),
  disablePrimary: Em.computed.alias('sliderHandlersManager.hasErrors'),

  /**
   * Can't be null or undefined
   *
   * @type {SingleHandler|DoubleHandlers}
   */
  sliderHandlersManager: null,

  /**
   * Widget view
   * Can't be not a view. Used to save Thresholds. Normally it's an instance of <code>App.DashboardWidgetView</code>
   *
   * @type {Em.View}
   */
  widgetView: null,

  /**
   * Determines if slider is enabled for slide
   * true - don't enabled
   * false - enabled
   *
   * Determines if slider handlers should be updated with Threshold values
   * true - don't update
   * false - update
   *
   * Used as option <code>disabled</code> for $.ui.slider
   *
   * @type {boolean}
   */
  sliderDisabled: false,

  /**
   * Slider "ticks"
   * Used as option <code>values</code> for $.ui.slider
   *
   * @type {number[]}
   */
  sliderHandlers: function () {
    return this.get('sliderHandlersManager.preparedThresholds');
  }.property('sliderHandlersManager.preparedThresholds.[]'),

  /**
   * Colors used for slider ranges
   * @type {string[]}
   */
  sliderColors: [App.healthStatusRed, App.healthStatusOrange, App.healthStatusGreen],

  /**
   * Maximum value for slider
   * Used as option <code>max</code> for $.ui.slider
   *
   * @type {number}
   */
  sliderMaxValue: Em.computed.alias('sliderHandlersManager.maxValue'),

  /**
   * Minimum value for slider
   * Used as option <code>min</code> for $.ui.slider
   *
   * @type {number}
   */
  sliderMinValue: 0,

  /**
   * Check how many handlers has slider
   * true - 2 handlers
   * false - 1 handler
   *
   * Used as option <code>range</code> for $.ui.slider
   *
   * @type {boolean}
   */
  sliderIsRange: true,

  bodyClass: EditDashboardWidgetPopupBody,

  init() {
    Em.assert('`widgetView` should be valid view', this.get('widgetView.isView'));
    Em.assert('`sliderHandlersManager` should be set', !!this.get('sliderHandlersManager'));
    return this._super(...arguments);
  },

  /**
   * Save new threshold value on popup-close (means Primary click)
   * Use <code>widgetView</code> to get <code>widgetsView</code> and save new values
   * Current widget is updated too (without redrawing)
   */
  saveThreshold () {
    let preparedThresholds = this.get('sliderHandlersManager.preparedThresholds');
    this.get('widgetView').saveWidgetThresholds(preparedThresholds);
  },

  /**
   * Update slider values when new threshold values are provided
   * Don't do anything if some value is invalid or slider is disabled
   *
   * @private
   */
  _updateSliderValues: function() {
    var sliderHandlersManager = this.get('sliderHandlersManager');
    if (!sliderHandlersManager.get('hasErrors') && !this.get('sliderDisabled')) {
      $('#slider-range').slider('values', sliderHandlersManager.get('preparedThresholds'));
    }
  }.observes('sliderDisabled', 'sliderHandlersManager.preparedThresholds.[]', 'sliderHandlersManager.hasErrors'),

  onPrimary () {
    let sliderHandlersManager = this.get('sliderHandlersManager');
    if (!sliderHandlersManager.get('hasErrors')) {
      this.saveThreshold();
      this.hide();
    }
  },

  /**
   * Create slider in the popup when it's opened
   */
  createSlider() {
    var self = this;
    let sliderHandlersManager = this.get('sliderHandlersManager');
    var handlers = this.get('sliderHandlers');

    $('#slider-range').slider({
      range: this.get('sliderIsRange'),
      min: this.get('sliderMinValue'),
      max: this.get('sliderMaxValue'),
      disabled: this.get('sliderDisabled'),
      values: handlers,
      create: function () {
        self.updateSliderColors(handlers);
      },
      slide: function (event, ui) {
        self.updateSliderColors(ui.values);
        sliderHandlersManager.updateThresholds(ui.values);
      },
      change: function (event, ui) {
        self.updateSliderColors(ui.values);
      }
    });
  },

  didInsertElement: function () {
    this._super();
    this.createSlider();
  },

  /**
   * Update colors on slider using <code>sliderColors</code> theme when user interacts with it
   *
   * @param {number[]} handlers
   */
  updateSliderColors(handlers) {
    let gradient = this._getGradientStr(handlers);
    $('#slider-range')
      .css('background-image', '-webkit-' + gradient)
      .css('background-image', '-ms-' + gradient)
      .css('background-image', '-moz-' + gradient)
      .find('.ui-widget-header').css({
        'background-color': App.healthStatusOrange,
        'background-image': 'none'
      });
  },

  /**
   * @param {number[]} handlers
   * @returns {string}
   * @private
   */
  _getGradientStr(handlers) {
    let maxValue = this.get('sliderMaxValue');
    let colors = this.get('sliderColors');
    let gradient = colors[0] + ', ' + handlers.map((handler, i) => {
        return `${colors[i]}  ${handlers[i] * 100 / maxValue}%, ${colors[i + 1]} ${handlers[i] * 100 / maxValue}%,`;
      }).join('') + colors[colors.length - 1];
    return `linear-gradient(left,${gradient})`;
  }

});

App.EditDashboardWidgetPopup.reopenClass({
  SingleHandler,
  DoubleHandlers,
  EditDashboardWidgetPopupBody
});
