///////////////////////////////////////////////////////////////////////////
// Copyright © Esri. All Rights Reserved.
//
// 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.
///////////////////////////////////////////////////////////////////////////

define([
  'dojo/aspect',
  'dojo/Deferred',
  'dojo/_base/lang',
  'dojo/_base/html',
  'dojo/_base/array',
  'dojo/on',
  'dojo/_base/declare',
  './ValueProvider',
  'dojo/store/Memory',
  'jimu/utils',
  'dijit/form/FilteringSelect'
],
  function(aspect, Deferred, lang, html, array, on, declare, ValueProvider,
    Memory, jimuUtils, FilteringSelect) {

    return declare([ValueProvider], {
      templateString: '<div></div>',
      codedValues: null,//[{value,label}] for coded values and sub types
      staticValues: null,//[{value,label}]
      showNullValues: false,//show null values
      layerDataChanged: false, //layer data update status
      ifDropDown: false,
      customId: null, //optional, for screen readers

      postCreate: function(){
        this.inherited(arguments);
        html.addClass(this.domNode, 'jimu-filter-list-value-provider');
        this.customId = this.partObj.vpId;
        this._initValueSelect();

        this._uniqueValueCache = {};
        this.noDataTips = '<div class="error-tip-section" style="display: block;">' +
                          '<span class="jimu-icon jimu-icon-error"></span>' +
                          '<span class="jimu-state-error-text">' + this.nls.noFilterValueTip + '</span></div>';

        //[{id,value,label}]
        var store = new Memory({idProperty:'id', data: []});
        this.valuesSelect.set('store', store);

        //is numberical field
        this.isNumberField = jimuUtils.isNumberField(this.fieldInfo.type);

        if(!this.staticValues && typeof this.valuesSelect._onDropDownMouseDown === 'function'){
          if(!this.codedValues || (this.codedValues && this.filterCodedValue)){
            this.own(
              aspect.before(this.valuesSelect,
                            "_onDropDownMouseDown",
                            lang.hitch(this, this._onBeforeDropDownMouseDown))
            );
            this.own(on(document.body, 'click', lang.hitch(this, this._onBodyClick)));

            if(this.layerInfo){ //it always exsits because it's required from valueProviderFactory constructor
              //it will tragger after the  add/remove/update events happen
              this.layerInfo.getLayerObject().then(lang.hitch(this, function(layerObject){
                layerObject.on("edits-complete", lang.hitch(this, function() {
                  this.layerDataChanged = true;
                }));
              }));
            }
          }
        }
      },

      _initValueSelect: function(){
        var options = {
          searchAttr: 'label',
          required: false,
          intermediateChanges: true
        };
        if(this.customId){//use this attr because FilteringSelect uses input for Interaction
          options['aria-label'] = this.partObj.interactiveObj.prompt + ' ' + this.partObj.interactiveObj.hint;
        }
        this.valuesSelect = new FilteringSelect(options);
        this.valuesSelect.startup();
        this.valuesSelect.on('input', lang.hitch(this, this._onFilteringSelectInput));
        html.setStyle(this.valuesSelect.domNode, 'width', '100%');
        this.valuesSelect.placeAt(this.domNode);
      },

      _onFilteringSelectInput: function(){
        this.emit('change');
      },

      _getCodedValueLabelsBySubTypeId:function(){
        // var newExpr = this.getDropdownFilterExpr();
        // return jimuUtils.getCodedValueLabelsByExprs(this.layerDefinition, this.fieldName, newExpr, this.codedValues);
        var newParj = this.getDropdownFilterPartsObj();
        return this.getCodedValueListByPartsObj(this.layerDefinition, this.fieldName, newParj, this.codedValues);
      },

      _onBeforeDropDownMouseDown: function(){
        this.ifDropDown = true;
        this._tryUpdatingUniqueValues(undefined, true);
        return arguments;
      },

      _onBodyClick: function(evt){
        var target = evt.target || evt.srcElement;
        if(target === this.domNode || html.isDescendant(target, this.domNode)){
          return;
        }
        if(this.msgDiv){
          html.setStyle(this.msgDiv, "display", "none");
        }
      },

      getDijits: function(){
        return [this.valuesSelect];
      },

      isValidValue: function(){
        return this.getStatus() > 0;
      },

      //-1 means invalid value type
      //0 means empty value, this ValueProvider should be ignored
      //1 means valid value
      getStatus: function(){
        var item = this.valuesSelect.get('item');
        if(item){
          if(item.value !== undefined){ //if(item.label !== ''){
            //Its value must be a string when valueType is 'field', not need to check validation
            if(this.isNumberField && !jimuUtils.isValidNumber(item.value) &&
             this.partObj.valueObj.type === 'value'){
              var newVal = parseFloat(item.value);
              if(jimuUtils.isValidNumber(newVal)){
                item.value = newVal;
                return this._getStatusForDijit(this.valuesSelect);
              }
              return -1;
            }
            return this._getStatusForDijit(this.valuesSelect);
          }else{
            return 0;
          }
        }else{
          return 0;
        }
      },

      //return -1 means input a wrong value
      //return 0 means empty value
      //return 1 means valid value
      _getStatusForDijit: function(dijit){
        if(dijit.validate()){
          if(dijit.get("DisplayedValue")){
            return 1;
          }else{
            return 0;
          }
        }else{
          return -1;
        }
      },

      //maybe return a deferred
      setValueObject: function(valueObj){
        if(this.staticValues){
          return this._setValueForStaticValues(valueObj.value, this.staticValues);
        } else if(this.codedValues){
          if(this.filterCodedValue){

            return this._tryUpdatingUniqueValues(valueObj.value, false);
          }else{
            return this._setValueForStaticValues(valueObj.value, this.codedValues);
          }
        } else{
          return this._tryUpdatingUniqueValues(valueObj.value, false);
        }
      },

      getValueObject: function(){
        if(this.isValidValue()){
          var item = this.valuesSelect.get('item');
          var value = item.value;
          return {
            "isValid": true,
            "type": this.partObj.valueObj.type,
            "value": value
          };
        }
        return null;
      },

      tryGetValueObject: function(){
        if(this.isValidValue()){
          return this.getValueObject();
        }else if(this.isEmptyValue()){
          return {
            "isValid": true,
            "type": this.partObj.valueObj.type,
            "value": this.shortType === 'string' ? "" : null
          };
        }
        return null;
      },

      setRequired: function(required){
        this.valuesSelect.set("required", required);
      },

      /*disable: function(){
        this.inherited(arguments);
        this.valuesSelect.closeDropDown(true);
        this.valuesSelect.set('disabled', true);
      },

      enable: function(){
        this.inherited(arguments);
        this.valuesSelect.set('disabled', false);
      },*/

      _setValueForStaticValues: function(selectedValue, valueLabels){
        //selectedValue maybe undefined or null
        var data = null;
        var selectedId = -1;
        var selectedItem = null;
        if(valueLabels){
          data = array.map(valueLabels, lang.hitch(this, function(item, index){
            var dataItem = {
              id: index,
              value: item.value,
              label: item.label
            };
            if(dataItem.value + '' === selectedValue + ''){
              selectedId = index;
            }
            return dataItem;
          }));
          this.valuesSelect.store.setData(data);
          if(selectedId >= 0){
            selectedItem = this.valuesSelect.store.get(selectedId);
            if(selectedItem){
              this.valuesSelect.set('item', selectedItem);
            }
          }
          this._checkIfNoData();
        }
      },

      _uniqueValueLoadingDef: null,
      _uniqueValueLoadingExpr: '',
      _uniqueValueCache: null,//{expr1:values1,expr2:values2}

      _tryUpdatingUniqueValues: function(selectedValue, showDropDownAfterValueUpdate){
        var def = new Deferred();
        if(!this.valuesSelect._opened){
          var newExpr = this.getDropdownFilterExpr();
          if(newExpr !== this._uniqueValueLoadingExpr || this.layerDataChanged){
            //expr changed
            this.valuesSelect.readOnly = true;
            if(this._uniqueValueLoadingDef){
              this._uniqueValueLoadingDef.reject();
              this._uniqueValueLoadingDef = null;
            }
            this._uniqueValueLoadingExpr = newExpr;
            this._uniqueValueLoadingDef = this._getUniqueValues(newExpr);
            this._uniqueValueLoadingDef.then(lang.hitch(this, function(valueLabels){
              if(!this.domNode){
                return;
              }
              this._uniqueValueLoadingDef = null;
              this.valuesSelect.readOnly = false;
              this._setValueForUniqueValues(selectedValue, valueLabels);
              this._hideLoadingIcon();
              if(showDropDownAfterValueUpdate){
                this.valuesSelect.toggleDropDown();
              }
              this._checkIfNoData();
              def.resolve();
            }), lang.hitch(this, function(err){
              console.error(err);
              if(!this.domNode){
                return;
              }
              this._uniqueValueLoadingDef = null;
              this.valuesSelect.readOnly = false;
              this._hideLoadingIcon();
              this._checkIfNoData();
              def.reject(err);
            }));
          }else{
            this._checkIfNoData();
            def.resolve();
          }
        }else{
          this._checkIfNoData();
          def.resolve();
        }
        return def;
      },

      //return a deferred
      _setValueForUniqueValues: function(selectedValue, valueLabels){
        valueLabels.sort(function(item1, item2){
          if(item1.value < item2.value){
            return -1;
          }else if(item1.value === item2.value){
            return 0;
          }else{
            return 1;
          }
        });
        //selectedValue maybe undefined or null
        if(!this.showNullValues){
          valueLabels = array.filter(valueLabels, lang.hitch(this, function(item){
            return item.value !== '<Null>' && item.value !== null;
          }));
        }
        if(selectedValue === undefined){
          var currentValueObj = this.getValueObject();
          if(currentValueObj){
            selectedValue = currentValueObj.value;
          }
        }
        var selectedId = -1;
        var selectedItem = null;
        var data = array.map(valueLabels, lang.hitch(this, function(item, index) {
          var dataItem = {
            id: index,
            value: item.value,
            label: item.label
          };

          if (item.value + '' === selectedValue + '') {
            selectedId = index;
          }

          return dataItem;
        }));

        this.valuesSelect.store.setData(data);

        if (selectedId >= 0) {
          selectedItem = this.valuesSelect.store.get(selectedId);
        }

        //selectedItem maybe null
        //we need to set item to null to clear the previous invlaid value
        this.valuesSelect.set('item', selectedItem);
      },

      _checkIfNoData: function(){
        if(this.runtime && this.ifDropDown){
          this.ifDropDown = false;
          var dataList = this.valuesSelect.store.data;
          if (dataList.length === 0) {
            if(!this.msgDiv){
              this.msgDiv = document.createElement('div');
              html.addClass(this.msgDiv, "jimu-filter-list-value-provider-tip-container");
              this.msgDiv.innerHTML = this.noDataTips;
              this.valuesSelect.domNode.parentNode.appendChild(this.msgDiv);
            }else{
              html.setStyle(this.msgDiv, "display", "block");
            }
          }
        }
      },

      _showLoadingIcon: function(){
        html.addClass(this.valuesSelect.domNode, 'loading');
      },

      _hideLoadingIcon: function(){
        html.removeClass(this.valuesSelect.domNode, 'loading');
      },

      _getUniqueValues: function(where){
        var def = new Deferred();
        if(this._uniqueValueCache[where] && !this.layerDataChanged){
          def.resolve(this._uniqueValueCache[where]);
        }else{
          this._showLoadingIcon();
          jimuUtils.getUniqueValues(this.url, this.fieldName, where, this.layerDefinition, this.fieldPopupInfo)
          .then(lang.hitch(this, function(valueLabels){
            if(!this.domNode){
              return;
            }
            this._uniqueValueCache[where] = valueLabels;
            def.resolve(valueLabels);
            this._hideLoadingIcon();
          }), lang.hitch(this, function(err){
            if(!this.domNode){
              return;
            }
            def.reject(err);
            this._hideLoadingIcon();
          }));
        }
        this.layerDataChanged = false;//reset default value
        return def;
      }

    });
  });