/*jslint node: true */
/*jslint browser: true*/
/*jslint nomen: true*/
/*global angular, BaseController, CFE_mapping, defaultSQL, STRING_CONDITION, NUMERIC_CONDITION, LIST_CONDITION, DATE_CONDITION, CONTEXT_CONDITION, FIELD_TYPES  */
'use strict';

/**
 *  Directive
 *
 * Use of Class.js
 *
 */
var inventoryFilterDirectiveCtrl = BaseController.extend({
    init: function ($scope, $elm, $attrs, $q, inventoryHelper, inventoryDataService, $location, personalGroupsService, sharedGroupsService, inventoryFilterService) {
        var self = this;
        this._super($scope);
        this._element = $elm;
        this._attrs   = $attrs;
        this._$q      = $q;
        this._inventoryHelper = inventoryHelper;
        this._inventoryDataService = inventoryDataService;
        this.$location = $location;
        this.personalGroupsService = personalGroupsService;
        this.sharedGroupsService = sharedGroupsService;
        self.$scope.inventoryFilterService = inventoryFilterService;

        self.$scope.filters = []; // all selected filters collected here, and only after user click apply we will move them to the SQL filter

        self.$scope.defaultSQL = defaultSQL;

        self.$scope.STRING_CONDITION  = STRING_CONDITION;
        self.$scope.NUMERIC_CONDITION = NUMERIC_CONDITION;
        self.$scope.LIST_CONDITION    = LIST_CONDITION;
        self.$scope.DATE_CONDITION    = DATE_CONDITION;
        self.$scope.CONTEXT_CONDITION = CONTEXT_CONDITION;

        self.$scope.FIELD_TYPES       = FIELD_TYPES;

        self.$scope.openedDate = []; // for data field

        self._initInventoryObject();
        self._prepareInventoryVariables(self.$scope.cfemappingaddfields, self.$scope.cfemappingremovefields, self.$scope.cfemappingaddtofilters);
        $scope.data = {};
        this.defaultClassList = DefaultInventoryFilterClasses;
        self.$scope.data.classes = this.defaultClassList;
        $scope.CfeClassRegex = /^\w+$/;
    },

    defineScope: function () {
        var self = this;
        self.$scope.addFilter    = this.addFilter.bind(this);
        self.$scope.applyFilter  = this.applyFilter.bind(this);
        self.$scope.updateFilterState = this.updateFilterState.bind(this);
        self.$scope.regexValidation = this.regexValidation.bind(this);
        self.$scope.removeFilter      = this.removeFilter.bind(this);
        self.$scope.clearAll     = this.clearAll.bind(this);
        self.$scope.maybeRunReport = function () {
            if (!self.$scope.showfilterapplybtn && self.$scope.runReport) {
                self.$scope.runReport();
            }
        };
    },

    defineListeners: function () {
        var self = this;

        self.$scope.$on("clearAll_EVENT", function (event) {
            self.clearAll();
        });

        self.$scope.$on("runDefaultReport_EVENT", function (event, columns) {
            self._runDefaultReport(columns);
        });

        self.$scope.$on("modifyColumn_EVENT", function (event, column, pos) {
            self._modifyColumn(column, pos);
        });

        self.$scope.$on("modifyColumns_EVENT", function (event, columns) {
            self._modifyColumns(columns);
        });

        self.$scope.$on("environmentFilter_EVENT", function (event) {
            self.applyFilter();
        });

        self.$scope.$on("applyInvFilters_EVENT", function (event) {
            self._updateSQLAfterFilterChange();
        });

        self.$scope.$watch('sql', function (sql) {
            self.$scope.sql = sql;
            self.$scope.filters = !sql.filters || sql.filters.length == 0 ? [{}] : sql.filters;
        }, true);

        self.$scope.$on('inventoryDirectiveReady_EVENT', function () {
            if (self.$scope.sql.hasOwnProperty('group') && !self.$scope.selectedGroup) {
                self.$scope.selectedGroup = self.$scope.sql.group;
            }
        });

        self.$scope.$watch('data.searchFilter', function (searchFilter) {
            if (searchFilter !== undefined && searchFilter.length > 0) {
                self._inventoryDataService.getClasses(searchFilter).then(function (apiResponse) {
                    // include default classes in the search results because they may not be reported
                    self.$scope.data.classes = [
                        ...apiResponse.data.map(item => item[0]), // classes from the API
                        ...self.defaultClassList.filter(item => item.includes(searchFilter)) // default classes
                    ];
                    self.$scope.searchExactMatch = self.$scope.data.classes.some(item => item === searchFilter);
                })
                self.$scope.searchIsValidClass = searchFilter.match(self.$scope.CfeClassRegex);
            } else {
                self.$scope.data.classes = self.defaultClassList;
            }
        })

        self.$scope.$on("reportLoaded_EVENT", function (event, SQL) {
            self.$scope.sql = SQL;

            self.$scope.filters = SQL.filters;

            self._runQuery();
        });

        self.$scope.$on("updateVariablesDictionary_EVENT", function(event) {
            self._updateVariablesDictionary();
        });

        self.$scope.$on("getInventoryAlertData_EVENT", function(event, defaultColumns) {
            self.$scope.sql.columns = [];

            // refresh filters, this will also remove empty filters and set sql.filters
            self._updateSQLAfterFilterChange(); // this will set all properties to SQL object

            // for inventory report we set columns = filter, and remove duplicates
            angular.forEach(self.$scope.sql.filters, function(column, index) {
                var pos = self._findIndexInObjectByAttributeName(self.$scope.sql.columns, column.attribute_name);
                if (pos === -1) {
                    //unset attr which we don't need
                    var tmp = angular.copy(column);
                    delete (tmp.condition);
                    delete (tmp.value);
                    self.$scope.sql.columns.push(tmp);
                }
            });

            // append default col - usually hostname
            angular.forEach(defaultColumns, function(column, index) {
                var pos = self._findIndexInObjectByAttributeName(self.$scope.sql.columns, column.attribute_name);
                if (pos === -1) {
                 self.$scope.sql.columns.unshift(column);
                }
            });
        });
        self.$scope.$watch('selectedGroup', group => {
            if (!group) {
                self.$scope.selectedGroupData = {};
                return;
            }
            const groupService = group.type === 'shared' ? self.sharedGroupsService : self.personalGroupsService;
            groupService.get(group.id).then(response => {
                self.$scope.selectedGroupData = response.data;
            })
        });


        self.$scope.$watch('selectedGroupData', async selectedGroupData => {
            if (!selectedGroupData || !selectedGroupData?.filter) {
                return;
            }

            self.$scope.sql.group = self.$scope.selectedGroup;
            self.$scope.sql.filters = await self.$scope.inventoryFilterService.convertFilterObjectToFilters(selectedGroupData?.filter, self.$scope.ui.CFE_mapping);
            self.$scope.sql.entriesFilter = (selectedGroupData.filter.hasOwnProperty('hostsFilter')) ?
                self.$scope.inventoryFilterService.hostsFilterToEntries(selectedGroupData.filter.hostsFilter) :
                {
                    ...{includes: (selectedGroupData.filter.hostFilter?.includes?.entries || {})},
                    ...{excludes: (selectedGroupData.filter.hostFilter?.excludes?.entries || {})}
                };
            self.$scope.$apply();
            this.applyFilter();
        });
    },



////////////////////////////////////////////////////////////////////////////////
//
//                  UI - related
//
////////////////////////////////////////////////////////////////////////////////


    /**
     * Get an element from filters array and add it to the filters.
     * set condition properties
     *
     *@param {object} field
     */
    addFilter: function(field, index) {
        var self = this;
        if (angular.equals({}, field)) {
            return;
        }

        var filterField = {};
        angular.copy(field, filterField);
        self.$scope.filters = self.$scope.filters ? self.$scope.filters : [];
        // check for software fields: combine as one field
        if (field.attribute_name === 'Software installed' || field.attribute_name === 'Software version') {

            var tmpField  = {};
            filterField['type']   = 'software_group';
            filterField['fields'] = {};

            // 1. get software name field
            tmpField = self._findObjectByAttributeName(self.$scope.ui.InventoryItems.filters.softwareFilter, 'Software installed');

            if (!angular.equals({}, tmpField)) {
                filterField['fields']['softwarename'] = angular.copy(tmpField);
            }

            // 2. get Software version  field
            tmpField = self._findObjectByAttributeName(self.$scope.ui.InventoryItems.filters.softwareFilter, 'Software version');

            if (!angular.equals({}, tmpField)) {
                filterField['fields']['softwareversion'] = angular.copy(tmpField);
            }
        }

        if (index !== undefined) {
            // preserve previous value
            let previousValue = angular.copy(self.$scope.filters[index].value);
            // in case of int or real convert value to number
            if (filterField.type === 'int' || filterField.type === 'real') {
                let parsedValue = parseFloat(previousValue);
                previousValue = isNaN(parsedValue) ? '' : parsedValue;
                filterField.value = previousValue;
            } else if (filterField.type === 'class' || self.$scope.inventoryFilterService.isAttributeSearchQuery(filterField.attribute_name)) {
                filterField.value = field.value;
            } else {
                filterField.value = previousValue;
            }

            // overwrite
            self.$scope.filters[index] = filterField;

            // set default condition
            if (!self.$scope.filters[index].condition) {
                self.$scope.filters[index].condition = self._setDefaultCondition(field.type);
            }
        } else {
            // add new filter
            self.$scope.filters.push(filterField);
        }
        this.updateFilterState();
    },

    _setDefaultCondition: function(type) {
        var self = this;
        var defaultCondition;
        switch (type) {
            case 'slist':
                defaultCondition = self.$scope.LIST_CONDITION[0].value;
                break;
            case 'string':
                defaultCondition = self.$scope.STRING_CONDITION[0].value;
                break;
            case 'date':
                defaultCondition = self.$scope.DATE_CONDITION[0].value;
                break;
            case 'context':
                defaultCondition = self.$scope.CONTEXT_CONDITION[0].value;
                break;
            case 'int':
            case 'real':
                defaultCondition = self.$scope.NUMERIC_CONDITION[0].value;
                break;
            default:
                defaultCondition = self.$scope.STRING_CONDITION[0].value;
                break

        }

        return defaultCondition;
    },

    /**
     * Remove filter
     * @param {int} idx index
     */
    removeFilter: function(idx) {
        var self = this;
        var key = self.$scope.filters[idx].attribute_name;
        
        //remove filter from URL when filter removed on the UI
        self.$location.search('filter', null)

        self.$scope.filters.splice(idx, 1);

        self.$scope.sql.filters = angular.copy(self.$scope.filters);

        if (self.$scope.filters.length === 0) {
            self._updateSQLAfterFilterChange(); // clear filter
            self._runQuery();
        }
    },

    /**
     * Apply filters
     */
    applyFilter: function () {
        this._updateSQLAfterFilterChange();
        this._runQuery();
        this.$scope.showAllFilters = false;
    },

    /**
     * Clear all fields and checkboxes in form
     * Clear SQL string
     */
    clearAll: function() {
        var self = this;
        
        self.$scope.filters     = [];
        self.$scope.sql.filters = [];
        self.$scope.sql.group = null;
        self.$scope.sql.entriesFilter = {includes: {}, excludes: {}};
        self.$scope.selectedGroup = undefined;
        self.$scope.selectedGroupData = {};

        angular.forEach(self.$scope.hardwareGroup, function(col) {
            col.selected = false;
        });
        angular.forEach(self.$scope.softwareGroup, function(col) {
            col.selected = false;
        });
        angular.forEach(self.$scope.networkGroup, function(col) {
            col.selected = false;
        });
        angular.forEach(self.$scope.noGroupGroup, function(col) {
            col.selected = false;
        });
        angular.forEach(self.$scope.noCategoryGroup, function(col) {
            col.selected = false;
        });
        self._updateSQLAfterFilterChange();

        setTimeout(() => self.$scope.runReport(), 500);
    },

    /**
     * Used in datablicker for blur event
     */
    updateFilterState: function(currentFilter) {
        var self = this;
        self.$scope.filters = self.$scope.filters.map(function (filter) {
            if (filter.value == undefined) {
                filter.value = '';
            }
            return filter;
        })

        if(currentFilter){
            var newFilter = $.grep(self.$scope.filters, function (filter) {
                return filter.id_key == currentFilter.id_key;
            })[0];
            self._inventoryDataService.updateVariable(newFilter);
        }
        self._updateSQLAfterFilterChange();
        self.$scope.$emit('INV_FILTER_CHANGED');
    },

    regexValidation: function(formElement, filter) {

        let self = this;
        clearTimeout(self.timeOut);
        clearTimeout(self.timeOutRemoveBlock);

        self.templates = {
            inProgress: `<span class="warning"><span class="icon-spinner icon-spin"></span> Validating regular expression</span>`,
            succeed : `<span class="green"><span class="icon-ok"></span> Validation successful</span>`,
            failed: `<span class="red"<span class="icon-warning-sign"></span> error_msg</span>`
        };

        const inventoryHelpBlock =  $(formElement.$$element).next('.inventory_help');

        if (filter.condition.indexOf('regex') == 0 && formElement.$viewValue) {
            // show message about validation when user changing value
            inventoryHelpBlock.html(self.templates.inProgress).show();
        }

        const validationFn = (formElement, filter, inventoryHelpBlock, self) => {
        const value = formElement.$viewValue;
        if (filter.condition.indexOf('regex') == 0 && formElement.$viewValue) {
            let params = {
                select: ['Host name'],
                filter: {}
            };
            params['filter'][filter.label] = {};
            params['filter'][filter.label][filter.condition] = value

            // clear interval, otherwise validation help block will be blinking when typing
            return this._inventoryDataService.getData(params, {"hide-spinner": true}).then(
                function () {
                    formElement.$setValidity('regex', true);
                    inventoryHelpBlock.html(self.templates.succeed);
                    self.timeOutRemoveBlock = setTimeout(() => $('.inventory_help .green').hide(), 2000);
                },
                function (error) {
                    clearTimeout(self.timeOutRemoveBlock);
                    const extractedError = error.data.substr(error.data.indexOf('invalid regular expression'))
                        || 'Regex validation error';
                    formElement.$setValidity('regex', false);
                    // set error message
                    inventoryHelpBlock.html(self.templates.failed.replace('error_msg', extractedError.capitalizeFirstLetter()));
                }
            );
        } else {
            formElement.$setValidity('regex', true);
        }
    }
    self.timeOut = setTimeout(() => validationFn(formElement, filter, inventoryHelpBlock, self), 500);
    },

    /**
     * Emit event to run query
     */
    _runQuery: function() {
        var self = this;
        self.$scope.$emit('runQuery_EVENT');
    },


////////////////////////////////////////////////////////////////////////////////
//
//                  Utils
//
////////////////////////////////////////////////////////////////////////////////

    /**
     *  Update SQL accordingly to selected filters
     *
     *  it will also remove all filters which are empty
     */
    _updateSQLAfterFilterChange: function() {
        var self = this;
        var tmpFilter = new Array();

        // cleanup filters - remove filters without value
        if (self.$scope.filters) {
            angular.forEach(self.$scope.filters, function(filter, index) {
                //ignore empty items
                if (filter.type!== 'software_group') {
                    if (filter.value != null && filter.condition) {
                        tmpFilter.push(filter);
                    }
                } else {
                    // if softwarename is empty - do not use that filter
                    if (filter.fields['softwarename']['value'] && filter.fields['softwarename']['condition']) {
                        tmpFilter.push(filter);
                    }
                }
            });
            self.$scope.filters = tmpFilter;
        }


        self.$scope.sql.filters = angular.copy(self.$scope.filters);
        
        self._updateSessionStorage();
    },

    _updateSessionStorage: function () {
        //don't update session storage if report has no columns
        if (this.$scope.sql.columns !== undefined && this.$scope.sql.columns.length > 0) {
            sessionStorage.setItem('inventory_sql', JSON.stringify(this.$scope.sql));
        }
    },

    /**
     * Update variables dictionary. Usually called on save report
     */
    _updateVariablesDictionary: function() {
        var self = this, variablesList = {};
        if (self.$scope.sql.filters) {

            angular.forEach(self.$scope.sql.filters, function(field, index) {
                if (field.readonly !== "1") { // do not update readonly fields
                    variablesList[field.attribute_name] = variablesList[field.attribute_name] || {};
                    variablesList[field.attribute_name] = field;
                }
            });
        }
        if (!angular.equals({}, variablesList)) {
            var data = angular.toJson(variablesList);
            self._inventoryHelper.updateVariablesDictionary(data).then(
                    function(result) {
                        // do nothing
                    },
                    function(error) {
                        error.data[0].text = error.data[0].text || 'Error. Unable to update variable type';
                        self._notificationService.setNotification('error', error.data);

                    }
            );
        }
    },

    /**
     * Run default report - no filtering only preselected columns
     *
     * @param {array} defaultColumns - array of default columns, format - see DCA
     */
    _runDefaultReport: function(defaultColumns) {
        var self = this, isFound = false;
        if (defaultColumns) {
            self.$scope.sql.columns = [];
            angular.forEach(defaultColumns, function(label) {
                 angular.forEach(self.$scope.ui.InventoryItems.grouped, function(groupField, groupIndex) {
                    if (groupField.label === label)
                    {
                        // mark field as checked
                        groupField.selected = true;
                        self.$scope.sql.columns.push(groupField);
                        isFound = true;
                    }
                });
            });
        }

        if (isFound === true) {
            self._runQuery();
        }
    },

    /**
     * When someone selected new column - re-run query  this function usually called from event
     * @param {object} field
     */
    _modifyColumn: function (col, position) {
        var self = this;
        var pos = -1;
        var field, isInInventoryItems = false;

        angular.forEach(self.$scope.ui.InventoryItems.grouped, function (groupField) {
            if (groupField.label === col) {
                // mark field as checked
                field = groupField;
                isInInventoryItems = true;
                pos = self._findIndexInObjectByAttributeName(self.$scope.sql.columns, field.attribute_name);

                if (pos !== -1) {
                    self._removeSqlColumn(field, pos);
                } else {
                    var newPos = position ? position : self.$scope.sql.columns.length;
                    self.$scope.sql.columns.splice(newPos, 0, field);
                }

                self._updateSqlQueries();
            }
        });

        if (!isInInventoryItems) {
            angular.forEach(self.$scope.sql.columns, function (column, index) {
                if ((col === column.attribute_name) || (('attribute_name=' + col) === column.attribute_name)) {
                    self._removeSqlColumn(column, index);
                    self._updateSqlQueries();
                }
            });
        }
    },

    /**
     * Modify all columns
     * @param {array} columns
     */
    _modifyColumns: function (columns) {
        let self = this;

        self.$scope.sql.columns = [];
        angular.forEach(columns, function (label) {
            angular.forEach(self.$scope.ui.InventoryItems.grouped, function (groupField) {
                if (groupField.label === label) {
                    groupField.selected = true;
                    self.$scope.sql.columns.push(groupField);
                }
            });
        });
        self._updateSqlQueries();
    },

    /**
     * Remove sql column
     *
     * @param {object} column
     * @param {integer} index
     * @private
     */
    _removeSqlColumn: function (column, index) {
        var self = this;
        self.$scope.sql.columns.splice(index, 1);
    },

    /**
     * Updates sql queries
     * @private
     */
    _updateSqlQueries: function () {
        var self = this;
        
        self._updateSessionStorage();
        self._runQuery();
    },

    /**
     * Search for item in object by attribute_name
     *
     * @param {object} object
     * @param {string} attribute_name
     *
     * @return {object} item if empty object
     */
    _findObjectByAttributeName: function(object, attribute_name) {
        var result = {};

        angular.forEach(object, function(groupItem, index) {
            if (groupItem.attribute_name === attribute_name) {
                result = groupItem;
                return;
            }
        });
        return result;
    },

   /**
     * Search for item in object by attribute_name and return index
     *
     * @param {object} object
     * @param {string} attribute_name
     *
     * @return {int} index of element in array or -1
     */
    _findIndexInObjectByAttributeName: function(object, attribute_name) {
        var result = -1;

        angular.forEach(object, function(groupItem, index) {
            if (groupItem.attribute_name === attribute_name) {
                result = index;
                return;
            }
        });
        return result;
    },
    
    /**
     * Initialize inventory object
     */
    _initInventoryObject: function() {
        var self = this;
        //self.$scope.UI = {}; - initialized outside direcive
        self.$scope.ui.SELECT_HEADER_ARRAY    = []; // to convert result based on type, for example remove quotes from slist
        self.$scope.ui.CFE_mapping            = {};
        self.$scope.ui.InventoryItems         = {};
        self.$scope.ui.InventoryItems.groups  = {};
        self.$scope.ui.InventoryItems.filters = {};

        self.$scope.ui.InventoryItems.groups.hardwareGroup   = {};
        self.$scope.ui.InventoryItems.groups.softwareGroup   = {};
        self.$scope.ui.InventoryItems.groups.networkGroup    = {};
        self.$scope.ui.InventoryItems.groups.noCategoryGroup = {};
        self.$scope.ui.InventoryItems.groups.noGroupGroup    = {};
        self.$scope.ui.InventoryItems.grouped = {};

        self.$scope.ui.InventoryItems.filters.hardwareFilter   = {};
        self.$scope.ui.InventoryItems.filters.softwareFilter   = {};
        self.$scope.ui.InventoryItems.filters.networkFilter    = {};
        self.$scope.ui.InventoryItems.filters.noCategoryFilter = {};
        self.$scope.ui.InventoryItems.filters.noGroupFilter    = {};
    },

    /**
     * Load variables from DB
     */


    _prepareInventoryVariables: function(addFields, removeFields, addFilters) {
        var self = this, deferred = self._$q.defer();

        self._inventoryHelper.getInventoryVariables(addFields, removeFields, addFilters).then(
                function(result) {
                    // extend scope with new variables
                    angular.extend(self.$scope.ui, result);
                    deferred.resolve();
                    self.$scope.$emit('inventory_variables_loaded', result); // notify that we loaded all variables
                    self.$scope.$emit('inventoryDirectiveReady_EVENT'); // notify that we loaded all variables
                },
                function(error) {
                    // error handled in helper
                    deferred.reject(error);
                }
        );
        return deferred.promise;
    }
});

window.inventoryFilterDirectiveCtrl = inventoryFilterDirectiveCtrl;
