// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
define(['hp/services/IndexFilterParser',
    'hp/core/UrlFragment',
    'hp/services/Log',
    'jquery'],
function (parser, urlFragment, log) {
"use strict";

    var IndexFilter = (function () {

        var PROP_MAP = {
            // query string property name : REST argument name
            'f_suri' : 'startObjUri',
            'f_euri' : 'endObjUri',
            'f_an' : 'associationName',
            'f_q' : 'userQuery',
            'f_category' : 'category',
            'f_name' : 'name',
            'f_start' : 'start',
            'f_count' : 'count',
            'f_sort' : 'sort'
        };

        // When hasFilterResetFlag flag is set, a call to updateLocation() will add
        // a flag FILTER_RESET to the param list to signal that the user
        // has selected one of the "reset all" filter. The existence of this
        // flag in location prevents the reuse of the old filter stored prior.
        var FILTER_RESET = 'freset';
        var FILTER_RESET_TRUE = FILTER_RESET + '=true';
        var DEFAULT_REFERENCE_URI_PADDING = 5; // The number of padded items for calling REST API with referenceUri

        /**
         * arg is either a location or an IndexFilter to start from
         */
        function IndexFilter(arg) {

            var self = this;
            var data = {}; // {properties: {any prop: value, ...}, PROP_MAP prop: value, ...}
            var defaults = {};
            var hasFilterResetFlag = false;

            function hasResetParam(paramList) {
                return paramList.hasOwnProperty('freset');
            }

            function loadUrlParameters(urlParameters) {
                hasFilterResetFlag = hasResetParam(urlParameters);
                $.each(PROP_MAP, function (queryParam, dataProp) {
                    if (urlParameters.hasOwnProperty(queryParam)) {
                        data[dataProp] = urlParameters[queryParam];
                    }
                });

                if (data.hasOwnProperty('associationName')) {
                    data.hasARelationship = true;
                }

                // If don't have formal properties, use whatever's in the query string
                if (data.userQuery && ! data.properties && ! data.notProperties) {
                    parser.parseQuery(data);
                } else {
                    // otherwise, get the non-formal search terms and generate the
                    // a fresh query with the formal properties
                    parser.parseQueryTerms(data);
                }

                parser.generateQuery(data);
            }

            function arePropertiesDifferent(props1, props2) {
                var result = false;

                // NOTE: It's hard to improve coverage here because
                // we tend to catch differences comparing the user query
                // before we get here

                $.each(props1, function (n1, v1) {
                    if (! props2.hasOwnProperty(n1)) {
                        result = true;
                        return false;
                    } else {
                        var v2 = props2[n1];
                        if ($.isArray(v2) && $.isArray(v1)) {
                            if ($(v1).not(v2).length !== 0 ||
                                $(v2).not(v1).length !== 0) {
                                result = true;
                                return false;
                            }
                        } else if (v1 !== v2) {
                            result = true;
                            return false;
                        }
                    }
                });

                if (! result) {
                    // just to check for missing properties the other way
                    $.each(props2, function (n2, v2) {
                        if (! props1.hasOwnProperty(n2)) {
                            result = true;
                            return false;
                        }
                    });
                }

                return result;
            }

            function initialize() {
                if ('string' === typeof arg) {
                    var urlParameters = urlFragment.getParameters(arg);
                    loadUrlParameters(urlParameters);
                } else if (arg && arg.hasOwnProperty('data')) {
                    $.extend(true, data, arg.data);
                    $.extend(true, defaults, arg.defaults);
                }
            }

            this.updateLocation = function (location) {
                var oldParameters = urlFragment.getParameters(location);
                var newParameters = {};

                // add non-filter parameters
                $.each(oldParameters, function (name, value) {
                    if (! name.match(/^f_/)) {
                        // not a filter, keep it
                        newParameters[name] = value;
                    }
                });

                // add formal properties
                $.each(PROP_MAP, function (queryParam, dataProp) {
                    if (data.hasOwnProperty(dataProp) &&
                        data[dataProp] && data[dataProp].length &&
                        data[dataProp].length > 0) {
                        if ('category' !== dataProp &&
                            (! defaults.hasOwnProperty(dataProp) ||
                            defaults[dataProp] !== data[dataProp])) {
                            newParameters[queryParam] = data[dataProp];
                        }
                    }
                });

                if (hasFilterResetFlag) {
                    newParameters[FILTER_RESET] = 'true';
                } else {
                    delete newParameters[FILTER_RESET];
                }

                return urlFragment.replaceParameters(location, newParameters);
            };

            // This method is used to support property that has array values (e.g. category)
            // The two values to be compared are equal if one of the follow conditions are met:
            // - Both are arrays and they contain the same elements
            // - Both are not arrays and their values are equal.
            function valuesAreEqual(v1, v2) {
                var result = false;

                if ($.isArray(v2) && $.isArray(v1)) {
                    if ($(v1).not(v2).length !== 0 ||
                        $(v2).not(v1).length !== 0) {
                        result = false;
                    } else {
                        result = true;
                    }
                } else {
                    result = v1 === v2;
                }

                return result;
            }

            function sameOrDefault(data, data2, defaults,dataProp) {
                return (
                    (data.hasOwnProperty(dataProp) &&
                        data2.hasOwnProperty(dataProp) &&
                        valuesAreEqual(data[dataProp], data2[dataProp])) ||
                    (!data.hasOwnProperty(dataProp) &&
                        !data2.hasOwnProperty(dataProp)) ||         // Same
                    (defaults.hasOwnProperty(dataProp) &&
                        valuesAreEqual(defaults[dataProp], data[dataProp]) &&
                        !data2.hasOwnProperty(dataProp))            // Default
                );
            }

            function compareProperties(props1, props2) {
                var result = false;
                if (! props1 && ! props2) {
                    // same
                } else if (props1 && props2) {
                    // check all properties
                    if (arePropertiesDifferent(props1, props2)) {
                        result = true;
                    }
                } else {
                    // one has, one doesn't
                    result = true;
                }
                return result;
            }

            this.isDifferent = function (filter2) {
                var result = (filter2 === null || typeof filter2 === 'undefined');
                var data2;

                if (result && ! data.properties && ! data.notProperties &&
                    ! data.hiddenProperties && ! data.hiddenNotProperties) {
                    result = false;
                    // filter2 doesn't have anything, see if all this has is defaults
                    $.each(PROP_MAP, function (queryParam, dataProp) {
                        if (data.hasOwnProperty(dataProp) &&
                            (!defaults.hasOwnProperty(dataProp) ||
                            defaults[dataProp] !== data[dataProp])) {
                            result = true;
                            return true;
                        }
                    });
                }

                if (! result && filter2) {
                    data2 = filter2.data;
                    // check formal filter properties
                    $.each(PROP_MAP, function (queryParam, dataProp) {
                        if (!sameOrDefault(data, data2, defaults, dataProp)) {
                            result = true;
                            return false;
                        }
                    });
                }

                if (! result && data2) {
                    // check variable filter properties
                    result = compareProperties(data.properties, data2.properties);
                    if (! result) {
                        result = compareProperties(data.notProperties,
                            data2.notProperties);
                    }
                    if (! result) {
                        result = compareProperties(data.hiddenProperties,
                            data2.hiddenProperties);
                    }
                    if (! result) {
                        result = compareProperties(data.hiddenNotProperties,
                            data2.hiddenNotProperties);
                    }
                }

                return result;
            };

            this.isCustom = function () {
                return (data.hasOwnProperty('userQuery') ||
                    data.hasOwnProperty('associationName') ||
                    data.hasOwnProperty('properties') ||
                    data.hasOwnProperty('notProperties'));
            };

            this.getUserQuery = function () {
                return data.userQuery || '';
            };

            this.setUserQuery = function (query) {
                data.userQuery = query;
                parser.parseQuery(data);
                parser.generateQuery(data);
            };

            this.getReferrerUri = function () {
                if (data.hasOwnProperty('startObjUri')) {
                    return data.startObjUri;
                } else if (data.hasOwnProperty('endObjUri')) {
                    return data.endObjUri;
                } else {
                    return null;
                }
            };

            this.setProperty = function (name, value) {
                // backward compatibility until Fusion removes CicNotificationResource_DO_NOT_MODIFY.js
                if (value.slice(0, 4) === 'NOT ') {
                    return self.setNotProperty(name, value.slice(4));
                } else {
                    var result = true;
                    if (! data.properties) {
                        data.properties = {};
                    }
                    if (data.properties[name] === value) {
                        result = false;
                    } else {
                        data.properties[name] = value;
                        parser.generateQuery(data);
                    }
                    return result;
                }
            };

            this.unsetProperty = function (name) {
                var result = false;
                if (data.properties) {
                    if (data.properties.hasOwnProperty(name)) {
                        delete data.properties[name];
                        result = true;
                        parser.generateQuery(data);
                    }
                }
                return result;
            };

            this.getProperty = function (name) {
                if (data.properties) {
                    return data.properties[name];
                } else {
                    return undefined;
                }
            };

            this.setProperties = function (properties) {
                data.properties = properties;
                parser.generateQuery(data);
            };

            this.setNotProperty = function (name, value) {
                var result = true;
                if (! data.notProperties) {
                    data.notProperties = {};
                }
                if (data.notProperties[name] === value) {
                    result = false;
                } else {
                    data.notProperties[name] = value;
                    parser.generateQuery(data);
                }
                return result;
            };

            // when you don't want the user to see it, but you need it
            this.setHiddenProperty = function (name, value) {
                var result = true;
                if (! data.hiddenProperties) {
                    data.hiddenProperties = {};
                }
                if (data.hiddenProperties[name] === value) {
                    result = false;
                } else {
                    data.hiddenProperties[name] = value;
                }
                return result;
            };
           
            this.unsetHiddenProperty = function (name) {
                var result = false;
                if (data.hiddenProperties) {
                    if (data.hiddenProperties.hasOwnProperty(name)) {
                        delete data.hiddenProperties[name];
                        result = true;
                    }
                }
                return result;
            };

            this.setHiddenNotProperty = function (name, value) {
                var result = true;
                if (! data.hiddenNotProperties) {
                    data.hiddenNotProperties = {};
                }
                if (data.hiddenNotProperties[name] === value) {
                    result = false;
                } else {
                    data.hiddenNotProperties[name] = value;
                }
                return result;
            };

            this.getHiddenProperty = function (name) {
                if ( data.hiddenProperties && data.hiddenProperties.hasOwnProperty(name) ) {
                    return data.hiddenProperties[name];
                }
                return null;
            };
            
            this.setSort = function (propertyName, direction) {
                if (propertyName) {
                    // allow for string args or single object
                    if ('string' === typeof propertyName) {
                        data.sort = propertyName + ':' + direction;
                    } else {
                        data.sort = propertyName.name + ':' +
                            propertyName.direction;
                    }
                } else {
                    delete data.sort;
                }
            };

            this.getSort = function () {
                if (data.sort) {
                    var parts = data.sort.split(':');
                    return {name: parts[0], direction: parts[1]};
                } else {
                    return null;
                }
            };

            this.bumpCount = function () {
                if(data.defaultCount) {
                    data.count += data.defaultCount;
                } else {
                    data.count += defaults.count;
                }
            };

            /**
             * Set the 'start' property after decrementing its value by 'count'.
             * If padding exists, it is also incremented by the same amount. This
             * will maintain the position of the referenceUri within the index results.
             * This method is used for loading an extra page above the current set.
             *
             * @param {int} start The 'start' index of the last index result
             */
            this.decrementStart = function (start) {
                var pageSize = data.defaultCount ? data.defaultCount : defaults.count;
                var prevStart = data.start;
                start -= pageSize;
                data.start = (start < 0) ? 0 : start;
                if (self.hasReferenceUri()) {
                    data.padding += prevStart - data.start;
                }
            };

            this.resetCount = function () {
                if (defaults.hasOwnProperty('count') && data.count !== defaults.count) {
                    data.count = defaults.count;
                    if (self.hasReferenceUri()) {
                        data.padding = DEFAULT_REFERENCE_URI_PADDING;
                    }
                    return true;
                }
                return false;
            };

            this.useCount = function (filter2) {
                if (filter2 && (! self.data.count ||
                    (filter2.data.count > self.data.count))) {
                    self.data.count = filter2.data.count;
                }
                if (filter2 && filter2.defaults &&
                    (! self.defaults.hasOwnProperty('count'))) {
                    self.defaults.count = filter2.defaults.count;
                }
            };

            /**
             * Simular to useCount, this method set the "start" property
             * based on the supplied filter
             *
             * @param {IndexFilter} filter2 The filter from which the start property is copied
             */
            this.useStart = function (filter2) {
                if (filter2 && (! self.data.start)) {
                    self.data.start = filter2.data.start;
                }
                if (filter2 && filter2.defaults &&
                    (! self.defaults.hasOwnProperty('start'))) {
                    self.defaults.start = filter2.defaults.start;
                }
            };

            /**
             * Check to see if this filter contains referenceUri and padding properties.
             *
             * @return True if referenceUri and padding exist. False otherwise.
             */
            this.hasReferenceUri = function () {
                return (self.data.referenceUri &&
                        self.data.padding !== undefined &&
                        self.data.padding !== null);
            };

            /**
             * Set or unset this filter to use referenceUri.
             *
             * @param {object} pageOpt If null, remove referenceUri and padding properties.
             *        Otherwise, it can contain the following properties:
             *
             *        uri: specifies referenceUri.
             *        padding: specifies padding if referenceUri is set.
             */
            this.setReferenceUri = function (pageOpt) {
                if (!pageOpt) {
                    delete self.data.referenceUri;
                    delete self.data.padding;
                } else {
                    if (pageOpt.uri) {
                        self.data.referenceUri = pageOpt.uri;
                        self.data.padding = DEFAULT_REFERENCE_URI_PADDING;
                    }
                    if (pageOpt.padding && self.data.referenceUri) {  
                            self.data.padding = pageOpt.padding;
                    }
                    if (pageOpt.padding > self.data.count) {
                        // padding can never be bigger count
                        pageOpt.padding = self.data.count;
                    }
                }
            };

            /**
             * Retrieve the referenceUri if it exists
             */
            this.getReferenceUri = function() {
                return self.data.referenceUri;
            };

            // @private, for IndexService and Resource

            this.data = data;
            this.defaults = defaults;

            this.ensureDefaults = function (category, start, count) {
                if (category) {
                    defaults.category = category;
                }
                defaults.start = start;
                defaults.count = count;

                if (data.properties && data.properties.hasOwnProperty('category')) {
                    data.category = data.properties.category;
                }
                if (category && ! data.hasOwnProperty('category')) {
                    data.category = category;
                }
                if (! data.hasOwnProperty('start')) {
                    data.start = start;
                }
                if (! data.hasOwnProperty('count')) {
                    data.count = count;
                }
            };

            this.hasResetFlag = function () {
                return hasFilterResetFlag;
            };

            this.unsetResetFlag = function () {
                hasFilterResetFlag = false;
            };

            this.setResetFlag = function () {
                hasFilterResetFlag = true;
            };

            this.reset = function () {
                data.properties = {};
                data.notProperties = {};
                delete data.startObjUri;
                delete data.endObjUri;
                delete data.associationName;
                delete data.userQuery;
                delete data.terms;
            };

            /**
             * Reset this filter to all its defaults.
             *
             * @param {object} option If null, reset everything.
             *        Otherwise, it can contain properties. If the
             *        corresponding property is found in the "defaults"
             *        object, then the property value is reset to default.
             */
            this.resetToDefaults = function (options) {
                if (options) {
                    // reset selected properties to their default values
                    for (var property in options) {
                        if (defaults.hasOwnProperty(property)) {
                            data[property] = defaults[property];
                        }
                    }
                } else {
                    // reset everything to its default value
                    self.reset();
                    data.category = defaults.category;
                    data.start = defaults.start;
                    data.count = defaults.count;
                }
            };

            this.resetCategory = function () {
                data.category = defaults.category;
            };

            // Reset this filter using the info in location and userQuery to
            // prepare for a local search. See SearchBoxPresenter.
            this.updateLocalSearchQuery = function (location, userQuery) {
                var parameters = urlFragment.getParameters(location);
                if (userQuery && userQuery.length > 0) {
                    parameters.f_q = userQuery;
                    // clear uris on filter change
                    location = urlFragment.replaceUris(location, []);
                } else {
                    delete parameters.f_q;
                }

                if (!userQuery || userQuery.length === 0) {
                    parameters = [FILTER_RESET_TRUE];
                }

                // reset this filter
                data={};
                defaults={};
                location = urlFragment.replaceParameters(location, parameters);
                loadUrlParameters(urlFragment.getParameters(location));
            };

            this.copyHidden = function (filter2) {
                if (filter2) {
                    var data2 = filter2.data;
                    if (data2.hiddenProperties) {
                        for (name in data2.hiddenProperties) {
                            if (! data.hiddenProperties) {
                                data.hiddenProperties = {};
                            }
                            // copy if not set already
                            if (! data.hiddenProperties.hasOwnProperty(name)) {
                                data.hiddenProperties[name] =
                                    data2.hiddenProperties[name];
                            }
                        }
                    }
                    if (filter2.data.hiddenNotProperties) {
                        for (name in data2.hiddenNotProperties) {
                            if (! data.hiddenNotProperties) {
                                data.hiddenNotProperties = {};
                            }
                            // copy if not set already
                            if (! data.hiddenNotProperties.hasOwnProperty(name)) {
                                data.hiddenNotProperties[name] =
                                    data2.hiddenNotProperties[name];
                            }
                        }
                    }
                }
            };

            // Convert to IndexService REST query string parameters
            this.toRESTParameters = function (version) {
                return parser.toRESTParameters(data, version);
            };

            function matchTerm(value, term) {
                var result = false;
                if (value) {
                    if ('string' === typeof value) {
                        result = (value.indexOf(term) !== -1);
                    } else if ('number' === typeof value) {
                        result = (value === parseInt(term, 10));
                    }
                }
                return result;
            }

            /**
             * Helper function for search page to determine what to show matches.
             */
            this.match = function (name, value) {
                var result = false;
                if (data.properties && data.properties[name]) {
                    result = (value.toLowerCase().indexOf(
                        data.properties[name].toLowerCase()) !== -1);
                }
                if (! result) {
                    $.each(data.terms, function (index, term) {
                        if (matchTerm(value, term)) {
                            result = true;
                            return false;
                        }
                    });
                }
                return result;
            };

            this.toString = function () {
                return parser.toString(data, defaults, PROP_MAP);
            };

            initialize();
        }

        return IndexFilter;
    }());

    return IndexFilter;
});
