///////////////////////////////////////////////////////////////////////////
// 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/_base/declare',
  'dojo/_base/lang',
  'dojo/_base/array',
  'dojo/_base/html',
  'dijit/_WidgetBase',
  'dojo/Deferred',
  'dojo/promise/all',
  '../utils',
  '../WidgetPlaceholder',
  '../OnScreenWidgetIcon',
  "./A11yLayoutManager"
],

function(declare, lang, array, html, _WidgetBase, Deferred, all, jimuUtils,
  WidgetPlaceholder, OnScreenWidgetIcon, a11y) {
  var instance = null, clazz;

  /*jshint unused:false */
  /* global jimuConfig:true */

  clazz = declare([_WidgetBase], {
    constructor: function() {
      this.widgetPlaceholders = [];
      this.onScreenWidgetIcons = [];
      this.invisibleWidgetIds = [];
    },

    name: 'BaseLayoutManager',
    mapId: 'map',
    map: null,
    layoutId: 'jimu-layout-manager',

    postCreate: function(){
      this.containerNode = this.domNode;
      this.layoutId = jimuConfig.layoutId;
    },

    resize: function() {
    },

    isSupportEdit: function(){
      return false;
    },

    getMapDiv: function(){
    },

    setMap: function(map){
      this.map = map;
    },

    onEnter: function(appConfig, mapId){
      var def = new Deferred();
      this.appConfig = appConfig;
      this.mapId = mapId;
      def.resolve();
      return def;
    },

    onLeave: function(){

    },

    onThemeLoad: function() {
    },

    /**
     * do load and layout job in this function, including onscreen widgets and map
     *
     * @param  {[type]} appConfig [description]
     * @return {[type]}           [description]
     */
    loadAndLayout: function(appConfig){

    },

    openWidget: function(widgetId){

    },

    /////////////functions to handle builder events
    onLayoutChange: function(appConfig){

    },

    onWidgetChange: function(appConfig, widgetJson){

    },

    onGroupChange: function(appConfig, groupJson){

    },

    onWidgetPoolChange: function(appConfig, changeData){
      this.reloadControllerWidget(appConfig, changeData.controllerId);
    },

    onOnScreenOrderChange: function(appConfig, onscreenWidgets){
      array.forEach(onscreenWidgets, lang.hitch(this, function(widgetConfig) {
        if (!widgetConfig.uri) {
          array.some(this.widgetPlaceholders, lang.hitch(this, function(phDijit) {
            if (phDijit.index === widgetConfig.placeholderIndex) {
              var style = jimuUtils.getPositionStyle({
                top: widgetConfig.position.top,
                left: widgetConfig.position.left,
                right: widgetConfig.position.right,
                bottom: widgetConfig.position.bottom,
                width: 40,
                height: 40
              });
              html.setStyle(phDijit.domNode, style);
              return true;
            }
          }));
        } else {
          array.some(this.onScreenWidgetIcons, lang.hitch(this, function(iconDijit) {
            if (iconDijit.configId === widgetConfig.id) {
              html.setStyle(iconDijit.domNode, jimuUtils.getPositionStyle({
                top: widgetConfig.position.top,
                left: widgetConfig.position.left,
                right: widgetConfig.position.right,
                bottom: widgetConfig.position.bottom,
                width: 40,
                height: 40
              }));
              iconDijit.moveTo(widgetConfig.position);
              return true;
            }
          }));
        }
      }));
    },

    onActionTriggered: function(actionInfo){
    },

    onLayoutDefinitionChange: function(appConfig, layoutDefinition){

    },

    onOnScreenGroupsChange: function(appConfig, groups){

    },

    destroyOnScreenWidgetsAndGroups: function(appConfig){

    },

    /*
      Because, for now, onscreen widgets are managed by the same way by these 2 layout manager, so, put code here.
      when we have a layout that does not use the same way to manage onscreen widgets, we'll refactor the code
      These functions are used to called by sub class, not used to be inherited.
    */

    loadOnScreenWidgets: function(appConfig){
      var defs = [];
      array.forEach(appConfig.widgetOnScreen.widgets, function(widgetConfig) {
        if(widgetConfig.visible === false){
          this.invisibleWidgetIds.push(widgetConfig.id);
          return;
        }
        defs.push(this.loadOnScreenWidget(widgetConfig, appConfig));
      }, this);

      return all(defs);
    },

    loadOnScreenWidget: function(widgetConfig, appConfig){
      var def = new Deferred();

      if(appConfig.mode === 'config' && !widgetConfig.uri){
        var placeholder = this._createOnScreenWidgetPlaceHolder(widgetConfig);
        def.resolve(placeholder);
        return def;
      }else if(!widgetConfig.uri){
        //in run mode, when no uri, do nothing
        def.resolve(null);
        return def;
      }

      var iconDijit;
      if(widgetConfig.inPanel || widgetConfig.closeable){//TODO closeable rename
        //in panel widget or closeable off panel widget
        iconDijit = this._createOnScreenWidgetIcon(widgetConfig);
        def.resolve(iconDijit);
      }else{
        //off panel
        this.widgetManager.loadWidget(widgetConfig).then(lang.hitch(this, function(widget){
          try{
            widget.setPosition(widget.position);
            this.widgetManager.openWidget(widget);
          }catch(err){
            console.log(console.error('fail to startup widget ' + widget.name + '. ' + err.stack));
          }

          widget.configId = widgetConfig.id;
          def.resolve(widget);
        }), function(err){
          def.reject(err);
        });
      }

      return def;
    },

    onOnScreenWidgetChange: function(appConfig, widgetJson){
      widgetJson = appConfig.getConfigElementById(widgetJson.id);
      if(widgetJson.isController){
        this.reloadControllerWidget(appConfig, widgetJson.id);
        return;
      }
      array.forEach(this.widgetPlaceholders, function(placeholder){
        if(placeholder.configId === widgetJson.id){
          placeholder.destroy();
          this.loadOnScreenWidget(widgetJson, appConfig);
        }
      }, this);
      this.removeDestroyed(this.widgetPlaceholders);
      this._updatePlaceholder(appConfig);

      array.forEach(this.onScreenWidgetIcons, function(icon){
        if(icon.configId === widgetJson.id){
          var state = icon.state;
          icon.destroy();
          this.loadOnScreenWidget(widgetJson, appConfig).then(function(iconNew){
            if(widgetJson.uri && state === 'opened'){
              iconNew.onClick();
            }
          });
        }
      }, this);
      this.removeDestroyed(this.onScreenWidgetIcons);

      array.forEach(this.widgetManager.getOnScreenOffPanelWidgets(), function(widget){
        if(widget.configId === widgetJson.id){
          widget.destroy();
          if(widgetJson.visible === false){
            if(this.invisibleWidgetIds.indexOf(widgetJson.id) < 0){
              this.invisibleWidgetIds.push(widgetJson.id);
            }
            return;
          }
          this.loadOnScreenWidget(widgetJson, appConfig);
        }
      }, this);

      //if widget change visible from invisible, it's not exist in onscreen offpanel Widgets
      //so, load it here
      array.forEach(this.invisibleWidgetIds, function(widgetId){
        if(widgetId === widgetJson.id && widgetJson.visible !== false){
          this.loadOnScreenWidget(widgetJson, appConfig);
          var i = this.invisibleWidgetIds.indexOf(widgetJson.id);
          this.invisibleWidgetIds.splice(i, 1);
        }
      }, this);

      if(!widgetJson.isOnScreen){
        array.forEach(this.widgetManager.getControllerWidgets(), function(controllerWidget){
          if(controllerWidget.widgetIsControlled(widgetJson.id)){
            this.reloadControllerWidget(appConfig, controllerWidget.id);
          }
        }, this);
      }
    },

    destroyOnScreenWidgetIcons: function(){
      array.forEach(this.onScreenWidgetIcons, function(icon){
        icon.destroy();
      }, this);
      this.onScreenWidgetIcons = [];
    },

    destroyOnScreenOffPanelWidgets: function(){
      array.forEach(this.widgetManager.getOnScreenOffPanelWidgets(), function(widget){
        if(widget.isController){
          this._destroyControllerWidget(widget);
        }else{
          this.widgetManager.destroyWidget(widget);
        }
      }, this);
    },

    destroyWidgetPlaceholders: function(){
      array.forEach(this.widgetPlaceholders, function(placeholder){
        placeholder.destroy();
      }, this);
      this.widgetPlaceholders = [];
    },

    removeDestroyed: function(_array){
      var willBeDestroyed = [];
      array.forEach(_array, function(e){
        if(e._destroyed){
          willBeDestroyed.push(e);
        }
      });
      array.forEach(willBeDestroyed, function(e){
        var i = _array.indexOf(e);
        _array.splice(i, 1);
      });
    },

    _createOnScreenWidgetPlaceHolder: function(widgetConfig){
      var pid;
      if(widgetConfig.position.relativeTo === 'map'){
        pid = this.mapId;
      }else{
        pid = this.layoutId;
      }
      var cfg = lang.clone(widgetConfig);

      cfg.position.width = 40;
      cfg.position.height = 40;
      var style = jimuUtils.getPositionStyle(cfg.position);
      var phDijit = new WidgetPlaceholder({
        index: cfg.placeholderIndex,
        configId: widgetConfig.id
      });
      html.setStyle(phDijit.domNode, style);
      html.place(phDijit.domNode, pid);
      this.widgetPlaceholders.push(phDijit);
      return phDijit;
    },

    _createOnScreenWidgetIcon: function(widgetConfig){
      var iconDijit = new OnScreenWidgetIcon({
        panelManager: this.panelManager,
        widgetManager: this.widgetManager,
        widgetConfig: widgetConfig,
        configId: widgetConfig.id,
        map: this.map
      });

      if(widgetConfig.position.relativeTo === 'map'){
        html.place(iconDijit.domNode, this.mapId);
      }else{
        html.place(iconDijit.domNode, this.layoutId);
      }
      //icon position doesn't use width/height in config
      html.setStyle(iconDijit.domNode, jimuUtils.getPositionStyle({
        top: widgetConfig.position.top,
        left: widgetConfig.position.left,
        right: widgetConfig.position.right,
        bottom: widgetConfig.position.bottom,
        width: 40,
        height: 40
      }));
      iconDijit.startup();

      if(!this.openAtStartWidget && widgetConfig.openAtStart){
        iconDijit.switchToOpen();
        this.openAtStartWidget = widgetConfig.name;
      }

      this.onScreenWidgetIcons.push(iconDijit);
      return iconDijit;
    },

    reloadControllerWidget: function(appConfig, controllerId){
      var controllerWidget = this.widgetManager.getWidgetById(controllerId);
      if(!controllerWidget){
        this._loadControllerWidget(appConfig, controllerId);
        return;
      }

      //get old info
      var openedIds = controllerWidget.getOpenedIds();
      var windowState = controllerWidget.windowState;

      this._destroyControllerWidget(controllerWidget);
      this._loadControllerWidget(appConfig, controllerId, openedIds, windowState);
    },

     _doPostLoad: function(){
      //load somethings that may be used later.
      //let it load behind the stage.
      require(['dynamic-modules/postload']);
    },

    _destroyControllerWidget: function(controllerWidget){
      //destory all panels controlled by the controller.
      //we can't destroy the opened only, because some panels are closed but the
      //instance is still exists

      //destroy controlled widgets
      array.forEach(controllerWidget.getAllConfigs(), function(configJson){
        if(configJson.widgets){//it's group
          this.panelManager.destroyPanel(configJson.id + '_panel');
          array.forEach(configJson.widgets, function(widgetItem){
            //Group items must be in panel
            this.panelManager.destroyPanel(widgetItem.id + '_panel');
          }, this);
        }else{
          var widget = this.widgetManager.getWidgetById(configJson.id);
          if(!widget){
            return;
          }
          if(configJson.inPanel){
            this.panelManager.destroyPanel(widget.getPanel());
          }else{
            this.widgetManager.destroyWidget(widget);
          }
        }

      }, this);

      //destroy controller itself
      this.widgetManager.destroyWidget(controllerWidget);
    },

    _loadControllerWidget: function(appConfig, controllerId, openedIds, windowState){
      //load widget
      var newControllerJson = appConfig.getConfigElementById(controllerId);
      if(newControllerJson.visible === false){
        return;
      }
      this.loadOnScreenWidget(newControllerJson, appConfig).then(lang.hitch(this, function(widget){
        if(windowState){
          this.widgetManager.changeWindowStateTo(widget, windowState);
        }
        if(openedIds){
          widget.setOpenedIds(openedIds);
        }
      }));
    },

    _updatePlaceholder: function (appConfig) {
      array.forEach(this.widgetPlaceholders, function(placehoder){
        placehoder.setIndex(appConfig.getConfigElementById(placehoder.configId).placeholderIndex);
      }, this);
    }

  });

  clazz.extend(a11y);
  clazz.getInstance = function() {
    if (instance === null) {
      instance = new clazz();
    }
    return instance;
  };
  return clazz;
});
