/*
 * The MIT License
 *
 * Copyright (c) 2015, Sebastian Sdorra
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * @ngdoc directive
 * @name adf.directive:adfDashboard
 * @element div
 * @restrict EA
 * @scope
 * @description
 *
 * `adfDashboard` is a directive which renders the dashboard with all its
 * components. The directive requires a name attribute. The name of the
 * dashboard can be used to store the model.
 *
 * @param {string} name name of the dashboard. This attribute is required.
 * @param {boolean=} editable false to disable the editmode of the dashboard.
 * @param {boolean=} collapsible true to make widgets collapsible on the dashboard.
 * @param {boolean=} maximizable true to add a button for open widgets in a large modal panel.
 * @param {boolean=} enableConfirmDelete true to ask before remove an widget from the dashboard.
 * @param {string=} structure the default structure of the dashboard.
 * @param {object=} adfModel model object of the dashboard.
 * @param {function=} adfWidgetFilter function to filter widgets on the add dialog.
 * @param {boolean=} continuousEditMode enable continuous edit mode, to fire add/change/remove
 *                   events during edit mode not reset it if edit mode is exited.
 * @param {boolean=} categories enable categories for the add widget dialog.
 */

angular.module('adf')
  .directive('adfDashboard', function ($rootScope, $log, $timeout, $uibModal, dashboard, adfTemplatePath) {
    'use strict';

    function stringToBoolean(string){
      switch(angular.isDefined(string) ? string.toLowerCase() : null){
        case 'true': case 'yes': case '1': return true;
        case 'false': case 'no': case '0': case null: return false;
        default: return Boolean(string);
      }
    }

    function copyWidgets(source, target) {
      if ( source.widgets && source.widgets.length > 0 ){
        var w = source.widgets.shift();
        while (w){
          target.widgets.push(w);
          w = source.widgets.shift();
        }
      }
    }

    /**
    * Copy widget from old columns to the new model
    * @param object root the model
    * @param array of columns
    * @param counter
    */
    function fillStructure(root, columns, counter) {
      counter = counter || 0;

      if (angular.isDefined(root.rows)) {
        angular.forEach(root.rows, function (row) {
          angular.forEach(row.columns, function (column) {
            // if the widgets prop doesn't exist, create a new array for it.
            // this allows ui.sortable to do it's thing without error
            if (!column.widgets) {
              column.widgets = [];
            }

            // if a column exist at the counter index, copy over the column
            if (angular.isDefined(columns[counter])) {
              // do not add widgets to a column, which uses nested rows
              if (angular.isUndefined(column.rows)){
                copyWidgets(columns[counter], column);
                counter++;
              }
            }

            // run fillStructure again for any sub rows/columns
            counter = fillStructure(column, columns, counter);
          });
        });
      }
      return counter;
    }

    /**
    * Read Columns: recursively searches an object for the 'columns' property
    * @param object model
    * @param array  an array of existing columns; used when recursion happens
    */
    function readColumns(root, columns) {
      columns = columns || [];

      if (angular.isDefined(root.rows)) {
        angular.forEach(root.rows, function (row) {
          angular.forEach(row.columns, function (col) {
            columns.push(col);
            // keep reading columns until we can't any more
            readColumns(col, columns);
          });
        });
      }

      return columns;
    }

    function changeStructure(model, structure){
      var columns = readColumns(model);
      var counter = 0;

      model.rows = angular.copy(structure.rows);

      while ( counter < columns.length ){
        counter = fillStructure(model, columns, counter);
      }
    }

    function createConfiguration(type){
      var cfg = {};
      var config = dashboard.widgets[type].config;
      if (config){
        cfg = angular.copy(config);
      }
      return cfg;
    }

    /**
     * Find first widget column in model.
     *
     * @param dashboard model
     */
    function findFirstWidgetColumn(model){
      var column = null;
      if (!angular.isArray(model.rows)){
        $log.error('model does not have any rows');
        return null;
      }
      for (var i=0; i<model.rows.length; i++){
        var row = model.rows[i];
        if (angular.isArray(row.columns)){
          for (var j=0; j<row.columns.length; j++){
            var col = row.columns[j];
            if (!col.rows){
              column = col;
              break;
            }
          }
        }
        if (column){
          break;
        }
      }
      return column;
    }

    /**
     * Adds the widget to first column of the model.
     *
     * @param dashboard model
     * @param widget to add to model
     * @param name name of the dashboard
     */
    function addNewWidgetToModel(model, widget, name){
      if (model){
        var column = findFirstWidgetColumn(model);
        if (column){
          if (!column.widgets){
            column.widgets = [];
          }
          column.widgets.unshift(widget);

          // broadcast added event
          $rootScope.$broadcast('adfWidgetAdded', name, model, widget);
        } else {
          $log.error('could not find first widget column');
        }
      } else {
        $log.error('model is undefined');
      }
    }

    /**
     * Checks if the edit mode of the widget should be opened immediately.
     *
     * @param widget type
     */
    function isEditModeImmediate(type){
      var widget = dashboard.widgets[type];
      return widget && widget.edit && widget.edit.immediate;
    }

    /**
     * Opens the edit mode of the specified widget.
     *
     * @param dashboard scope
     * @param widget
     */
    function openEditMode($scope, widget){
      // wait some time before fire enter edit mode event
      $timeout(function(){
        $scope.$broadcast('adfWidgetEnterEditMode', widget);
      }, 200);
    }

    /**
     * Splits an object into an array multiple objects inside.
     *
     * @param object source object
     * @param size size of array
     *
     * @return array of splitted objects
     */
    function split(object, size) {
      var arr = [];
      var i = 0;
      angular.forEach(object, function(value, key){
        var index = i++ % size;
        if (!arr[index]){
          arr[index] = {};
        }
        arr[index][key] = value;
      });
      return arr;
    }

    /**
     * Creates object with the category name as key and an array of widgets as value.
     *
     * @param widgets array of widgets
     *
     * @return array of categories
     */
    function createCategories(widgets){
      var categories = {};
      angular.forEach(widgets, function(widget, key){
        var category = widget.category;
        // if the widget has no category use a default one
        if (!category){
          category = i18n_message("smartDecisions.diversos");
        }
        // push widget to category array
        if (angular.isUndefined(categories[category])){
          categories[category] = {widgets: {}};
        }
        categories[category].widgets[key] = widget;
      });
      return categories;
    }
    
    function createSmartCategories(widgets, smartReports){
        var categories = {};
        
        if (widgets !== undefined) {
        	angular.forEach(widgets, function(widget, key){
        		var category = widget.category;
        		// if the widget has no category use a default one
        		if (!category){
        			category = i18n_message("smartDecisions.diversos");
        		}
        		// push widget to category array
        		if (angular.isUndefined(categories[category])){
        			categories[category] = {widgets: {}};
        		}
        		categories[category].widgets[key] = widget;
        	});
        }
        
        if (smartReports !== undefined) {
            angular.forEach(smartReports, function(widget, key){
                var category = widget.category;
                // if the widget has no category use a default one
                if (!category){
                  category = i18n_message("smartDecisions.diversos");
                }
                // push widget to category array
                if (angular.isUndefined(categories[category])){
                  categories[category] = {widgets: {}};
                }
                categories[category].widgets[key] = widget;
          });
        }
        
        return categories;
    }

    return {
      replace: true,
      restrict: 'EA',
      transclude : false,
      scope: {
        structure: '@',
        name: '@',
        collapsible: '@',
        editable: '@',
        editMode: '@',
        continuousEditMode: '=',
        maximizable: '@',
        adfModel: '=',
        adfWidgetFilter: '=',
        categories: '@'
      },
      controller: function($scope){
        var model = {};
        var structure = {};
        var widgetFilter = null;
        var structureName = {};
        var name = $scope.name;

        // Watching for changes on adfModel
        $scope.$watch('adfModel', function(oldVal, newVal) {
          // has model changed or is the model attribute not set
          if (newVal !== null || (oldVal === null && newVal === null)) {
            model = $scope.adfModel;
            widgetFilter = $scope.adfWidgetFilter;
            if ( ! model || ! model.rows ){
              structureName = $scope.structure;
              structure = dashboard.structures[structureName];
              if (structure){
                if (model){
                  model.rows = angular.copy(structure).rows;
                } else {
                  model = angular.copy(structure);
                }
                model.structure = structureName;
              } else {
                $log.error( 'could not find structure ' + structureName);
              }
            }

            if (model) {
              if (!model.title){
                model.title = 'Dashboard';
              }
              if (!model.titleTemplateUrl) {
                model.titleTemplateUrl = adfTemplatePath + 'dashboard-title.html';
              }
              $scope.model = model;
            } else {
              $log.error('could not find or create model');
            }
          }
        }, true);

        // edit mode
        $scope.editMode = false;
        $scope.editClass = '';

        //passs translate function from dashboard so we can translate labels inside html templates
        $scope.translate = dashboard.translate;

        function getNewModalScope() {
          var scope = $scope.$new();
          //pass translate function to the new scope so we can translate the labels inside the modal dialog
          scope.translate = dashboard.translate;
          return scope;
        }

        $scope.toggleEditMode = function(){
          $scope.editMode = ! $scope.editMode;
          if ($scope.editMode){
            if (!$scope.continuousEditMode) {
              $scope.modelCopy = angular.copy($scope.adfModel, {});
              $rootScope.$broadcast('adfIsEditMode');
            }
          }

          if (!$scope.editMode){
            $rootScope.$broadcast('adfDashboardChanged', name, model);
          }
        };

        $scope.$on('adfToggleEditMode', function() {
            $scope.toggleEditMode();
        });

        $scope.collapseAll = function(collapseExpandStatus){
          $rootScope.$broadcast('adfDashboardCollapseExpand',{collapseExpandStatus : collapseExpandStatus});
        };

        $scope.cancelEditMode = function(){
          $scope.editMode = false;
          if (!$scope.continuousEditMode) {
            $scope.modelCopy = angular.copy($scope.modelCopy, $scope.adfModel);
          }
          $rootScope.$broadcast('adfDashboardEditsCancelled');
        };

        // edit dashboard settings
        $scope.editDashboardDialog = function(){
          var editDashboardScope = getNewModalScope();
          // create a copy of the title, to avoid changing the title to
          // "dashboard" if the field is empty
          editDashboardScope.copy = {
            title: model.title
          };

          // pass dashboard structure to scope
          editDashboardScope.structures = dashboard.structures;

          // pass split function to scope, to be able to display structures in multiple columns
          editDashboardScope.split = split;

          var adfEditTemplatePath = adfTemplatePath + 'dashboard-edit.html';
          if(model.editTemplateUrl) {
            adfEditTemplatePath = model.editTemplateUrl;
          }
          var instance = $uibModal.open({
            scope: editDashboardScope,
            templateUrl: adfEditTemplatePath,
            backdrop: 'static',
            size: 'lg'
          });
          editDashboardScope.changeStructure = function(name, structure){
            $log.info('change structure to ' + name);
            changeStructure(model, structure);
            if (model.structure !== name){
              model.structure = name;
            }
          };
          editDashboardScope.closeDialog = function(){
            // copy the new title back to the model
            model.title = editDashboardScope.copy.title;
            // close modal and destroy the scope
            instance.close();
            editDashboardScope.$destroy();
          };
        };

        // add widget dialog
        $scope.addWidgetDialog = function(){
          var addScope = getNewModalScope();
          var model = $scope.model;
          var widgets;
          if (angular.isFunction(widgetFilter)){
            widgets = {};
            angular.forEach(dashboard.widgets, function(widget, type){
              if (widgetFilter(widget, type, model)){
                widgets[type] = widget;
              }
            });
          } else {
            widgets = dashboard.widgets;
          }
          addScope.widgets = widgets;
          
          var instance;
          
          var smartReports = {};
          document.formController.fireEvent("loadWidgets", function() {
        	  var widgetsSmartReport = JSON.parse(document.formController.widgetsSmartReport.value);
        	  
        	  if (angular.isFunction(widgetFilter)){
        		  angular.forEach(widgetsSmartReport, function(widget, type){
        			  smartReports[widget.idSmartReport] = angular.copy(dashboard.widgets["smart-report"]);
        			  if (widgetFilter(widget, "smart-report")){
        				  smartReports[widget.idSmartReport] = $.extend({}, smartReports[widget.idSmartReport], widget);
        			  } else {
        				  delete smartReports[widget.idSmartReport];
        			  }
        		  });
        	  }
        	  
        	  if ($.isEmptyObject(smartReports)) {
        		  addScope.smartReports = {};
        	  } else {
        		  addScope.smartReports = smartReports;
        	  }
        	  
              delete widgets["smart-report"];
              
              var widgetsSmartDecisions = JSON.parse(document.formController.widgetsSmartDecisions.value);
              
              _.forEach(widgets, function(widget, key) {
            	  if (widget.idWidget !== undefined) {
            		  if ($.inArray(widget.idWidget, widgetsSmartDecisions) === -1) {
            			  delete widgets[key];
            		  }
            	  }
            	  
              });
              
        	  addScope.widgets = widgets;

              //pass translate function to the new scope so we can translate the labels inside the modal dialog
              addScope.translate = $scope.translate;

              // pass createSmartCategories function to scope, if categories option is enabled
              if ($scope.options.categories){
                $scope.createSmartCategories = createSmartCategories;
              }

              var adfAddTemplatePath = adfTemplatePath + 'widget-add.html';
              if(model.addTemplateUrl) {
                adfAddTemplatePath = model.addTemplateUrl;
              }

              var opts = {
                scope: addScope,
                templateUrl: adfAddTemplatePath,
                backdrop: 'static',
                size: 'lg'
              };

              instance = $uibModal.open(opts);
          });
          
          addScope.addWidgetSmartReport = function(widgetSmartReport){
        	  dashboard.widgets["smart-report"] = widgetSmartReport;
        	  
              var w = {
                type: "smart-report",
                config: createConfiguration("smart-report")
              };
              addNewWidgetToModel(model, w, name);
              // close and destroy
              instance.close();
              addScope.$destroy();

              // check for open edit mode immediately
              if (isEditModeImmediate("smart-report")){
                openEditMode($scope, w);
              }
            };
          
          addScope.addWidget = function(widget){
            var w = {
              type: widget,
              config: createConfiguration(widget)
            };
            addNewWidgetToModel(model, w, name);
            // close and destroy
            instance.close();
            addScope.$destroy();

            // check for open edit mode immediately
            if (isEditModeImmediate(widget)){
              openEditMode($scope, w);
            }
          };
          
          addScope.closeDialog = function(){
            // close and destroy
            instance.close();
            addScope.$destroy();
          };
        };
        
        $scope.isEmpty = function(obj) {
        	for(var prop in obj) {
        		if(obj.hasOwnProperty(prop))
        			return false;
        	}
        	return true;
        };
        
        $scope.addNewWidgetToModel = addNewWidgetToModel;
        
        $scope.$on("widget.fullscreen", function(e, v) {
        	$scope.options.editable = !v;
        });
      },
      link: function ($scope, $element, $attr) {
        // pass options to scope
        var options = {
          name: $attr.name,
          editable: true,
          enableConfirmDelete: stringToBoolean($attr.enableConfirmDelete),
          maximizable: stringToBoolean($attr.maximizable),
          collapsible: stringToBoolean($attr.collapsible),
          categories: stringToBoolean($attr.categories)
        };
        if (angular.isDefined($attr.editable)){
          options.editable = stringToBoolean($attr.editable);
        }
        $scope.options = options;
      },
      templateUrl: adfTemplatePath + 'dashboard.html'
    };
  });
