/*  component-location-service  */

/**
 * Service that provides wrappers for geolocation API
 * @module locationService
 * @param  {jQuery} $ Instance of jQuery
 * @return {Object} List of location service methods
 */
XA.component.locationService = (function ($, document) {

    "use strict";
    /**
    * This object stores all public api methods.
    * @type {Object.<Methods>}
    * @memberOf module:locationService
    */
    var api = {},
        getCurrentLocation,
        getLocationOptions,
        detectBrowser,
        gettingLocation = false,
        errorCallbacks = [],
        successCallbacks = [],
        reportError;

    /**
    * getCurrentLocation method call geolocation.getCurrentPosition API and call callback functions
    * @memberOf module:locationService
    * @param {Function} success function callback that calls in case of positive answer from geolocation API
    * @param {Function} error function callback that calls in case of error answer from geolocation API
    * @alias module:locationService.getCurrentLocation
    * @private
    */
    getCurrentLocation = function (success, error) {
        var i;

        errorCallbacks.push(error);
        successCallbacks.push(success);

        if (gettingLocation) {
            //process for getting current location started already, just subscribe for proper event and wait
            return;
        }
        gettingLocation = true;
        if (navigator.geolocation) {
            navigator.geolocation.getCurrentPosition(
                function (position) {
                    for (i = 0; i < successCallbacks.length; i++) {
                        successCallbacks[i]([position.coords.latitude, position.coords.longitude]);
                    }
                    gettingLocation = false;
                },
                function (error) {
                    reportError("Error while detecting user location");
                    gettingLocation = false;
                },
                getLocationOptions()
            );
        } else {
            reportError("Your browser does not support geolocation");
            gettingLocation = false;
        }
    };
    /**
    * getLocationOptions method return option for location search based on browser
    * @memberOf module:locationService
    * @alias module:locationService.getLocationOptions
    * @returns {Object} list of option for location search
    * @private
    */
    getLocationOptions = function () {
        var browser = detectBrowser();
        if (browser.indexOf("Chrome") !== -1) {
            return {
                enableHighAccuracy: true,
                timeout: 10000,
                maximumAge: 0
            };
        } else {
            return {
                timeout: 1000,
                maximumAge: Infinity
            };
        }
    };
    /**
    * detectBrowser method detects browser name and version
    * @memberOf module:locationService
    * @alias module:locationService.detectBrowser
    * @returns {String} returns browser name and its version
    * @private
    */
    detectBrowser = function () {
        var ua = navigator.userAgent,
            tem,
            M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];

        if (/trident/i.test(M[1])) {
            tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
            return "IE " + (tem[1] || "");
        }
        if (M[1] === "Chrome") {
            tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
            if (tem != null) {
                return tem.slice(1).join(" ").replace("OPR", "Opera");
            }
        }
        M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, "-?"];
        if ((tem = ua.match(/version\/(\d+)/i)) != null) {
            M.splice(1, 1, tem[1]);
        }
        return M.join(" ");
    };
    /**
    * reportError method adds error message to errorCallbacks array
    * @memberOf module:locationService
    * @param {String} errorMessage text of appeared error
    * @alias module:locationService.reportError
    * @private
    */
    reportError = function (errorMessage) {
        var i;
        for (i = 0; i < errorCallbacks.length; i++) {
            if (typeof errorCallbacks[i] === "function") {
                errorCallbacks[i](errorMessage);
            }
        }
    };
    /**
    * detectLocation method call
    * ["getCurrentLocation"]{@link module:locationService.getCurrentLocation} method 
    * to get user location.
    * @memberOf module:locationService
    * @param {Function} success function callback that calls in case of positive answer from geolocation API
    * @param {Function} error function callback that calls in case of error answer from geolocation API
    * @alias module:locationService.detectLocation
    */
    api.detectLocation = function (success, error) {
        getCurrentLocation(success, error);
    };

    return api;

}(jQuery, document));

XA.register("locationService", XA.component.locationService);


/*  component-search  */
/**
* @typedef {} FacetDataRequestInfo requests data from each facet component stored in object
* @property {String} signature - signature of facet
* @property {String} facetName - facet name
* @property {String} endpoint - endpoint property
* @property {Boolean} filterWithoutMe - should this filter be a part of filtering process
*/
/**
* @typedef {Object} facetRequestData requests data from each facet component stored in object
* @property {String} endpoint - endpoint property
* @property {Object} data - list of facet names based on signature
*/
/**
 * @namespace XA.component.search
 */
XA.component.search = {};
XA.component.search.facet = {};
XA.component.search.results = {};

/**
 * @namespace XA.component.search.vent
 */
/**
* Fired when hash changed
* @event XA.component.search.vent#hashChanged
* @type {object}
*/
/**
* Fired after response from server with filtered facet data
* @event XA.component.search.vent#facet-data-loaded
* @type {object}
*/
/**
* Fired after response from server with filtered facet data
* @event XA.component.search.vent#facet-data-filtered
* @type {object}
*/
/**
* Fired after response from server with filtered partial facet data
* @event XA.component.search.vent#facet-data-partial-filtered
* @type {object}
*/
/**
* Fired by load more component
* @event XA.component.search.vent#loadMore
* @type {object}
*/
XA.component.search.vent = _.extend({}, Backbone.Events);


/*  component-search-ajax  */
/**
 * Provides methods for working searches using Ajax
 * @module ajax
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {ApiModel} list of Api methods for Search Ajax functionality
*/
XA.component.search.ajax = (function ($, document) {
    var ApiModel, getPrameterByName;

    /**
    * @name module:ajax.ApiModel
    * @constructor
    * @augments Backbone.Model
    */
    ApiModel = Backbone.Model.extend(
        /** @lends module:ajax.ApiModel.prototype **/

        {
            /**
            * Makes ajax request and calls callback on success
            * @method
            * @param {Object} properties properties of ajax call and callback
            */
            getData: function (properties) {
                var siteName = this.getPrameterByName("sc_site"),
                    url = typeof properties.excludeSiteName !== "undefined" && properties.excludeSiteName
                        ? properties.url
                        : XA.component.search.url.createSiteUrl(properties.url, siteName);
                Backbone.ajax({
                    dataType: "json",
                    url: url,
                    success: function (data) {
                        properties.callback(data);
                    }
                });
            },
            /**
             * Gets value of parameter from url
             * @method
             * @param {String} name name of parameter that should be taken from url
             * @param {String} url link from which parameter should be taken
             * @returns {string} parameter value
             */
            getPrameterByName: function (name, url) {
                if (!url) url = window.location.href;
                name = name.replace(/[\[\]]/g, "\\$&");
                var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
                    results = regex.exec(url);
                if (!results) return null;
                if (!results[2]) return '';
                return decodeURIComponent(results[2].replace(/\+/g, " "));
            }
        });

    return new ApiModel();

}(jQuery, document));

/*  component-search-base-model  */
/**
 * include functionality for sorting facet array
 * @module searchBaseModel
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {baseModel} list of methods for working with facet array
*/
XA.component.search.baseModel = (function ($, document) {
	/**
	* @name module:searchBaseModel.baseModel
	* @constructor
	* @augments Backbone.Model
	*/
	return Backbone.Model.extend(
		/** @lends module:searchBaseModel.baseModel.prototype **/
		{
			/**
			 * Sort facets by sort order option
			 * @param {String} sortOrder method of sorting - SortByCount|SortByNames
			 * @param {Array} facetArray  list of facets that should be sorted
			 */
			sortFacetArray: function (sortOrder, facetArray) {
				switch (sortOrder) {
					case 'SortByCount': {
						facetArray.sort(function (a, b) { return b.Count - a.Count });
						break;
					}
					//customization for cadence so that the audience is always in this order
					case 'SortByAudience': {
						var audienceArray = ["Personal", "Business", "Commercial", "Insurance"];
						facetArray.sort(function (a, b) { return audienceArray.indexOf(a.Name) - audienceArray.indexOf(b.Name) });
						break;
					}
					case 'SortByNames':
					default: {
						//no need to sort by names as values are sorted that way by default
						break;
					}
				}
			}
		});

}(jQuery, document));

XA.register('searchBaseModel', XA.component.search.baseModel);


/*  component-search-base-view  */
/**
 * Provides methods for translate and update signature
 * @module searchBaseView
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {baseView} list of Api methods for work with signature
*/
XA.component.search.baseView = (function ($, document) {
	/**
	* @name module:searchBaseView.baseViewModel
	* @constructor
	* @augments Backbone.Model
	*/
	return Backbone.View.extend(
		/** @lends module:searchBaseView.baseViewModel.prototype **/
		{

			initialize: function () {
			},
			/**
			 * 
			 * @param {*} rawSignature 
			 * @param {*} f 
			 */
			translateSignatures: function (rawSignature, f) {
				var signatures, i;

				f = f ? f.toLowerCase() : "";

				if (typeof rawSignature === "undefined" || rawSignature === null) {
					return [f];
				}

				signatures = rawSignature.split(',');

				if (rawSignature === "") {
					return [f];
				} else {
					for (i = 0; i < signatures.length; i++) {
						signatures[i] = signatures[i] + "_" + f;
					}
					return signatures;
				}
			},
			/**
			 * 
			 * @param {*} sig 
			 * @param {*} value 
			 * @param {*} hash 
			 */
			updateSignaturesHash: function (sig, value, hash) {
				for (var i = 0; i < sig.length; i++) {
					hash[sig[i]] = value;
				}
				return hash;
			},
			/**
			 * Manages whether the component should be visible, depending on data.
			 * @param {DomElement} component Dom element that we want ho show\hide
			 * @param {Object} data Object with facet data. Depending on data, component will be hidden or shown.
			 */
			manageVisibilityByData: function (component, data) {
				if (_.size(data) === 0 || (typeof data.Values !== "undefined" && data.Values.length == 0)) {
					$(component).hide();
				} else {
					$(component).show();
				}
			}
		});

}(jQuery, document));

XA.register('searchBaseView', XA.component.search.baseView);


/*  component-search-box  */
/**
 * Search box component functionality
 * @module searchBox
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with search box
*/
XA.component.search.box = (function ($, document) {
    var api = {},
        searchBoxViews = [],
        searchBoxModels = [],
        queryModel,
        urlHelperModel,
        searchResultModels;
    /**
    * @name module:searchBox.SearchBoxModel
    * @constructor
    * @augments Backbone.Model
    */
    var SearchBoxModel = Backbone.Model.extend(
        /** @lends module:searchBox.SearchBoxModel.prototype **/
        {
            /**
            * Default model options
            * @default
            */
            defaults: {
                searchEngine: "",
                typeahead: "",
                dataProperties: {},
                searchQuery: "",
                loadingInProgress: false,
                sig: []
            },
            /**
            * create instance of 
            * ["Bloodhound"]{@link https://github.com/twitter/typeahead.js/blob/master/doc/bloodhound.md}
            * width necessary options
            * @memberOf module:searchBox.SearchBoxModel
            * @method
            * @alias module:searchBox.SearchBoxModel.initSearchEngine
            */
            initSearchEngine: function () {
                var inst = this,
                    siteName = XA.component.search.ajax.getPrameterByName("sc_site"),
                    searchEngine = new Bloodhound({
                        datumTokenizer: Bloodhound.tokenizers.obj.whitespace("name"),
                        queryTokenizer: Bloodhound.tokenizers.whitespace,
                        limit: inst.get("dataProperties").p,
                        remote: {
                            url: urlHelperModel.createSiteUrl(inst.createSuggestionsUrl($.extend({ l: inst.getLanguage() }, inst.get("dataProperties")), inst.get("searchQuery")), siteName),
                            filter: function (list) {
                                var searchArr = [];
                                return _.map(list.Results, function (item) { return { html: item.Html }; });
                            },
                            replace: function () {
                                var valueProvider = inst.get("valueProvider"),
                                    searchQuery = valueProvider(),
                                    properties = $.extend({ l: inst.getLanguage() }, inst.get("dataProperties"));

                                return urlHelperModel.createSiteUrl(inst.createSuggestionsUrl(properties, searchQuery), siteName);
                            },
                            ajax: {
                                beforeSend: function () {
                                    inst.set({ "loadingInProgress": true });
                                },
                                complete: function () {
                                    inst.set({ "loadingInProgress": false });
                                }
                            }
                        }
                    });

                searchEngine.initialize();
                this.set({ searchEngine: searchEngine });
            },
            /**
            * Creates url from which suggestion should be taken
            * @memberOf module:searchBox.SearchBoxModel
            * @method
            * @param {Object} properties list of properties for suggestion functionality 
            * @param {String} searchQuery text from search box
            * @alias module:searchBox.SearchBoxModel.createSuggestionsUrl
            * @returns {string} search url based on setups
            */
            createSuggestionsUrl: function (properties, searchQuery) {
                var suggestionsMode = this.get("dataProperties").suggestionsMode,
                    resultsEndpoint = this.get("dataProperties").endpoint,
                    suggestionsEndpoint = this.get("dataProperties").suggestionEndpoint;

                switch (suggestionsMode) {
                    case "ShowPredictions": {
                        return urlHelperModel.createPredictiveSearchUrl(suggestionsEndpoint, properties, searchQuery);
                    }
                    default: {
                        return urlHelperModel.createPredictiveSearchUrl(resultsEndpoint, properties, searchQuery);
                    }
                }
            },
            /**
            * get search box signature
            * @memberOf module:searchBox.SearchBoxModel
            * @method
            * @alias module:searchBox.SearchBoxModel.getSignature
            * @returns {string} search box signature
            */
            getSignature: function () {
                var rawSignature = this.get("dataProperties").searchResultsSignature,
                    signatures;

                if (typeof rawSignature === "undefined" || rawSignature === null) {
                    return "";
                }

                signatures = rawSignature.split(',');

                if (rawSignature === "") {
                    return "";
                } else {
                    return signatures[0];
                }
            },
            /**
            * get search box language
            * @memberOf module:searchBox.SearchBoxModel
            * @method
            * @alias module:searchBox.SearchBoxModel.getLanguage
            * @returns {string} selected language
            */
            getLanguage: function () {
                var dataProperties = this.get("dataProperties"),
                    searchResultModels = XA.component.search.results.searchResultModels,
                    languageSource = dataProperties.languageSource,
                    signature = this.getSignature(),
                    model;

                switch (languageSource) {
                    case "CurrentLanguage":
                    case "AllLanguages": {
                        return dataProperties.l;
                    }
                    default: {
                        model = searchResultModels.filter(function (element) {
                            return element.get("dataProperties").sig === signature;
                        })[0];
                        if (typeof model !== "undefined") {
                            return model.get("dataProperties").l;
                        }
                        break;
                    }
                }

                return "";
            }
        });
    /**
    * @name module:searchBox.SearchBoxView
    * @constructor
    * @augments Backbone.View
    */
    var SearchBoxView = XA.component.search.baseView.extend(
        /** @lends module:searchBox.SearchBoxView.prototype **/
        {
            /**
             *Initializes typeahead for view, sets up watchers for change on model
             *and call ["initSearchEngine"]{@link module:searchBox.SearchBoxModel}
            * @memberof module:searchBox.SearchBoxView.prototype
            */
            initialize: function () {
                var inst = this,
                    dataProperties = this.$el.data(),
                    typeahead;

                dataProperties.properties.targetSignature = dataProperties.properties.targetSignature !== null ? dataProperties.properties.targetSignature : "";

                this.model.set({ dataProperties: dataProperties.properties });
                this.model.set("sig", this.translateSignatures(dataProperties.properties.searchResultsSignature, "q"));
                this.model.initSearchEngine();
                this.model.on("change:loadingInProgress", this.loading, this);

                typeahead = this.$el.find(".search-box-input").typeahead({
                    hint: true,
                    minLength: dataProperties.properties.minSuggestionsTriggerCharacterCount
                },
                    {
                        source: inst.model.get("searchEngine").ttAdapter(),
                        displayKey: function () { return inst.$el.find(".search-box-input.tt-input").val(); },
                        templates: {
                            suggestion: function (data) {
                                var suggestionsMode = dataProperties.properties.suggestionsMode,
                                    text = data.html.replace(/(<([^>]+)>)/ig, ""),
                                    suggestionText = text !== "" ? text : data.html;

                                switch (suggestionsMode) {
                                    case "ShowPredictions":
                                    case "ShowSearchResultsAsPredictions": {
                                        return '<div class="sugesstion-item">' + suggestionText + '</div>';
                                    }
                                    default: {
                                        return '<div class="sugesstion-item">' + data.html + '</div>';
                                    }
                                }
                            }
                        }
                    }).on('typeahead:selected', this.suggestionSelected.bind(inst));

                //TODO: Seems like bellow line isn't needed as updateSearchBoxValue() function will be callend when hash will change - to be tested in non/multi signatures cases
                //$searchBox.val(hashObj[this.model.get("sig")] !== undefined ? hashObj[this.model.get("sig")] : "");

                this.model.set({ typeahead: typeahead });
                this.model.set({
                    valueProvider: function () {
                        return inst.$el.find(".search-box-input.tt-input").val();
                    }
                });
                XA.component.search.vent.on("hashChanged", this.updateSearchBoxValue.bind(this));
            },
            /**
             * list of events for Backbone View
             * @memberof module:searchBox.SearchBoxView
             * @alias module:searchBox.SearchBoxView#events
             */
            events: {
                "click .search-box-button": "updateQueryModelClick",
                "click .search-box-button-with-redirect": "updateQueryWithRedirect",
                "keypress .search-box-input.tt-input": "predictiveSearch",
                "keydown .search-box-input.tt-input": "predictiveSearch"
            },
            /**
            * Toggle css class "loading-in-progress"
            * @memberof module:searchBox.SearchBoxView
            * @alias module:searchBox.SearchBoxView#loading
            */
            loading: function () {
                this.$el.toggleClass("loading-in-progress");
            },
            /**
            * Called when selected value from search box and call
            * ["performSearch"]{@link module:performSearch} or re
            * @memberof module:searchBox.SearchBoxView
            * @param {jQuery<Event>} event jQuery event object
            * @param {DomElement} data DOM element that was selected
            * @alias module:searchBox.SearchBoxView#suggestionSelected
            */
            suggestionSelected: function (event, data) {
                event.preventDefault();

                var suggestionsMode = this.model.get("dataProperties").suggestionsMode;
                var text;
                try {
                    text = $(data.html).text();
                }
                catch (error) {
                    text = "";
                }

                var suggestionText = text != "" ? text : data.html,
                    link;

                switch (suggestionsMode) {
                    case "ShowPredictions":
                    case "ShowSearchResultsAsPredictions": {
                        this.performSearch(suggestionText);
                        break;
                    }
                    default: {
                        link = $(data.html).find("a");
                        if (link.length) {
                            window.location.href = $(link[0]).attr("href");
                        }
                        break;
                    }
                }
            },
            /**
            * Updates hash value and makes redirect based on value from search box
            * @memberof module:searchBox.SearchBoxView
            * @param {jQuery<Event>} event jQuery event object
            * @alias module:searchBox.SearchBoxView#updateQueryWithRedirect
            */
            updateQueryWithRedirect: function (event) {
                event.preventDefault();

                var resultPage = this.model.get("dataProperties").resultPage,
                    targetSignature = this.model.get("dataProperties").targetSignature,
                    searchResultsSignature = this.model.get("dataProperties").searchResultsSignature,
                    query = encodeURIComponent(this.$el.find(".search-box-input.tt-input").val()),
                    sig = this.model.get("sig"),
                    queryWithSignature = {};

                if (targetSignature !== "") {
                    queryWithSignature = this.updateSignaturesHash([targetSignature + "_q"], query, this.createOffsetObject());
                } else {
                    queryWithSignature = this.updateSignaturesHash(sig, query, this.createOffsetObject());
                }

                window.location.href = urlHelperModel.createRedirectSearchUrl(resultPage, queryWithSignature, searchResultsSignature, targetSignature);
            },
            /**
            * Takes search box value as a query value and calls
            * ["updateQueryModel"]{@link module:module:searchBox.SearchBoxView#updateQueryModel}
            * @memberof module:searchBox.SearchBoxView
            * @param {jQuery<Event>} event jQuery event object
            * @alias module:searchBox.SearchBoxView#updateQueryModelClick
            */
            updateQueryModelClick: function (event) {
                event.preventDefault();

                XA.component.search.vent.trigger("new-search");

                var query = this.$el.find(".search-box-input.tt-input").val();
                this.closeDropdown();
                this.updateQueryModel(query);
            },
            /**
             * Updates hash based on searchValue
             * @memberof module:searchBox.SearchBoxView
             * @param {String} query jQuery event object
             * @alias module:searchBox.SearchBoxView#updateQueryModel
             */
            updateQueryModel: function (query) {
                var searchValue = {},
                    offsetSignatures = this.translateSignatures(this.model.get("dataProperties").searchResultsSignature, "e"),
                    sig = this.model.get("sig"),
                    i;

                for (i = 0; i < sig.length; i++) {
                    searchValue[sig[i]] = query;
                    searchValue[offsetSignatures[i]] = 0;
                }

                queryModel.updateHash(searchValue, this.model.get("dataProperties").targetUrl);
            },
            /**
            * Calls ["performSearch"]{@link module:searchBox.SearchBoxView#performSearch} 
            * by pressing Enter key
            * @memberof module:searchBox.SearchBoxView
            * @param {Event} event Event object
            * @alias module:searchBox.SearchBoxView#predictiveSearch
            */
            predictiveSearch: function (event) {
                if (event.keyCode === 13) {
                    event.preventDefault();
                    this.performSearch(this.$el.find(".search-box-input.tt-input").val());
                }
            },
            /**
             * Makes search
             * @memberof module:searchBox.SearchBoxView
             * @param {String} query value from search box
             * @alias module:searchBox.SearchBoxView#performSearch
             */
            performSearch: function (query) {
                var properties = this.model.get("dataProperties"),
                    targetSignature = properties.targetSignature,
                    searchResultsSignature = properties.searchResultsSignature,
                    resultPage = properties.resultPage,
                    sig = this.model.get("sig"),
                    queryWithSignature = {};

                XA.component.search.vent.trigger("new-search");

                this.closeDropdown();

                if (resultPage === "") {
                    this.updateQueryModel(query);
                    $('.search-box-input').typeahead('val', query);
                } else {
                    query = encodeURIComponent(query);
                    if (targetSignature !== "") {
                        queryWithSignature = this.updateSignaturesHash([targetSignature + "_q"], query, this.createOffsetObject())
                    } else {
                        queryWithSignature = this.updateSignaturesHash(sig, query, this.createOffsetObject())
                    }
                    window.location.href = urlHelperModel.createRedirectSearchUrl(resultPage, queryWithSignature, searchResultsSignature, targetSignature);
                }
            },
            /**
             * Creates object that contain signatures
             * @memberof module:searchBox.SearchBoxView
             * @alias module:searchBox.SearchBoxView#createOffsetObject
             */
            createOffsetObject: function () {
                var sig = this.model.get("sig"),
                    targetSignature = this.model.get("dataProperties").targetSignature,
                    signature = targetSignature !== "" ? targetSignature : this.model.get("dataProperties").searchResultsSignature,
                    offsetSignatures = this.translateSignatures(signature, "e"),
                    offsetObject = {},
                    i;

                for (i = 0; i < sig.length; i++) {
                    offsetObject[offsetSignatures[i]] = 0;
                }

                return offsetObject;
            },
            /**
             * Sets value into search box based on hash 
             * @memberof module:searchBox.SearchBoxView
             * @alias module:searchBox.SearchBoxView#updateSearchBoxValue
             */
            updateSearchBoxValue: function () {
                var hashObj = queryModel.parseHashParameters(window.location.hash),
                    el = this.$el.find(".search-box-input.tt-input"),
                    sig = this.model.get("sig"),
                    i;

                for (i = 0; i < sig.length; i++) {
                    if (hashObj.hasOwnProperty(sig[i])) {
                        el.val(decodeURIComponent(hashObj[sig[i]]));
                    } else {
                        el.val("");
                    }
                }
            },
            /**
             * Closes typeahead drop down
             * @memberof module:searchBox.SearchBoxView
             * @alias module:searchBox.SearchBoxView#closeDropdown
             */
            closeDropdown: function () {
                this.$el.find(".search-box-input").typeahead('close');
            }
        });
    /**
     * For each search box on a page creates instance of 
     * ["searchBoxModel"]{@link module:searchBox.SearchBoxModel} and 
     * ["SearchBoxView"]{@link module:searchBox.SearchBoxView} 
     * @memberOf module:searchBox
     * @alias module:searchBox.init
     */
    api.init = function () {
        if ($("body").hasClass("on-page-editor")) {
            return;
        }
        queryModel = XA.component.search.query;
        searchResultModels = XA.component.search.results.searchResultModels;
        urlHelperModel = XA.component.search.url;

        var searchBox = $(".search-box:not(.initialized)");

        _.each(searchBox, function (elem) {
            var $el = $(elem),
                boxModel = new SearchBoxModel();

            searchBoxModels.push(boxModel);
            searchBoxViews.push(new SearchBoxView({ el: $el, model: boxModel }));
            $el.addClass("initialized");
        });
    };
    /**
    * Extends search box API with 
    * ["searchBoxViews"]{@link module:searchBox.searchBoxViews} 
    * @memberOf module:searchBox
    * @alias module:searchBox.searchBoxViews
    */
    api.searchBoxViews = searchBoxViews;
    /**
* Extends search box API with 
* ["searchBoxModels"]{@link module:searchBox.searchBoxModels} 
* @memberOf module:searchBox
* @alias module:searchBox.searchBoxViews
*/
    api.searchBoxModels = searchBoxModels;

    return api;

}(jQuery, document));

XA.register('searchBox', XA.component.search.box);


/*  component-search-facet-data  */
/**
 * Includes functionality for manipulating facet data
 * @module FacetData
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {FacetDataModel} FacetDataModel api
*/
XA.component.search.facet.data = (function ($, document) {
    /**
      * @name module:FacetData.FacetDataModel
      * @constructor
      * @augments Backbone.Model
      */
    var FacetDataModel = Backbone.Model.extend(
        /** @lends module:FacetData.FacetDataModel.prototype **/
        {
            /**
            * Default model options
            * @default
            */
            defaults: {
            },
            /**
           * @memberof module:FacetData.FacetDataModel.prototype
           */
            initialize: function () {
            },
            /**
            * Gets facet data by calling 
            * ["search.ajax.getData"]{@link module:ajax.ApiModel#getData} method
            * @memberof module:FacetData.FacetDataModel
            * @alias module:FacetData.FacetDataModel#getInitialFacetData
            */
            getInitialFacetData: function () {
                var facetRequestData = this.getFacetRequestData(),
                    data = facetRequestData.data,
                    facetNames = [],
                    language;

                for (var signature in data) {
                    if (data.hasOwnProperty(signature)) {
                        //here we don't have any params after hash in the url, we are getting all data at once
                        facetNames = facetNames.concat(data[signature].normalFiltering);
                        facetNames = facetNames.concat(data[signature].partialFiltering);

                        //get language settings form Search Results rendering
                        language = this.getSearchResultsLanguage(signature);

                        if (facetNames.length > 0) {
                            XA.component.search.ajax.getData({
                                callback: function (data) {
                                    XA.component.search.vent.trigger("facet-data-loaded", data);
                                },
                                url: XA.component.search.url.createMultiFacetUrl({
                                    endpoint: facetRequestData.endpoint,
                                    s: facetRequestData.s,
                                    l: language
                                }, facetNames, signature)
                            });
                        }
                    }
                }
            },
            /**
            * Filter facet data
            * ["search.ajax.getData"]{@link module:ajax.ApiModel#getData} method and 
            * triggered event '' on callback with sending response from server
            * @memberof module:FacetData.FacetDataModel
            * @param {Object} hashObj Object with hashes from url
            * @fires XA.component.search.vent#facet-data-filtered
            * @fires XA.component.search.vent#facet-data-partial-filtered
            * @alias module:FacetData.FacetDataModel#filterFacetData
            */
            filterFacetData: function (hashObj) {
                var facetRequestData = this.getFacetRequestData(hashObj),
                    data = facetRequestData.data,
                    requestData,
                    language,
                    scope,
                    searchModel,
                    itemId;

                for (var signature in data) {
                    if (data.hasOwnProperty(signature)) {
                        //Gets language settings form Search Results rendering
                        language = this.getSearchResultsLanguage(signature);
                        //Gets scope settings form Search Results rendering
                        scope = this.getSearchResultsScope(signature);

                        searchModel = this.getSearchResultsModelBySignature(signature),
                            itemId = searchModel.get('dataProperties').itemid;

                        //Makes one request for data for facet controls with all hash params
                        if (data[signature].normalFiltering.length > 0) {
                            requestData = $.extend({ endpoint: facetRequestData.endpoint, s: scope, l: language }, hashObj);
                            XA.component.search.ajax.getData({
                                callback: function (data) {
                                    XA.component.search.vent.trigger("facet-data-filtered", data);
                                },
                                url: XA.component.search.url.createMultiFacetUrl(requestData, data[signature].normalFiltering, signature, itemId)
                            });
                        }

                        //Makes as many requests as many controls that require partial filtering we have
                        //We will take all params from url hash without control facet name so controls won't collapse
                        if (data[signature].partialFiltering.length > 0) {
                            _.each(data[signature].partialFiltering, function (facetName) {
                                var hash = $.extend({}, hashObj);

                                //Ensures removal of facet (despite lower case or not) from hash object in case of partial filtering  
                                delete hash[signature !== "" ? signature + "_" + facetName : facetName];
                                delete hash[signature !== "" ? signature + "_" + facetName.toLowerCase() : facetName.toLowerCase()];

                                requestData = $.extend({ endpoint: facetRequestData.endpoint, s: scope, l: language }, hash);

                                XA.component.search.ajax.getData({
                                    callback: function (data) {
                                        XA.component.search.vent.trigger("facet-data-partial-filtered", data);
                                    },
                                    url: XA.component.search.url.createMultiFacetUrl(requestData, [facetName], signature, itemId)
                                });
                            });
                        }
                    }
                }
            },

            /**
            * Gets facet request data from server by calling 
            * getFacetDataRequestInfo method in a loop for all facet components
            * and stores them in returned object
            * @memberof module:FacetData.FacetDataModel
            * @param {Object} hashObj Object with hashes from url
            * @returns {facetRequestData} request data from each facet component stored in object
            * @alias module:FacetData.FacetDataModel#getFacetRequestData
            */
            getFacetRequestData: function (hashObj) {
                var that = this, data = [], requestData = {}, facetControl, control, endpoint, facetName;

                for (facetControl in XA.component.search.facet) {
                    control = XA.component.search.facet[facetControl];
                    if (typeof (control.getFacetDataRequestInfo) === "function") {
                        data = control.getFacetDataRequestInfo();
                        _.each(data, function (controlData) {
                            facetName = controlData.signature !== "" ? controlData.signature + "_" + controlData.facetName : controlData.facetName;

                            if (!requestData.hasOwnProperty(controlData.signature)) {
                                that.initRequestObject(requestData, controlData);
                            }

                            if (!controlData.filterWithoutMe || (hashObj !== undefined && !hashObj.hasOwnProperty(facetName) && !hashObj.hasOwnProperty(facetName.toLowerCase()))) {
                                //If the control does not require partial filtering or control facet name is not in the hash, add it to "one request" list.
                                requestData[controlData.signature].normalFiltering.push(controlData.facetName);
                                endpoint = controlData.endpoint;
                            } else {
                                requestData[controlData.signature].partialFiltering.push(controlData.facetName);
                                endpoint = controlData.endpoint;
                            }
                        });
                    }
                }
                return {
                    endpoint: endpoint,
                    data: requestData
                };
            },
            /**
            * On initialization fullfils requestData argument with
            * right structure
            * @memberof module:FacetData.FacetDataModel
            * @param {Object} requestData Object with request data
            * @param {Object} controlData Object data taked from facet
            * @alias module:FacetData.FacetDataModel#initRequestObject
            */
            initRequestObject: function (requestData, controlData) {
                requestData[controlData.signature] = {};
                requestData[controlData.signature].normalFiltering = [];
                requestData[controlData.signature].partialFiltering = [];
            },
            /**
            * Returns language of search result
            * @memberof module:FacetData.FacetDataModel
            * @param {String} signature - signature of search result
            * @alias module:FacetData.FacetDataModel#getSearchResultsLanguage
            * @returns {String} search result language
            */
            getSearchResultsLanguage: function (signature) {
                var searchResultsModel = this.getSearchResultsModelBySignature(signature);
                if (typeof searchResultsModel !== "undefined") {
                    return searchResultsModel.get("dataProperties").l;
                }
                return "";
            },
            /**
            * Returns search result scope
            * @memberof module:FacetData.FacetDataModel
            * @param {String} signature - signature of search result
            * @alias module:FacetData.FacetDataModel#getSearchResultsScope
            * @returns {String} search result scope
            */
            getSearchResultsScope: function (signature) {
                var searchResultsModel = this.getSearchResultsModelBySignature(signature);
                if (typeof searchResultsModel !== "undefined") {
                    return searchResultsModel.get("dataProperties").s;
                }
                return "";
            },
            /**
            * Returns search result model by signature
            * @memberof module:FacetData.FacetDataModel
            * @param {String} signature - signature of search result
            * @alias module:FacetData.FacetDataModel#getSearchResultsModelBySignature
            * @returns {BackboneModel} search result model
            */
            getSearchResultsModelBySignature: function (signature) {
                var searchResultModels = XA.component.search.results.searchResultModels,
                    model = searchResultModels.filter(function (element) {
                        return element.get("dataProperties").sig === signature;
                    })[0];

                return model;
            }
        });

    return new FacetDataModel();

}(jQuery, document));

/*  component-search-facet-summary  */

/**
 * Facet summary component functionality
 * @module facetSummary
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with component facet summary
*/
XA.component.search.facet.summary = (function ($, document) {
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:facetSummary
    */
    var api = {},
        urlHelperModel = XA.component.search.url,
        queryModel = XA.component.search.query,
        apiModel = XA.component.search.ajax,
        facetName = "summary";

    /**
    * @name module:facetSummary.FacetSummaryModel
    * @constructor
    * @augments Backbone.Model
    */
    var FacetSummaryModel = Backbone.Model.extend(
        /** @lends module:facetSummary.FacetSummaryModel.prototype **/

        {
            /**
            * Default model options
            * @default
            * @param {Object} properties properties of ajax call and callback
            */
            defaults: {
                dataProperties: {},
                sig: [],
                hash: ''
            },
            /**
           * Listens to changes on facets and hash
            * @listens module:XA.component.search.vent~event:facet-data-loaded
            * @listens module:XA.component.search.vent~event:facet-data-filtered
            * @listens module:XA.component.search.vent~event:facet-data-partial-filtered
            * @listens module:XA.component.search.vent~event:hashChanged
            */
            initialize: function () {
                var that = this;
                this.set('resultData', {})
                //Event to get data at the begining or in case that there are no hash parameters in the url - one request for all controls
                XA.component.search.vent.on("facet-data-loaded", that.processData.bind(that));
                // //If the url hash contains this control facet name (someone clicked this control), then we have to listen for partial filtering
                XA.component.search.vent.on("facet-data-partial-filtered", that.processData.bind(that));
                // //When we are not filtering by this control (not clicked)
                XA.component.search.vent.on("facet-data-filtered", that.processData.bind(that));

            },
            /**
             * Finds all facets that have same name as facetName in Control Data and return them
             * @param {Object} facetData 
             * @param {*} controlData 
             * @returns {Array} Filtered facet data
             * @memberof module:facetSummary.FacetSummaryModel
             * @alias module:facetSummary.FacetSummaryModel.requestFacetData
             */
            requestFacetData: function (facetData, controlData) {
                return _.find(facetData, function (f) {
                    return f.Key.toLowerCase() === controlData.facetName.toLowerCase();
                });
            },
            /**
			 * Concatenates signature and facet name in order to build proper hash parameter name
			 * @param {*} rawSignature 
			 * @param {*} f 
			 */
            translateSignatures: function (rawSignature, f) {
                var signatures, i;

                f = f.toLowerCase();

                if (typeof rawSignature === "undefined" || rawSignature === null) {
                    return [f];
                }

                signatures = rawSignature.split(',');

                if (rawSignature === "") {
                    return [f];
                } else {
                    for (i = 0; i < signatures.length; i++) {
                        signatures[i] = signatures[i] + "_" + f;
                    }
                    return signatures;
                }
            },
            /**
            * Processes data that comes as parameter. Update
            * model and store all facets that are active and have
            * same signature
            * @param {Object} data Data from server with facet values
            * @memberof module:facetSummary.FacetSummaryModel
            * @alias module:facetSummary.FacetSummaryModel.processData
            */
            processData: function (data) {
                var hashObj = queryModel.parseHashParameters(window.location.hash),
                    requestFacedData,
                    that = this,
                    displayName,
                    facetData = [], requestData = {}, facetControl, control;
                var facets = XA.component.search.facet;

                for (facetControl in facets) {
                    control = XA.component.search.facet[facetControl];
                    if (typeof (control.getFacetDataRequestInfo) === "function") {
                        facetData = control.getFacetDataRequestInfo();
                        for (var facetIndex = 0; facetIndex < facetData.length; facetIndex++) {
                            var controlData = facetData[facetIndex];
                            var signaturesArray = controlData.signature.split(',');
                            for (var signatureIndex = 0; signatureIndex < signaturesArray.length; signatureIndex++) {
                                var signature = signaturesArray[signatureIndex];
                                if (data.Signature == signature && that.get('sig') == signature) {
                                    requestFacedData = that.requestFacetData(data.Facets, controlData);
                                    if (typeof requestFacedData !== "undefined") {
                                        displayName = requestFacedData.Name;
                                        var currResults = this.get('resultData');
                                        var facetHashNames = that.translateSignatures(signature, requestFacedData.Key.toLowerCase())
                                        for (var facetHashNameIndex = 0; facetHashNameIndex < facetHashNames.length; facetHashNameIndex++) {
                                            var facetHashName = facetHashNames[facetHashNameIndex];
                                            if (hashObj[facetHashName]) {
                                                currResults[displayName] = {
                                                    "value": hashObj[facetHashName],
                                                    "key": requestFacedData.Key,
                                                    "signature": data.Signature,
                                                    "facetHashName": facetHashName
                                                }
                                            } else {
                                                delete currResults[displayName];
                                            }
                                        }
                                        this.set('resultData', currResults);
                                        this.trigger('change', this)

                                    }
                                }
                            }

                        }
                    }
                }
            }
        });
    /**
    * @name module:facetSummary.FacetSummaryView
    * @constructor
    * @augments Backbone.View
    */
    var FacetSummaryView = XA.component.search.baseView.extend(
        /** @lends module:facetSummary.FacetSummaryView.prototype **/

        {
            tagName: "div",
            className: "facet-search-summary",
            template:
                "<% if(Object.keys(resultData).length){ %> <div class='facet-summary-wrapper clearfix'>" +
                "<% _.forEach(resultData, function(obj,key){ %>" +
                "<div class='active-facet-summary-element' data-hash='" + "<%=obj.facetHashName%>" + "' data-key='" + "<%=obj.key%>" + "' data-signature='" + "<%=obj.signature%>" + "'>" +
                "<span class='facet-summary-name'><%= key  %>:</span> " + "<%= renderFacetValues(obj.value) %>" +
                "</div>" +
                "<% }); %>" +
                "</div><% }%>",

            renderFacetValues: function (value) {
                var params = value.split("||")

                return params.map(function (p) {
                    var template = '<div class=\'facet-summary-value\' data-value=' + encodeURIComponent(p) + '>'
                        + '<p>' + p + '</p>'
                        + '<span class=\'removeFacetValue\'>x</span>'
                        + '</div>'

                    return template;
                }).join(' ')
            },
            /**
            * Initially sets data to model and watches events on which
            * view should be updated
            * @listens module:facetSummary.FacetSummaryView~event:change
            * @memberof module:facetSummary.FacetSummaryView
            * @alias module:facetSummary.FacetSummaryView#initialize
            */
            clearButtons: function (event) {
                var $target = $(event.currentTarget),
                    $facetSummary = $target.closest(".facet-summary");
                $facetSummary.find('.clear-all-active-facets').trigger('click');

            },
            /**
            * Initializes the view by assigning events to the buttons and rendering component
            * @memberof module:facetSummary.FacetSummaryView
            * @alias module:facetSummary.FacetSummaryView#events
            */
            initialize: function () {
                var dataProperties = this.$el.data(),
                    that = this,
                    properties = dataProperties.properties,
                    signatures;
                Backbone.$('.facet-summary .clear-filter').on('click', function (event) {
                    that.clearButtons(event)

                })
                Backbone.$('.facet-summary .bottom-remove-filter').on('click', function (event) {
                    that.clearButtons(event)

                })
                signatures = this.translateSignatures(properties.searchResultsSignature, facetName);
                this.model.set("sig", properties.searchResultsSignature);
                dataProperties = this.$el.data("properties"),
                    this.model.on("change", this.render, this);
                this.render();
            },
            /**
             * list of events for Backbone View
            * @memberof module:facetSummary.FacetSummaryView
            * @alias module:facetSummary.FacetSummaryView#events
             */
            events: {
                'click .removeFacetValue': 'removeFacetValue',
                'click .clear-filter': 'clearAllActiveFacets',
                'click .bottom-remove-filter button': 'clearAllActiveFacets'
            },
            /**
            * Prepares hash string based on hash object
            * @param {Object} hashObj Object created from hash parameters
            * @memberof module:facetSummary.FacetSummaryView
            * @alias module:facetSummary.FacetSummaryView#createHash
            * @returns {String} hash as a string
            */
            createHash: function (hashObj) {
                var result = "#";
                for (option in hashObj) {
                    result += option + "=" + hashObj[option] + "&";
                }
                return result;
            },
            /**
             * Updates current location
             * @param {Object} hashObj 
             */
            updateLocation: function (hashObj) {
                XA.component.search.query.updateHash(hashObj);
            },
            /**
             * Returns facet params
             * @param {Jquery} el jquery event target element 
             */
            getFacetParams: function (el) {
                var summaryEl = $(el).closest(".active-facet-summary-element");

                return {
                    key: summaryEl.data('key'),
                    signature: summaryEl.data('signature')
                }
            },
            /**
             * Clears all active facets that are stored under summary component
            * @memberof module:facetSummary.FacetSummaryView
            * @alias module:facetSummary.FacetSummaryView#clearAllActiveFacets
            */
            clearAllActiveFacets: function () {
                var self = this;
                var hashObj = queryModel.parseHashParameters(window.location.hash);
                
				this.$el.find('.active-facet-summary-element').each(function () {
                    var facetParams = self.getFacetParams($(this));
                    var facetHashNames = self.translateSignatures(facetParams.signature, facetParams.key)

                    _.each(facetHashNames, function (facetHashName) {
                        hashObj[facetHashName]="";
                    })
                })

                self.updateLocation(hashObj);
            },
            /**
             * Removes selected facet value and updates hash
             * @param {Event} event Event object where stored clicked element
             */
            removeFacetValue: function (event) {
                var facetValueElem = $(event.currentTarget).closest(".facet-summary-value")

                var selectedFacetValue = decodeURIComponent(facetValueElem.data('value'))
                var facetParams = this.getFacetParams(event.currentTarget)

                var hashObj = queryModel.parseHashParameters(window.location.hash);
                var facetHashNames = this.translateSignatures(facetParams.signature, facetParams.key)

                var removeSelectedValue = function (facetHashName) {
                    hashObj[facetHashName] = hashObj[facetHashName]
                        .split("||")
                        .filter(function(val) {
                            return val !== selectedFacetValue
                        })
                        .join("||");
                }

                _.each(facetHashNames, removeSelectedValue)

                this.updateLocation(hashObj);
            },
            /**
            * Renders view
            * @memberof module:facetSummary.FacetSummaryView
            * @alias module:facetSummary.FacetSummaryView#render
            */
            render: function () {
                var inst = this,
                    resultData = this.model.get("resultData"),
                    template, templateResult;
                var dataProperties = this.$el.data();
                this.properties = dataProperties.properties;
                this.manageVisibilityByData(this.$el, resultData)
                if (this.model) {
                    this.model.set({ dataProperties: this.properties });
                    this.model.set("sig", this.properties.searchResultsSignature);
                }
                //checks whether page is opened from disc - if yes then we are in Creative Exchange mode
                if (window.location.href.startsWith("file://")) {
                    return;
                }

                if (resultData !== undefined) {
                    template = _.template(this.template, {
                        imports: { renderFacetValues: this.renderFacetValues }
                    });

                    templateResult = template({
                        "resultData": resultData
                    });
                }

                inst.$el.find(".facet-summary-placeholder").html(templateResult);
            }

        });
    /**
    * For each search dropdown component on a page creates instance of 
    * ["FacetSummaryModel"]{@link module:facetSummary.FacetSummaryModel} and 
    * ["FacetSummaryView"]{@link module:facetSummary.FacetSummaryView} 
    * @memberOf module:facetSummary
    * @alias module:facetSummary.init
    */
    api.init = function () {
        if ($("body").hasClass("on-page-editor")) {
            return;
        }

        queryModel = XA.component.search.query;
        apiModel = XA.component.search.ajax;
        urlHelperModel = XA.component.search.url;

        var facetSummary = $(".facet-summary:not(.initialized)");      

        _.each(facetSummary, function (elem) {
            var $el = $(elem),
                model = new FacetSummaryModel(),
                view = new FacetSummaryView({ el: $el, model: model });

            $el.addClass("initialized");
        });
    };

    return api;

}(jQuery, document));

XA.register('facetSummary', XA.component.search.facet.summary);

/*  component-search-facet-dropdown  */
/**
 * Facet dropdown component functionality
 * @module dropdown
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with component dropdown
*/
XA.component.search.facet.dropdown = (function ($, document) {

    "use strict";
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:dropdown
    */
    var api = {},
        urlHelperModel,
        queryModel
    /**
    * @name module:dropdown.FacetDropdownModel
    * @constructor
    * @augments Backbone.Model
    */
    var FacetDropdownModel = XA.component.search.baseModel.extend(
        /** @lends module:dropdown.FacetDropdownModel.prototype **/
        {
            /**
            * Default model options
            * @default
            */
            defaults: {
                template: "<% _.forEach(results, function(result){" +
                    "%><option data-facetName=\"<%= result.Name !== '' ? encodeURIComponent(result.Name) : '_empty_' %>\" <%= result.Selected !== undefined ? 'selected' : '' %> ><%= result.Name !== '' ? result.Name : emptyText %></option><%" +
                    "}); %>",
                dataProperties: {},
                blockNextRequest: false,
                resultData: {},
                optionSelected: false,
                sig: []
            },
            /**
             * Listens to changes on facets and hash
            * @listens module:XA.component.search.vent~event:facet-data-loaded
            * @listens module:XA.component.search.vent~event:facet-data-filtered
            * @listens module:XA.component.search.vent~event:facet-data-partial-filtered
            * @listens module:XA.component.search.vent~event:hashChanged
            */
            initialize: function () {
                //Event to get data at the begining or when there are no hash parameters in the url - one request for all controls
                XA.component.search.vent.on("facet-data-loaded", this.processData.bind(this));

                //Event to get filtered data
                XA.component.search.vent.on("facet-data-filtered", this.processData.bind(this));

                //If the url contains a hash, and facet name control is clicked, then we have to listen for partial filtering
                XA.component.search.vent.on("facet-data-partial-filtered", this.processData.bind(this));

                //Event after change of hash
                XA.component.search.vent.on("hashChanged", this.updateComponent.bind(this));
            },
            /**
             * Toggle value of blockNextRequest variable
             * @memberof module:dropdown.FacetDropdownModel
             * @alias module:dropdown.FacetDropdownModel#toggleBlockRequests
             */
            toggleBlockRequests: function () {
                var state = this.get("blockNextRequest");
                this.set(this.get("blockNextRequest"), !state);
            },
            /**
             * Processes data that comes as parameter update
             * model and sorts facets
             * @param {Object} data Data from server with facet values
             * @memberof module:dropdown.FacetDropdownModel
             * @alias module:dropdown.FacetDropdownModel#processData
             */
            processData: function (data) {
                var inst = this,
                    hashObj = queryModel.parseHashParameters(window.location.hash),
                    sig = inst.get("sig"),
                    dataProperties = this.get("dataProperties"),
                    searchResultsSignature = dataProperties.searchResultsSignature.split(','),
                    sortOrder = dataProperties.sortOrder,
                    facet = dataProperties.f,
                    facetItem,
                    facedData,
                    i, j;

                for (j = 0; j < searchResultsSignature.length; j++) {
                    if (data.Facets.length > 0 && (data.Signature === searchResultsSignature[j]) || data.Signature === "" || data.Signature === null) {
                        facedData = _.find(data.Facets, function (f) {
                            return f.Key.toLowerCase() === facet.toLowerCase();
                        });

                        if (facedData === undefined) {
                            return;
                        }

                        for (i = 0; i < sig.length; i++) {
                            if (!jQuery.isEmptyObject(_.pick(hashObj, sig[i]))) {
                                if (hashObj[sig[i]] !== "") {
                                    facetItem = _.where(facedData.Values, { Name: hashObj[sig[i]] });
                                    if (facetItem.length === 0) {
                                        facetItem = _.where(facedData.Values, { Name: "" });
                                    }
                                    if (facetItem.length > 0) {
                                        facetItem[0].Selected = true;
                                        inst.optionSelected = true;
                                    }
                                }
                            }
                        }

                        this.sortFacetArray(sortOrder, facedData.Values);
                        inst.set({ resultData: facedData.Values });
                    }
                }
            },
            /**
             * Sets option selected value to model based on hash
             * @param {Object} hash Hash stored as an object
             * @memberof module:dropdown.FacetDropdownModel
             * @alias module:dropdown.FacetDropdownModel#updateComponent
             */
            updateComponent: function (hash) {
                var sig = this.get("sig"), i, facetPart;

                for (i = 0; i < sig.length; i++) {
                    facetPart = sig[i].toLowerCase();
                    if (hash.hasOwnProperty(facetPart) && hash[facetPart] !== "") {
                        this.set({ optionSelected: true });
                    } else {
                        this.set({ optionSelected: false });
                    }
                }
            }
        });
    /**
    * @name module:dropdown.FacetDropdownView
    * @constructor
    * @augments Backbone.View
    */
    var FacetDropdownView = XA.component.search.baseView.extend(
        /** @lends module:dropdown.FacetDropdownView.prototype **/

        {
            /**
            * Initially sets data to model and watches events on which
            * view should be updated
            * @listens module:dropdown.FacetDropdownModel~event:change
            * @memberof module:dropdown.FacetDropdownView
            * @alias module:dropdown.FacetDropdownView#initialize
            */
            initialize: function () {
                var dataProperties = this.$el.data();
                this.properties = dataProperties.properties;

                if (this.model) {
                    this.model.set({ dataProperties: this.properties });
                    this.model.set("sig", this.translateSignatures(this.properties.searchResultsSignature, this.properties.f));
                }

                this.model.on("change", this.render, this);
            },
            /**
             * list of events for Backbone View
            * @memberof module:dropdown.FacetDropdownView
            * @alias module:dropdown.FacetDropdownView#events
             */
            events: {
                "change .facet-dropdown-select": "updateFacet",
                "click .bottom-remove-filter, .clear-filter": "clearFilter"
            },
            /**
             * Calculates value of optionSelected value in model and calls
             * ["updateSignaturesHash"]{@link module dropdown.FacetDropdownView#updateSignaturesHash}
             * inside of ["updateHash"]{@link module:searchQuery.updateHash}
            * @memberof module:dropdown.FacetDropdownView
            * @alias module:dropdown.FacetDropdownView#updateFacet
             */
            updateFacet: function (param) {
                var $selectedOption = this.$el.find(".facet-dropdown-select").find("option:selected"),
                    facetName = decodeURIComponent($selectedOption.data("facetname")),
                    sig = this.model.get("sig");

                if (facetName === "") {
                    this.model.set({ optionSelected: false });
                } else {
                    this.model.set({ optionSelected: true });
                }
                queryModel.updateHash(this.updateSignaturesHash(sig, facetName, {}));
            },
            /**
            * Renders view
            * @memberof module:dropdown.FacetDropdownView
            * @alias module:dropdown.FacetDropdownView#render
            */
            render: function () {
                var inst = this,
                    resultData = inst.model.get("resultData"),
                    dropdown = this.$el.find(".facet-dropdown-select"),
                    emptyValueText = inst.model.get('dataProperties').emptyValueText,
                    facetClose = this.$el.find(".facet-heading > span"),
                    notSelectedOption = dropdown.find("option:first"),
                    notSelectedOptionEntry = $("<option />"),
                    template = _.template(inst.model.get("template")),
                    templateResult = template({ results: resultData, emptyText: emptyValueText });

                this.manageVisibilityByData(this.$el, resultData)
                notSelectedOptionEntry.text(notSelectedOption.text());
                notSelectedOptionEntry.data("facetname", "");

                if (this.model.get("optionSelected")) {
                    facetClose.addClass("has-active-facet");
                } else if (notSelectedOption.data("facetname") === "") {
                    facetClose.removeClass("has-active-facet");
                }

                dropdown.empty().append(notSelectedOptionEntry).append(templateResult);




                //re-set up the fancy dropdowns
                var numberOfOptions = dropdown.children('option').length;
      
                dropdown.addClass('select-hidden'); 
                //dropdown.wrap('<div class="select"></div>');
                //dropdown.after('<div class="select-styled"></div>');
    
                var $styledSelect = dropdown.next('div.select-styled');
                //$styledSelect.text(dropdown.children('option').eq(0).text());
                $styledSelect.text(dropdown.find(":selected").text());

                
                var $list = $styledSelect.next("ul");
                $list.children().remove();
      
                for (var i = 0; i < numberOfOptions; i++) {
                    $('<li />', {
                        text: dropdown.children('option').eq(i).text(),
                        rel: dropdown.children('option').eq(i).val()
                    }).appendTo($list);
                    //if ($this.children('option').eq(i).is(':selected')){
                    //  $('li[rel="' + $this.children('option').eq(i).val() + '"]').addClass('is-selected')
                    //}
                }
      
                var $listItems = $list.children('li');
      
                $styledSelect.off("click");

                $styledSelect.click(function(e) {
                    e.stopPropagation();
                    $(this).parents('.selectWrapper').toggleClass('open');
                    $('div.select-styled.active').not(this).each(function(){
                        $(this).removeClass('active').next('ul.select-options').hide();
                    });
                    $(this).toggleClass('active').next('ul.select-options').toggle();
                });
            
                $styledSelect.blur(function(e) {
                    e.stopPropagation();
                    if(open){
                        $(this).parents('.selectWrapper').toggleClass('open');
                        open = !open;
                    }
                });

                $listItems.click(function(e) {
                    e.stopPropagation();
                    $styledSelect.text(($(this)).text()).removeClass('active');
                    $styledSelect.parents('.selectWrapper').toggleClass('open');
                    dropdown.val($(this).attr('rel'));
                    $list.hide();
                    $(dropdown).trigger("change");
                    //console.log($this.val());
                });
            
                $(document).click(function() {
                    $styledSelect.removeClass('active');
                    $list.hide();
                });
            },
            /**
            * Clears selected filters and updates hash by calling
            * ["updateHash"]{@link module:searchQuery.updateHash}
            * @memberof module:dropdown.FacetDropdownView
            * @alias module:dropdown.FacetDropdownView#clearFilter
             */
            clearFilter: function () {
                var dropdown = this.$el.find(".facet-dropdown-select"),
                    facetClose = this.$el.find(".facet-heading > span"),
                    sig = this.model.get("sig");

                queryModel.updateHash(this.updateSignaturesHash(sig, "", {}));
                this.model.set({ optionSelected: false });
                facetClose.removeClass("has-active-facet");
                dropdown.val(dropdown.find("option:first").val());
            }
        });
    /**
    * For each search dropdown component on a page creates instance of
    * ["FacetDropdownModel"]{@link module:dropdown.FacetDropdownModel} and
    * ["FacetDropdownView"]{@link module:dropdown.FacetDropdownView}
    * @memberOf module:dropdown
    * @alias module:dropdown.init
    */

    api.init = function () {
        if ($("body").hasClass("on-page-editor")) {
            return;
        }

        queryModel = XA.component.search.query;
        urlHelperModel = XA.component.search.url;

        var facetDropdownList = $(".facet-dropdown:not(.initialized)");

        _.each(facetDropdownList, function (elem) {
            var $el = $(elem),
                view = new FacetDropdownView({ el: $el, model: new FacetDropdownModel() });

            $el.addClass("initialized");
        });
    };
    /**
    *
    * @memberOf module:dropdown
    * @alias module:dropdown.getFacetDataRequestInfo
    * @returns {Array<FacetDataRequestInfo>} facet data needed for request
    */
    api.getFacetDataRequestInfo = function () {
        var facetDropdownList = $(".facet-dropdown"),
            result = [];

        _.each(facetDropdownList, function (elem) {
            var properties = $(elem).data().properties,
                signatures = properties.searchResultsSignature.split(','),
                i;

            for (i = 0; i < signatures.length; i++) {
                result.push({
                    signature: signatures[i] === null ? "" : signatures[i],
                    facetName: properties.f,
                    endpoint: properties.endpoint,
                    filterWithoutMe: true
                });
            }
        });

        return result;
    };

    return api;

}(jQuery, document));

XA.register("facetDropdown", XA.component.search.facet.dropdown);

/*  component-search-load-more  */
/**
 * Loads more component functionality
 * @module searchLoadMore
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with load more component
*/
XA.component.search.loadMore = (function ($, document) {
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:searchFacetRangeSlider
    */
    var api = {},
        SearchLoadMoreModel,
        SearchLoadMoreView;
    /**
    * @name module:searchLoadMore.SearchLoadMoreModel
    * @constructor
    * @augments Backbone.Model
    */
    SearchLoadMoreModel = Backbone.Model.extend(
        /** @lends module:searchLoadMore.SearchLoadMoreModel.prototype **/

        {
            /**
            * Default model options
            * @default
            */
            defaults: {
                dataProperties: {},
                sig: []
            }
        });
    /**
    * @name module:searchLoadMore.SearchLoadMoreView
    * @constructor
    * @augments Backbone.View
    */
    SearchLoadMoreView = Backbone.View.extend(
        /** @lends module:searchLoadMore.SearchLoadMoreView.prototype **/
        {
            /**
            * Initially sets data to model and watches events on which
            * view should be updated
            * @listens module:XA.component.search.vent~event:results-loaded
            * @memberof module:searchLoadMore.SearchLoadMoreView
            */
            initialize: function () {
                var dataProperties = this.$el.data(),
                    that = this;

                if (dataProperties.properties.searchResultsSignature === null) {
                    dataProperties.properties.searchResultsSignature = "";
                }

                if (this.model) {
                    this.model.set('sig', dataProperties.properties.searchResultsSignature.split(','));
                }

                XA.component.search.vent.on("results-loaded", function (results) {
                    var sig = that.model.get('sig'),
                        offset = typeof results.offset !== "undefined" ? results.offset : 0,
                        i;

                    for (i = 0; i < sig.length; i++) {
                        if (sig[i] === results.searchResultsSignature) {
                            if (results.pageSize >= results.dataCount || offset + results.pageSize >= results.dataCount) {
                                that.$el.hide();
                            } else {
                                that.$el.show();
                            }
                        }
                    }
                });
            },
            /**
            * list of events for Backbone View
            * @memberof module:searchLoadMore.SearchLoadMoreView
            * @alias module:searchLoadMore.SearchLoadMoreView#events
            */
            events: {
                'mousedown input': 'loadMore'
            },
            /**
           * Load more search results
           * @fires  XA.component.search.vent~event:loadMore
           * @listens module:XA.component.search.vent~event:results-loaded
           * @memberof module:searchLoadMore.SearchLoadMoreView 
           * @alias module:searchLoadMore.SearchLoadMoreView#loadMore 
           */
            loadMore: function () {
                var sig = this.model.get('sig'),
                    i;

                for (i = 0; i < sig.length; i++) {
                    XA.component.search.vent.trigger('loadMore', {
                        sig: sig[i]
                    });
                }
            }
        });
    /**
    * For each search facet range slider component on a page creates instance of 
    * ["SearchLoadMoreModel"]{@link module:searchLoadMore.SearchLoadMoreModel} and 
    * ["SearchLoadMoreView"]{@link module:searchLoadMore.SearchLoadMoreView} 
    * @memberOf module:searchLoadMore
    * @alias module:searchLoadMore.init
    */
    api.init = function () {
        if ($('body').hasClass('on-page-editor')) {
            return;
        }

        var searchLoadMore = $('.load-more:not(.initialized)');

        _.each(searchLoadMore, function (elem) {
            var $el = $(elem),
                view = new SearchLoadMoreView({ el: $el, model: new SearchLoadMoreModel() });

            $el.addClass("initialized");
        });
    };

    return api;

}(jQuery, document));

XA.register('searchLoadMore', XA.component.search.loadMore);


/*  component-search-page-selector  */
/**
 * Facet page selector component functionality
 * @module searchPageSelector
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with component page selector
*/
XA.component.search.pageSelector = (function ($, document) {
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:searchPageSelector
    */
    var api = {},
        queryModel;
    facetName = "e";
    /**
    * @name module:searchPageSelector.SearchPageSelectorModel
    * @constructor
    * @augments Backbone.Model
    */
    var SearchPageSelectorModel = Backbone.Model.extend(
        /** @lends module:searchPageSelector.SearchPageSelectorModel.prototype **/
        {
            /**
            * Default model options
            * @default
            */
            defaults: {
                dataProperties: {},
                resultsCount: 0,
                offset: 0,
                selectedValue: 1,
                pageSize: 0,
                repeatRequest: false,
                template: "<div class='component-content container'>" +
                    "<div class='page-selector-wrapper'>" +
                    "<ul class='page-selector-list'> " +
                    "<li class='page-selector-item-first'><a href='#'><%= data.first %></a></li>" +
                    "<li class='page-selector-item-previous'><a href='#'><%= data.previous %></a></li>" +
                    "<% var beforePage = 0; %>" +
                    "<% _.each(data.pages, function(page){ %>" +
                    "<% if((beforePage+1) != page.number){ %>" +
                    "<li><span class='page-selector-more'>...</span></li>" +
                    "<% } %>" +
                    "<% beforePage = page.number; %>" +
                    "<% if(data.selectedValue === page.number){ %>" +
                    "<% active = 'active'; %>" +
                    "<% }else { active = '' } %>" +
                    "<li><a class='page-selector-item-link <%= active %>' data-offset='<%= page.offset %>' data-itemNumber='<%= page.number %>' href='#'><%= page.number %></a></li>" +
                    "<% }); %>" +
                    "<li class='page-selector-item-next'><a href='#'><%= data.next %></a></li>" +
                    "<li class='page-selector-item-last'><a href='#'><%= data.last %></a></li>" +
                    "</ul>" +
                    "</div>" +
                    "</div>",
                sig: [],
                timeStamp: new Date().getTime()
            }
        });
    /**
     * @name module:searchPageSelector.SearchPageSelectorView
     * @constructor
     * @augments Backbone.View
     */
    var SearchPageSelectorView = XA.component.search.baseView.extend(
        /** @lends module:searchPageSelector.SearchPageSelectorView **/
        {
            /**
           * Initially sets data to model and watches events on which
           * view should be updated
           * @listens module:searchPageSelector.SearchPageSelectorView~event:change
           * @listens module:XA.component.search.vent~event:results-loaded
           * @memberof module:searchPageSelector.SearchPageSelectorView
           * @alias module:searchPageSelector.SearchPageSelectorView#initialize
           */
            initialize: function () {
                var dataProp = this.$el.data();

                if (dataProp.properties.searchResultsSignature === null) {
                    dataProp.properties.searchResultsSignature = "";
                }

                this.model.set("dataProperties", dataProp);
                this.model.set("sig", this.translateSignatures(dataProp.properties.searchResultsSignature, facetName));
                this.model.on("change", this.render, this);

                XA.component.search.vent.on("results-loaded", this.handleLoadedData.bind(this));

                //check whether page is opened from disc - if yes then we are in Creative Exchange mode and lets mock some content
                if (window.location.href.startsWith("file://")) {
                    this.model.set({
                        "resultsCount": 10,
                        "pageSize": 2,
                        "selectedValue": 2
                    });
                }
            },
            /**
             * list of events for Backbone View
             * @memberof module:searchPageSelector.SearchPageSelectorView
             * @alias module:searchPageSelector.SearchPageSelectorView#events
             */
            events: {
                'click .page-selector-item-link': "updateSelectedValue",
                'click .page-selector-item-first a': "showFirstPage",
                'click .page-selector-item-last a': "showLastPage",
                'click .page-selector-item-previous a': "showPrevPage",
                'click .page-selector-item-next a': "showNextPage"
            },
            /**
            * Updates model values with selectedValue
            * and data object from server response
            * @param {Object} data Data that contains component properties
            * @param {Number} selectedValue number of selected page 
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#updateModelAfterSearch
            */
            updateModelAfterSearch: function (data, selectedValue) {
                this.model.set({
                    "pageSize": parseInt(data.pageSize),
                    "resultsCount": parseInt(data.dataCount),
                    "offset": parseInt(data.offset),
                    "selectedValue": parseInt(selectedValue)
                });
                this.model.set("timeStamp", new Date().getTime());
                this.updateElementCssClass(data);
            },
            /**
            * Function that based on selected component values sets css class
            * page-selector-empty or page-selector-single-page
            * @param {Object} data Data that contains component properties
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#updateElementCssClass
            */
            updateElementCssClass: function (data) {
                this.el.classList.remove("page-selector-empty")
                this.el.classList.remove("page-selector-single-page")
                if (data.dataCount === 0) {
                    this.el.classList.add("page-selector-empty")
                } else if (data.pageSize >= data.dataCount || data.offset > data.dataCount) {
                    this.el.classList.add("page-selector-single-page")
                }
            },
            /**
            * Updates hash according to changed component value
            * @param {Event} event Event object that contains target with selected component value
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#updateSelectedValue
            */
            updateSelectedValue: function (event) {
                event.preventDefault();

                var sig = this.model.get("sig"),
                    dataProp = $(event.target).data();

                queryModel.updateHash(this.updateSignaturesHash(sig, dataProp.offset, {}));

                this.navigateToMain();
            },
            /**
            * Switches page selector to the first page and updates hash parameters
            * @param {Event} event Event object that contains target with selected component value
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#showFirstPage
            */
            showFirstPage: function (event) {
                event.preventDefault();

                var sig = this.model.get("sig"),
                    dataProp = $(event.target).data();

                queryModel.updateHash(this.updateSignaturesHash(sig, 0, {}));

                this.navigateToMain();
            },
            /**
            * Switches page selector to the last page and updates hash parameters
            * @param {Event} event Event object that contains target with selected component value
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#showLastPage
            */
            showLastPage: function (event) {
                event.preventDefault();
                var lastPageItems = this.model.get("resultsCount") % this.model.get("pageSize"),
                    offset = this.model.get("resultsCount") - ((lastPageItems === 0) ? this.model.get("pageSize") : lastPageItems),
                    sig = this.model.get("sig");

                queryModel.updateHash(this.updateSignaturesHash(sig, offset, {}));

                this.navigateToMain();
            },
            /**
            * Switches page selector to the next page and updates hash parameters
            * @param {Event} event Event object that contains target with selected component value
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#showNextPage
            */
            showNextPage: function (event) {
                event.preventDefault();

                var offset = this.model.get("offset"),
                    sig = this.model.get("sig");

                if ((offset + this.model.get("pageSize")) < this.model.get("resultsCount")) {
                    offset += this.model.get("pageSize");
                }

                queryModel.updateHash(this.updateSignaturesHash(sig, offset, {}));

                this.navigateToMain();
            },
            /**
            * Switches page selector to the precious page and updates hash parameters
            * @param {Event} event Event object that contains target with selected component value
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#showPrevPage
            */
            showPrevPage: function (event) {
                event.preventDefault();

                var offset = this.model.get("offset"),
                    dataProp = $(event.target).data(),
                    sig = this.model.get("sig");

                if ((offset - this.model.get("pageSize")) >= 0) {
                    offset -= this.model.get("pageSize");
                }

                queryModel.updateHash(this.updateSignaturesHash(sig, offset, {}));

                this.navigateToMain();
            },
            /**
            * Renders view
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#render
            */
            render: function () {
                var inst = this,
                    dataProp = this.model.get("dataProperties").properties,
                    resultsCount = this.model.get("resultsCount"),
                    pageSize = this.model.get("pageSize"),
                    selectedValue = this.model.get("selectedValue"),
                    pagesCount = Math.ceil(resultsCount / pageSize),
                    pages = [],
                    rangeStart = selectedValue - dataProp.treshold / 2,
                    rangeEnd = selectedValue + dataProp.treshold / 2,
                    templateObj;

                if (rangeStart < 0) {
                    rangeEnd += Math.abs(rangeStart);
                }

                if (rangeEnd > pagesCount) {
                    rangeStart -= (rangeEnd - pagesCount);
                }

                if (dataProp.treshold >= pagesCount) {
                    for (var i = 0; i < pagesCount; i++) {
                        pages.push({ number: i + 1, offset: i * pageSize });
                    }
                }
                else {
                    for (var i = 1; i <= pagesCount; i++) {
                        if ((i === 1) || (i === pagesCount)) {
                            pages.push({ number: i, offset: (i - 1) * pageSize });
                        }
                        else if ((i >= rangeStart) && (i <= rangeEnd)) {
                            pages.push({ number: i, offset: (i - 1) * pageSize });
                        }
                    }
                }

                templateObj = {
                    previous: dataProp.previous,
                    first: dataProp.first,
                    next: dataProp.next,
                    last: dataProp.last,
                    pages: pages,
                    selectedValue: selectedValue
                };
                var template = _.template(inst.model.get("template"));
                var templateResult = template({ data: templateObj });
                this.$el.html(templateResult);

                this.handleButtonState(selectedValue, pagesCount);
            },
            /**
            * Sets up page selectors buttons state depending on selected values
            * @param {Number} selectedPage number of selected page
            * @param {Number} pageCount number of pages
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#handleButtonState
            */
            handleButtonState: function (selectedPage, pageCount) {
                this.$el.find(".page-selector-item-last, .page-selector-item-next").removeClass("inactive");
                this.$el.find(".page-selector-item-first, .page-selector-item-previous").removeClass("inactive");


                if (pageCount == 0) {
                    this.$el.find(".page-selector-item-first, .page-selector-item-previous").addClass("inactive");
                    this.$el.find(".page-selector-item-last, .page-selector-item-next").addClass("inactive");
                }
                else {
                    if (selectedPage == 1) {
                        this.$el.find(".page-selector-item-first, .page-selector-item-previous").addClass("inactive");
                    }
                    if (selectedPage == pageCount) {
                        this.$el.find(".page-selector-item-last, .page-selector-item-next").addClass("inactive");
                    }
                }
            },
            /**
            * Updates hash and model based on data object
            * @param {Object} data Data that contains component properties
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#handleLoadedData
            */
            handleLoadedData: function (data) {
                var that = this,
                    sig = this.model.get("dataProperties").properties.searchResultsSignature.split(','),
                    hash = queryModel.parseHashParameters(window.location.hash),
                    newSelectedValue,
                    param,
                    hashObj,
                    i;

                if (typeof data.offset === 'undefined') {
                    data.offset = 0;
                }

                for (i = 0; i < sig.length; i++) {
                    if (encodeURIComponent(sig[i]) === data.searchResultsSignature) {
                        if (data.pageSize > data.dataCount || data.offset > data.dataCount) {
                            //When we have less results then page size then show first page
                            this.updateModelAfterSearch(data, 1);
                            //Wait till all requests are done and go to first page
                            setTimeout(function () {
                                //queryModel.updateHash({e: 0}); //- this will work, but doesn't give us proper browser history
                                hashObj = queryModel.parseHashParameters(window.location.hash);
                                param = data.searchResultsSignature !== "" ? data.searchResultsSignature + "_e" : "e";
                                if (hash[param] !== "0") {
                                    hashObj[param] = 0;
                                    Backbone.history.navigate(that.createFirstPageUrlHash(hashObj), { trigger: true, replace: true });
                                }
                            }, 100);
                        } else {
                            newSelectedValue = Math.ceil(data.offset / data.pageSize) + 1;
                            this.updateModelAfterSearch(data, newSelectedValue);
                        }
                    }
                }
            },
            /**
            * Calculates hash parameters for first page
            * @param {Object} data Data that contains component properties
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#createFirstPageUrlHash
            * @returns {String} hash parameter for first page
            */
            createFirstPageUrlHash: function (hashObj) {
                var hashStr = "";

                var i = 0;
                _.each(hashObj, function (item, key) {
                    if (i > 0) {
                        hashStr += "&";
                    }
                    i++;
                    hashStr += key + "=" + item;
                });

                return hashStr;
            },

            navigateToMain: function () {
                var mainTop = document.getElementsByTagName("main")[0].offsetTop;
                window.scrollTo({ top: mainTop, behavior: 'smooth'});
            }
        });

    /**
    * For each page selector on a page creates instance of 
    * ["SearchPageSelectorModel"]{@link module:searchPageSelector.SearchPageSelectorModel} and 
    * ["SearchPageSelectorView"]{@link module:searchPageSelector.SearchPageSelectorView} 
    * @memberOf module:searchPageSelector
    * @alias module:searchPageSelector.SearchPageSelectorView.init
    */
    api.init = function () {
        if ($("body").hasClass("on-page-editor")) {
            return;
        }

        queryModel = XA.component.search.query;

        var searchPageSelector = $(".page-selector:not(.initialized)");

        _.each(searchPageSelector, function (elem) {
            var $el = $(elem);

            new SearchPageSelectorView({ el: $el, model: new SearchPageSelectorModel });
            
            $el.addClass("initialized");
        });
    };

    return api;

}(jQuery, document));

XA.register('searchPageSelector', XA.component.search.pageSelector);


/*  component-search-page-size  */
/**
 * Page size component functionality
 * @module searchPageSize
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with component page size
*/
XA.component.search.pageSize = (function ($, document) {
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:searchPageSize
    */
    var api = {},
        queryModel,
        queryParameters,
        searchResultModels,
        facetName = "p";
    /**
    * @name module:searchPageSize.SearchPageSizeModel
    * @constructor
    * @augments Backbone.Model
    */
    var SearchPageSizeModel = Backbone.Model.extend(
        /** @lends module:searchPageSize.SearchPageSizeModel.prototype **/
        {
            /**
            * Default model options
            */
            defaults: {
                sig: []
            }
        });
    /**
     * @name module:searchPageSize.SearchPageSizeView
     * @constructor
     * @augments Backbone.View
     */
    var SearchPageSizeView = XA.component.search.baseView.extend(
        /** @lends module:searchPageSize.SearchPageSizeView **/
        {
            clicks: 0,
            /**
            * Initially sets data to model and watches events on which
            * view should be updated
            * @listens module:XA.component.search.vent~event:hashChanged
            * @memberof module:searchPageSize.SearchPageSizeView
            * @alias module:searchPageSize.SearchPageSizeView#initialize
            */
            initialize: function () {
                var inst = this,
                    pageSize = this.getCurrentPageSize(),
                    dataProperties = this.$el.data().properties;


                this.model.set("sig", this.translateSignatures(dataProperties.searchResultsSignature, facetName));

                this.selectOption(pageSize);

                if (typeof pageSize === 'undefined' && dataProperties.defaultPageSize !== "") {
                    inst.selectOption(dataProperties.defaultPageSize);
                }

                XA.component.search.vent.on("hashChanged", function (hash) {
                    var sig = inst.model.get("sig"), i;
                    for (i = 0; i < sig.length; i++) {
                        if (hash.hasOwnProperty(sig[i])) {
                            inst.selectOption(hash[sig[i]]);
                        }
                    }
                });
            },
            /**
             * list of events for Backbone View
             * @memberof module:searchPageSize.SearchPageSizeView
             * @alias module:searchPageSize.SearchPageSizeView#events
             */
            events: {
                'click select': 'updatePageSizeClick',
                'change select': 'updatePageSizeSelect'
            },
            /**
            * Get page size value from hash
            * @memberOf module:searchPageSize.SearchPageSizeView
            * @alias module:searchPageSize.SearchPageSizeView#getCurrentPageSize
            * @returns {String} value of current page size
            */
            getCurrentPageSize: function () {
                var hashObj = queryModel.parseHashParameters(window.location.hash),
                    sig = this.model.get("sig"),
                    i;

                for (i = 0; i < sig.length; i++) {
                    if (hash.hasOwnProperty(sig[i])) {
                        return hash[sig[i]];
                    }
                }
            },
            /**
            * Manage 'selected' attribute for options 
            * @param {String} pageSize active page 
            * @memberOf module:searchPageSize.SearchPageSizeView
            * @alias module:searchPageSize.SearchPageSizeView#selectOption
            */
            selectOption: function (pageSize) {
                if (pageSize !== undefined) {
                    var selectedOption = this.$el.find("select option[value='" + pageSize + "']");
                    selectedOption.siblings().removeAttr('selected');
                    selectedOption.attr('selected', 'selected');
                }
            },
            /**
            * Updates page size according to value of selected option
            * @param {Event} event active page 
            * @memberOf module:searchPageSize.SearchPageSizeView
            * @alias module:searchPageSize.SearchPageSizeView#updatePageSizeClick
            */
            updatePageSizeClick: function (event) {
                this.clicks++;
                if (this.clicks === 2) {
                    var pageSize = $(event.target).find("option:selected").val();
                    if (pageSize !== undefined) {
                        this.updatePageSize(pageSize);
                    }
                    this.clicks = 0;
                }
            },
            /**
           * Updates hash with selected page value
           * @param {String} pageSize active page 
           * @memberOf module:searchPageSize.updatePageSize
           * @alias module:searchPageSize.SearchPageSizeView#updatePageSize
           */
            updatePageSize: function (pageSize) {
                var sig = this.model.get("sig");
                queryParameters.updateHash(this.updateSignaturesHash(sig, pageSize, {}));
            },
            /**
            * Calls ['updatePageSize']{@link module:searchPageSize.SearchPageSizeView#updatePageSize}
            *  with select page as a parameter
            *  @param {Event} param event object with target element
            * @memberOf module:searchPageSize.updatePageSize
            * @alias module:searchPageSize.SearchPageSizeView#updatePageSizeSelect
            */
            updatePageSizeSelect: function (param) {
                var pageSize = param.currentTarget.value;
                this.updatePageSize(pageSize);
            }
        });

    /**
    * For each page size component on a page creates instance of 
    * ["SearchPageSizeModel"]{@link module:searchPageSize.SearchPageSizeModel} and 
    * ["SearchPageSizeView"]{@link module:searchPageSize.SearchPageSizeView} 
    * @memberOf module:searchPageSelector
    * @alias module:searchPageSize.SearchPageSizeView.init
    */
    api.init = function () {
        if ($("body").hasClass("on-page-editor")) {
            return;
        }

        queryModel = XA.component.search.query;
        queryParameters = XA.component.search.parameters;
        searchResultModels = XA.component.search.results.searchResultModels;

        var searchPageSize = $(".page-size:not(.initialized)");

        _.each(searchPageSize, function (elem) {
            var $el = $(elem),
                view = new SearchPageSizeView({ el: $el, model: new SearchPageSizeModel() });

            $el.addClass("initialized");
        });
    };
    /**
   * Returns default page size 
   * @memberOf module:searchPageSize
   * @alias module:searchPageSize.getDefaultPageSizes
   * @returns {Array|Number} array with signature and page sizes or -1
   */
    api.getDefaultPageSizes = function () {
        var pageSizeComponents = $(".page-size"),
            data,
            i,
            defaultPageSize,
            searchResultsSignatures,
            pageSize,
            result = [];

        if (pageSizeComponents.length > 0) {
            for (i = 0; i < pageSizeComponents.length; i++) {
                pageSize = $(pageSizeComponents[i]);
                data = pageSize.data();
                searchResultsSignatures = data.properties.searchResultsSignature.split(',')
                defaultPageSize = data.properties.defaultPageSize;
                result.push({
                    signatures: searchResultsSignatures,
                    defaultPageSize: defaultPageSize !== '' ? defaultPageSize : pageSize.find("select option:first-child").val()
                });
            }
            return result;
        }

        return -1;
    }

    return api;

}(jQuery, document));

XA.register('searchPageSize', XA.component.search.pageSize);

/*  component-search-parameters  */
/**
 * Provides api for manipulating with hashes
 * @module searchParameters
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for work with hash
*/
XA.component.search.parameters = (function ($, document) {
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:searchParameters
    */
    var api = {},
        queryModel,
        searchResultModels,
        initialized = false,
        defaults = {};
    /**
    * Registers modules that search parameter module needs
    * @memberOf module:searchParameters
    * @alias module:searchParameters.init
    */

    api.init = function () {
        if ($("body").hasClass("on-page-editor") || initialized) {
            return;
        }

        queryModel = XA.component.search.query;
        searchResultModels = XA.component.search.results.searchResultModels;
        initialized = true;
    };
    /**
    * Fills default variable with hashes and their values
    * @param {Object} hash Hash represented as an object
    * @memberOf module:searchParameters
    * @alias module:searchParameters.registerDefault
    */
    api.registerDefault = function (hash) {
        _.each(hash, function (item, key) {
            defaults[key] = hash[key].toString();
        });
    };
    /**
    * Updates hash with new one provided as an parameter
    * @param {Object} newHash Hash represented as an object
    * @memberOf module:searchParameters
    * @alias module:searchParameters.updateHash
    */
    api.updateHash = function (newHash) {
        _.each(newHash, function (item, key) {
            if (defaults[key] === newHash[key].toString()) {
                newHash[key] = "";
            }
        });

        queryModel.updateHash(newHash);
    }

    return api;

}(jQuery, document));

XA.register('searchParameters', XA.component.search.parameters);

/*  component-search-query  */
/**
 * Provides api for manipulating query model
 * @module SearchQuery
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for work with query model
*/
XA.component.search.query = (function ($, document) {
    /**
    * @name module:SearchQuery.QueryModel
    * @constructor
    * @augments Backbone.Model
    */
    var QueryModel = Backbone.Model.extend(
        /** @lends module:SearchQuery.QueryModel.prototype **/
        {
            /**
            * Default model options
            * @default
            */
            defaults: {
                hash: "",
                hashObj: {}
            },
            /**
            * Checks if hash is not empty and calls 
            * ['createHashObj']{@link module:SearchQuery.QueryModel#createHashObj}
            * @memberof SearchQuery.QueryModel
            * @alias SearchQuery.QueryModel#initialize
            */
            initialize: function () {
                var inst = this,
                    hash = window.location.hash;

                if (hash.length) {
                    this.set({ hash: hash });
                    this.createHashObj();
                }
            },
            /**
            * Set hashObj into model
            * @memberof SearchQuery.QueryModel
            * @alias SearchQuery.QueryModel#createHashObj
            */
            createHashObj: function () {
                this.set({ hashObj: this.parseHashParameters(this.get("hash")) });
            },
            /**
            * Converts hash string into Object
            * @param {String} aUrl url for converting hash
            * @memberof SearchQuery.QueryModel
            * @alias SearchQuery.QueryModel#parseHashParameters
            * @returns {Object} hash parameters as an object
            */
            parseHashParameters: function (aUrl) {
                if (aUrl === null || aUrl === "") {
                    return {};
                }

                aUrl = aUrl || window.location.hash;

				//cadence - if the hash is #_ set it back to #
                if(aUrl == "#_")
                   aUrl = "#";

                var vars = {};
                var hashes = aUrl.slice(aUrl.indexOf('#') + 1).split('&');

                hashes = hashes.filter(function (x) {
                    return x !== "";
                });

                for (var i = 0; i < hashes.length; i++) {
                    var hash = hashes[i].split('='),
                        facetPart = hash[0].toLowerCase();
                    if (hash.length > 1) {
                        vars[decodeURIComponent(facetPart)] = decodeURIComponent(hash[1].replace(/[+]/g, " "));
                    }
                    else {
                        vars[decodeURIComponent(facetPart)] = null;
                    }
                }
                return vars;
            },
            /**
           * updates hash parameters
           * @param {Object} newHash hash with new values
           * @param {String} targetUrl targetUrl from component propertie
           * @memberof SearchQuery.QueryModel
           * @alias SearchQuery.QueryModel#updateHash
           */
            updateHash: function (newHash, targetUrl) {
                var inst = this,
                    hashStr = "#",
                    hashObj = this.parseHashParameters(window.location.hash);

                _.each(newHash, function (item, key) {
                    if (item !== "") {
                        hashObj[key] = item;
                    } else {
                        delete hashObj[key];
                    }

                });

                if ((targetUrl === "#") || (targetUrl == undefined)) {
                    targetUrl = window.location.pathname;
                }

                var i = 0;
                _.each(hashObj, function (item, key) {
                    if (i > 0) {
                        hashStr += "&";
                    }
                    i++;
                    hashStr += key + "=" + encodeURIComponent(item).replace(/%7C/g, "|");
                });

                 //cadence - if the hash is # set it to # so that we dont jump to the top of the page
                 if(hashStr == "#")
                    hashStr = "#_";

                Backbone.history.navigate(hashStr, { trigger: true });

                if (hashStr.length) {
                    this.set({ hash: hashStr });
                    this.createHashObj();
                }
            },
            /**
            * Checks if signature present in hash
            * @param {Object} hash hash object
            * @param {String} signature signature for searching in hash parameter
            * @memberof SearchQuery.QueryModel
            * @alias SearchQuery.QueryModel#updateHash
            * @returns {Boolean} True - if hash present, False if not
            */
            isSignaturePresentInHash: function (hash, signature) {
                var hashKeys = Object.keys(hash),
                    isSignatureFoundInHash = false;

                for (var len = hashKeys.length, i = 0; i < len; i++) {
                    if (hashKeys[i].startsWith(signature)) {
                        isSignatureFoundInHash = true;
                        break;
                    }
                }

                return isSignatureFoundInHash;
            }
        });

    return new QueryModel();

}(jQuery, document));

/*  component-search-results  */
/**
 * Search result component functionality
 * Customized for Cadence
 * @module searchResults
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with component search result
*/
XA.component.search.results = (function ($, document) {

    "use strict";
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:searchResults
    */
    var api = {},
        searchResultViews = [],
        searchResultModels = [],
        urlHelperModel,
        queryModel,
        apiModel
    /**
    * @name module:searchResults.SearchResultModel
    * @constructor
    * @augments Backbone.Model
    */
    var SearchResultModel = Backbone.Model.extend(
        /** @lends module:searchResults.SearchResultModel.prototype **/
        {
            defaults: {
                template: "<% if(!results.length){ %><div class='no-results'><%= noResultsText %></div> <% }else { %>" +
                    "<ul class='search-result-list'> " +
                    "<% _.forEach(results, function(result){ %>" +
                    "<li " + "<% if(result.Geospatial){%>data-id='<%= result.Id %>' data-longitude='<%= result.Geospatial.Longitude %>' data-latitude='<%= result.Geospatial.Latitude %>'<% } %>" + "><%= result.Html %></li>" +
                    "<% }); %>" +
                    "</ul>" +
                    "<% } %>" +
                    "<div class='search-result-overlay'>",
                globalSearchTemplate: "<div class='component-content container'> " +
                "<div class='global-search-results-wrapper'>" +
                "<% if(!results.length){ %><div class='no-results rich-text'><%= noResultsText %></div> <% }else { %>" +                    
                    "<% _.forEach(results, function(result){ %>" +
                    "<%= result.Html %>" +
                    "<% }); %>" +
                    "<% } %>" +                    
                    "<div class='search-result-overlay'>" +
                    "</div>" +
                    "</div>",
                templateItems: "<% _.forEach(results, function(result){ %>" +
                    "<li " + "<% if(result.Geospatial){%>data-id='<%= result.Id %>' data-longitude='<%= result.Geospatial.Longitude %>' data-latitude='<%= result.Geospatial.Latitude %>'<% } %>" + "><%= result.Html %></li>" +
                    "<% }); %>",
                globalSearchTemplateItems: "<% _.forEach(results, function(result){ %>" +
                    "<%= result.Html %>" +
                    "<% }); %>",                    
                dataProperties: {},
                blockNextRequest: false,
                noResultsText: "",
                resultData: {},
                loadingInProgress: false,
                loadingMoreInProgress: false,
                resultDataMore: {},
                loadMoreOffset: 0,
                loadMore: false,
                variant:""
            },
            /**
             * Listens for changes on data from server
            * @listens module:XA.component.search.vent~event:facet-data-loaded
            * @listens module:XA.component.search.vent~event:results-loading
            */
            initialize: function () {
                var hashObj = queryModel.parseHashParameters(window.location.hash),
                    signature = encodeURIComponent(this.get("dataProperties").sig),
                    offsetKey = "e_" + signature;

                //If there is "e" in hash object, then one of page selectors on the page is global (not assigned to any of the
                //search results - in such case it will paginate all search results without signature
                if (hashObj.hasOwnProperty("e") && signature === '') {
                    this.set("loadMoreOffset", parseInt(hashObj.e));
                }

                //checks if there is page selector assigned to this specific search results
                if (hashObj.hasOwnProperty(offsetKey)) {
                    this.set("loadMoreOffset", parseInt(hashObj[offsetKey]));
                }

                XA.component.search.vent.on('new-search', this.resetModel.bind(this));
                XA.component.search.vent.on("results-loaded", this.resultsLoaded.bind(this));
                XA.component.search.vent.on('results-loading', this.resultsLoading.bind(this));                
            },
            /**
             * Sets value of blockNextRequest variable
             * @param {String} value new value of blockNextRequest variable
             * @memberof module:searchResults.SearchResultModel
             * @alias module:searchResults.SearchResultModel#blockRequests
             */
            blockRequests: function (value) {
                this.set("blockNextRequest", value);
            },
            /**
            * Returns value of blockNextRequest variable
            * @memberof module:searchResults.SearchResultModel
            * @alias module:searchResults.SearchResultModel#checkBlockingRequest
            * @returns {String} value of blockNextRequest variable
            */
            checkBlockingRequest: function () {
                return this.get("blockNextRequest");
            },
            /**
            * Returns value of component offset from hash parameters
            * @memberof module:searchResults.SearchResultModel
            * @alias module:searchResults.SearchResultModel#getMyOffset
            * @returns {String|0} value of offset
            */
            getMyOffset: function () {
                var hash = queryModel.parseHashParameters(window.location.hash),
                    signature = encodeURIComponent(this.get("dataProperties").sig);
                if (hash.hasOwnProperty("e_" + signature)) {
                    return hash["e_" + signature];
                }
                return 0;
            },
            /**
            * Updates model with new data from resultData parameter
            * @param {Object} resultsData new component data
            * @memberof module:searchResults.SearchResultModel
            * @alias module:searchResults.SearchResultModel#resultsLoaded
            */
            resultsLoaded: function (resultsData) {
                var signature = encodeURIComponent(this.get("dataProperties").sig);

                if (signature === resultsData.searchResultsSignature) {
                    if (this.get("loadMore")) {
                        this.set({ resultDataMore: resultsData });
                        this.set({ loadingMoreInProgress: false });
                        this.unset("loadMore", { silent: true });
                    } else {
                        this.set({ resultData: resultsData });
                        this.set({ loadingInProgress: false });
                        this.unset("resultDataMore", { silent: true });
                    }
                    this.blockRequests(false);
                }
            },
            /**
            * Manages loadingMoreInProgress model variable
            * @param {Object} cid component id
            * @memberof module:searchResults.SearchResultModel
            * @alias module:searchResults.SearchResultModel#resultsLoading
            */
            resultsLoading: function (cid) {
                if (this.cid == cid) {
                    if (this.get("loadMore")) {
                        this.set({ loadingMoreInProgress: true });
                    } else {
                        this.set({ loadingInProgress: true });
                    }
                }
            },
            /**
            * Resets search results model
            * @memberof module:searchResults.SearchResultModel
            * @alias module:searchResults.SearchResultModel#resultsLoading
            */            
            resetModel: function() {
                this.set({ loadMoreOffset: 0 });
                this.unset("resultDataMore", { silent: true });
            }
        });
    /**
    * @name module:searchResults.SearchResultView
    * @constructor
    * @augments Backbone.View
    */
    var SearchResultView = Backbone.View.extend(
        /** @lends module:searchResults.SearchResultView.prototype **/
        {
            /**
            * Initially sets data to model and watches events on which
            * view should be updated
            * @listens module:searchResults.SearchResultModel~event:change:loadingInProgress
            * @listens module:searchResults.SearchResultModel~event:change:loadingMoreInProgress
            * @listens module:searchResults.SearchResultModel~event:change:resultData
            * @listens module:searchResults.SearchResultModel~event:change:resultDataMore
            * @listens module:XA.component.search.vent~event:add-variant-class
            * @listens module:XA.component.search.vent~event:loadMore
            * @listens module:XA.component.search.vent~event:my-location-coordinates-changed
            * @memberof module:searchResults.SearchResultView
            * @alias module:searchResults.SearchResultView#initialize
            */
            initialize: function () {
                var dataProperties = this.$el.data(),
                    noResultsText = this.$el.find(".no-results").html(),
                    searchRouter = XA.component.search.router,
                    searchRouterInstance = searchRouter ? searchRouter.routerInstance : undefined,
                    maxHeight = 0,
                    inst = this;

                if (dataProperties.properties.sig === null) {
                    dataProperties.properties.sig = "";
                }

                if (this.model) {
                    this.model.set({ dataProperties: dataProperties.properties, noResultsText: noResultsText });
                }

                this.model.on("change:loadingInProgress", this.loading, this);
                this.model.on("change:loadingMoreInProgress", this.loadingMore, this);
                this.model.on("change:resultData", this.render, this);
                this.model.on("change:resultDataMore", this.renderPart, this);

                this.$el.on("remove", function () {
                    searchResultModels = searchResultModels.filter(function (m) {
                        return m.cid !== inst.model.cid
                    })
                })

                XA.component.search.vent.on("add-variant-class", function (data) {
                    var signature = inst.model.get("dataProperties").sig;
                    if (data.sig === signature) {
                        inst.$el.removeClass(inst.$el.attr("data-class-variant"));
                        inst.$el.attr("data-class-variant", data.classes);
                        inst.$el.addClass(data.classes);
                    }
                });

                XA.component.search.vent.on("loadMore", function (data) {
                    var signature = inst.model.get("dataProperties").sig;
                    if (data.sig === signature) {
                        XA.component.search.service.getData({
                            loadMore: "true",
                            p: inst.model.get("dataProperties").p,
                            singleRequestMode: signature
                        });
                    }
                });

                XA.component.search.vent.on("my-location-coordinates-changed", function (data) {
                    if (data.sig === inst.model.get("dataProperties").sig && inst.model.get("loadMore")) {
                        inst.$el.find(".search-result-list").html("");
                    }
                });

                if (searchRouterInstance && dataProperties.properties.autoFireSearch && this.$el.closest('.overlay-inner').length) {
                    searchRouterInstance.checkUrl(window.location.hash, true)
                }

                this.render();
            },
            /**
            * list of events for Backbone View
            * @memberof module:searchResults.SearchResultView
            * @alias module:searchResults.SearchResultView#events
            */
            events: {
                'click .search-result-list > li[data-longitude][data-latitude]': "poiClick"
            },
            /**
             * Manage 'loading-in-progress' css class
            * @memberof module:searchResults.SearchResultView
            * @alias module:searchResults.SearchResultView#loading
            */
            loading: function () {
                if (this.model.get("loadingInProgress")) {
                    this.$el.addClass("loading-in-progress");
                } else {
                    this.$el.removeClass("loading-in-progress");
                }
            },
            /**
             * Manage 'loading-more-in-progress' css class
            * @memberof module:searchResults.SearchResultView
            * @alias module:searchResults.SearchResultView#loadingMore
            */
            loadingMore: function () {
                if (this.model.get("loadingMoreInProgress")) {
                    this.$el.addClass("loading-more-in-progress");
                } else {
                    this.$el.removeClass("loading-more-in-progress");
                }
            },
            /**
            * Renders search result list
            * @memberof module:searchResults.SearchResultView
            * @alias module:searchResults.SearchResultView#renderPart
            */
            renderPart: function () {
                var template = _.template(this.model.get("templateItems"));
                var templateResult = template({ results: this.model.get("resultDataMore").data });
                this.$el.find(".search-result-list").append(templateResult);
            },
            /**
             * Renders view
             * @memberof module:searchResults.SearchResultView
             * @alias module:searchResults.SearchResultView#render
             */
            render: function () {
                var inst = this,
                    maxHeight = 0,
                    results = inst.model.get("resultData").data;

                //checks if page is opened from disc - if yes then we are in Creative Exchange mode
                if (window.location.href.startsWith("file://")) {
                    return;
                }

                if (typeof results === "undefined") {
                    results = [];
                }
                
                //if this is the global search, use diff template
                var template;
                if(inst.model.attributes.dataProperties.v == "{E105E282-B09E-494B-B3F4-0BD513727A7D}")
                {
                    template = _.template(inst.model.get("globalSearchTemplate"));    
                }
                else
                {
                     template = _.template(inst.model.get("template"));
                }

                var templateResult = template({ results: results, noResultsText: inst.model.get("noResultsText") });
                this.$el.html(templateResult);
            },
            /**
             * Renders view
             * @param {Event} e Event object that contains target poi elment
             * @fires XA.component.search.vent#center-map
             * @memberof module:searchResults.SearchResultView
             * @alias module:searchResults.SearchResultView#poiClick
             */
            poiClick: function (e) {
                var li = $(e.currentTarget);

                XA.component.search.vent.trigger("center-map", {
                    sig: this.model.get("dataProperties").sig,
                    coordinates: [li.data('latitude'), li.data('longitude')],
                    id: li.data('id')
                });
            }
        });
    /**
    * For each search result component on a page creates instance of 
    * ["SearchResultModel"]{@link module:searchResults.SearchResultModel} and 
    * ["SearchResultView"]{@link module:searchResults.SearchResultView} 
    * @memberOf module:searchResults
    * @alias module:searchResults.init
    */
    api.init = function () {
        if ($("body").hasClass("on-page-editor")) {
            return;
        }

        urlHelperModel = XA.component.search.url;
        queryModel = XA.component.search.query;
        apiModel = XA.component.search.ajax;

        var searchResults = $(".search-results:not(.initialized)");

        _.each(searchResults, function (elem) {
            var $el = $(elem),
                resultsModel = new SearchResultModel();

            searchResultModels.push(resultsModel);
            searchResultViews.push(new SearchResultView({ el: $el, model: resultsModel }));

            $el.addClass("initialized");
        });
    };
    /**
       *Extends API methods with searchResultModels
       * @alias module:searchResults.searchResultViews
       */
    api.searchResultViews = searchResultViews;
    /**
   * Extends API methods with searchResultModels
   * @memberOf module:searchResults
   * @alias module:searchResults.searchResultModels
   */
    api.searchResultModels = searchResultModels;

    return api;

}(jQuery, document));

XA.register('searchResults', XA.component.search.results);

/*  component-search-results-count  */
/**
 * Search result count component functionality
 * @module searchResultsCount
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with search result count component
*/
XA.component.search.results.count = (function ($, document) {
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:searchResultsCount
    */
    var api = {};
    /**
    * @name module:searchResultsCount.SearchResultCountView
    * @constructor
    * @augments Backbone.View
    */
    var SearchResultCountView = Backbone.View.extend(
        /** @lends module:searchResultsCount.SearchResultCountView.prototype **/
        {
            /**
            * Initially shows fake data for creative exchange mode
            * and watches events on which
            * view should be updated in all modes
            * @listens module:XA.component.search.vent~event:results-loaded
            * @memberof module:searchResultsCount.SearchResultCountView
            */
            initialize: function () {
                var dataProperties = this.$el.data(),
                    resultsCountContainer = this.$el.find(".results-count"),
                    inst = this;

                this.resultsCountText = resultsCountContainer.html();

                //check if page is opened from disc - if yes then we are in Creative Exchange mode so let's show fake results count
                if (window.location.href.startsWith("file://")) {
                    resultsCountContainer.html(inst.resultsCountText.replace('{count}', 1));
                    inst.$el.find(".results-count").show();
                    return;
                }

                XA.component.search.vent.on("results-loaded", function (data) {
                    if (inst.$el.find(".results-count").length > 0) {
                        var signature = inst.$el.data("properties").targetSignature;
                        if (signature != "" && signature != data.searchResultsSignature) {
                            return;
                        }
                        resultsCountContainer.html(inst.resultsCountText.replace('{count}', data.dataCount));
                        inst.$el.find(".results-count").show();
                    }
                });
            }
        });

    /**
    * For each search managed range component on a page creates instance of 
    * ["SearchResultCountView"]{@link module:searchResultsCount.SearchResultCountView} 
    * @memberOf module:searchResultsCount
    * @alias module:searchResultsCount.init
    */
    api.init = function () {
        if ($("body").hasClass("on-page-editor")) {
            return;
        }

        var searchResults = $(".search-results-count:not(.initialized)");

        _.each(searchResults, function (elem) {
            var $el = $(elem),
                searchResultsCountView = new SearchResultCountView({ el: $el });

            $el.addClass("initialized");
        });
    };

    return api;

}(jQuery, document));

XA.register('searchResultsCount', XA.component.search.results.count);

/*  component-search-results-filter  */
$(document).on('click', function (e) {
    //that.documentClick(event)
    Backbone.trigger('document-click-event', e);
});

/**
 * Facet facet result filter component functionality
 * @module facetResultsFilter
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with component facet result filter
*/
XA.component.search.facet.resultsfilter = (function ($, document) {
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:facetResultsFilter
    */
    var api = {},
        urlHelperModel,
        queryModel,
        apiModel;
    /**
    * @name module:facetResultsFilter.FacetResultsFilterModel
    * @constructor
    * @augments Backbone.Model
    */
    var FacetResultsFilterModel = XA.component.search.baseModel.extend(
        /** @lends module:facetResultsFilter.FacetResultsFilterModel.prototype **/
        {
            /**
            * Default model options
            * @default
            */
            defaults: {
                template: "<div class='facet-search-filter <% if(!showAllFacets){%>facet-hided<%}%>'><% " +
                    "_.forEach(facet.Values, function(value,key){" +
                    "%><p class='facet-value <% if(highlightBehaviour<=key){ %> hide-facet-value <% } %>' data-facetValue='<%= value.Name !== '' ? encodeURI(value.Name) : '_empty_' %>'>" +
                    "<span><%= value.Name !== '' ? value.Name : emptyText %> " +
                    "<span class='facet-count'>(<%= value.Count %>)</span>" +
                    "</span>" +
                    "</p><%" +
                    " }); %>" +
                    "<% if(highlightBehaviour>=1 && resultsCount>highlightBehaviour){ %>" +
                    "<div class='toogle-facet-visibility'><% if(showAllFacets){ %><%=showLessText%><%}else{%><%=showMoreText%><%} %></div>" +
                    "<%}%>" +
                    "</div>",

                templateMulti: "<div class='facet-search-filter <% if(!showAllFacets){%>facet-hided<%}%>'><% " +
                    "_.forEach(facet.Values, function(value,key){" +
                    "%><p class='facet-value <% if(highlightBehaviour<=key){ %> hide-facet-value <% } %>' data-facetValue='<%= value.Name !== '' ? encodeURI(value.Name) : '_empty_' %>'>" +
                    "<input type='checkbox' name='facetValue' />" +
                    "<label for='facetName'><%= value.Name !== '' ? value.Name : emptyText %> " +
                    "<span class='facet-count' data-facetCount='<%= value.Count %>'>(<%= value.Count %>)</span>" +
                    "</label>" +
                    "</p><%" +
                    " }); %>" +
                    "<% if(highlightBehaviour>=1 && resultsCount>highlightBehaviour){ %>" +
                    "<div class='toogle-facet-visibility'><% if(showAllFacets){ %><%=showLessText%><%}else{%><%=showMoreText%><%} %></div>" +
                    "<%}%>" +
                    "</div>",
                dataProperties: {},
                blockNextRequest: false,
                resultData: {},
                timeStamp: '',
                showAllFacets: false,
                sig: []
            },
            /**
            * Listens to changes on facets and hash
           * @listens module:XA.component.search.vent~event:facet-data-loaded
           * @listens module:XA.component.search.vent~event:facet-data-filtered
           * @listens module:XA.component.search.vent~event:facet-data-partial-filtered
           * @listens module:XA.component.search.vent~event:hashChanged
           */
            initialize: function () {
                //event to get data at the begining or in case that there are no hash parameters in the url - one request for all controls
                XA.component.search.vent.on("facet-data-loaded", this.processData.bind(this));
                //if in the url hash we have this control facet name (someone clicked this control) then we have to listen for partial filtering
                XA.component.search.vent.on("facet-data-partial-filtered", this.processData.bind(this));
                //in case that we are not filtering by this control (not clicked)
                XA.component.search.vent.on("facet-data-filtered", this.processData.bind(this));
                //event after change of hash
                XA.component.search.vent.on("hashChanged", this.updateComponent.bind(this));

                this.set({ facetArray: [] });
            },
            /**
             * Toggles value of blockNextRequest variable
             * @memberof module:facetResultsFilter.FacetResultsFilterModel
             * @alias module:facetResultsFilter.FacetResultsFilterModel#toggleBlockRequests
             */
            toggleBlockRequests: function () {
                var state = this.get("blockNextRequest");
                this.set(this.get("blockNextRequest"), !state);
            },
            /**
            * Processes data that comes as parameter update
            * model and sort facets
            * @param {Object} data Data from server with facet values
            * @memberof module:facetResultsFilter.FacetResultsFilterModel
            * @alias module:facetResultsFilter.FacetResultsFilterModel#processData
            */
            processData: function (data) {
                var inst = this,
                    dataProperties = this.get('dataProperties'),
                    sig = dataProperties.searchResultsSignature.split(','),
                    sortOrder = dataProperties.sortOrder,
                    i;

                if (data.Signature === null) {
                    data.Signature = "";
                }


                for (i = 0; i < sig.length; i++) {
                    if (data.Facets.length > 0 && (data.Signature === sig[i])) {
                        var facedData = _.find(data.Facets, function (f) {
                            return f.Key.toLowerCase() === inst.get('dataProperties').f.toLowerCase();
                        });
                        if (facedData !== undefined) {
                            this.sortFacetArray(sortOrder, facedData.Values);
                            inst.set({ resultData: facedData });
                        }
                    } else {
                        inst.set({ resultData: [] });
                    }
                }
            },
            /**
             * Updates model value 'facetArray' with values from valuesString parameter
             * @param {String} valuesString facet values separated by coma
             * @memberof module:facetResultsFilter.FacetResultsFilterModel
             * @alias module:facetResultsFilter.FacetResultsFilterModel#updateFacetArray
             */
            updateFacetArray: function (valuesString) {
                if (valuesString) {
                    var values = valuesString.split("||"),
                        array = [];
                    for (var i = 0; i < values.length; i++) {
                        array.push(values[i]);
                    }
                    this.set({ facetArray: _.unique(array) });
                }
            },
            /**
             * Sets option selected value to model based on hash
             * @param {Object} hash Hash stored as an object
             * @memberof module:facetResultsFilter.FacetResultsFilterModel
             * @alias module:facetResultsFilter.FacetResultsFilterModel#updateComponent
             */
            updateComponent: function (hash) {
                var sig = this.get("sig");
                for (i = 0; i < sig.length; i++) {
                    if (!hash.hasOwnProperty(sig[i])) {
                        this.set({ facetArray: [] });
                    } else {
                        this.updateFacetArray(hash[sig[i]]);
                    }
                    //in some cases change of facetArray doesn't trigger model change event (why?) and view isn't updates
                    //and because of that timeStamp is updated which properly triggers model change event
                    this.set("timeStamp", (new Date()).getTime());
                }
            }
        });
    /**
    * @name module:facetResultsFilter.FacetResultsFilterView
    * @constructor
    * @augments Backbone.View
    */
    var FacetResultsFilterView = XA.component.search.baseView.extend(
        /** @lends module:facetResultsFilter.FacetResultsFilterView.prototype **/
        {
            /**
            * Initially sets data to model and watches events on which
            * view should be updated
            * @listens module:facetResultsFilter.FacetResultsFilterModel~event:change
            * @memberof module:facetResultsFilter.FacetResultsFilterView
            * @alias module:facetResultsFilter.FacetResultsFilterView#initialize
            */
            initialize: function () {
                var dataProperties = this.$el.data(),
                    hash = queryModel.parseHashParameters(window.location.hash),
                    properties = dataProperties.properties,
                    signatures,
                    i;

                var that = this;

                if (dataProperties.properties.searchResultsSignature === null) {
                    dataProperties.properties.searchResultsSignature = "";
                }

                signatures = this.translateSignatures(properties.searchResultsSignature, properties.f);

                this.model.set({ dataProperties: properties });
                this.model.set("sig", signatures);

                for (i = 0; i < signatures.length; i++) {
                    if (!jQuery.isEmptyObject(_.pick(hash, signatures[i]))) {
                        var values = _.values(_.pick(hash, signatures[i]))[0];
                        this.model.updateFacetArray(values)
                    }
                }

                this.model.on("change", this.render, this);

                //cadence custom
                if(this.$el.hasClass("insights-dropdown"))
                {
                    this.$el.find("details").on('toggle', function (event) {
                        that.toggleDropdown(event);
                    })

                    this.listenTo(Backbone, 'document-click-event', function (e) {
                        that.documentClick(e);
                    });
                }
            },

            /**
             * list of events for Backbone View
            * @memberof module:facetResultsFilter.FacetResultsFilterView
            * @alias module:facetResultsFilter.FacetResultsFilterView#events
             */
            events: {
                'click .facet-value': 'updateFacet',
                'click .filterButton': 'updateFacet',
                'click .clear-filter': 'removeFacet',
                'click .bottom-remove-filter > button': 'removeFacet',
                'click .toogle-facet-visibility': 'toogleFacetVisibility'
            },

            toogleFacetVisibility: function () {
                var showFacets = this.model.get('showAllFacets');
                this.model.set('showAllFacets', !showFacets);
            },

            /**
            * Updates model 'facetArray' based on params
            * @param {Event}  param Event object with current target
            * @memberof module:facetResultsFilter.FacetResultsFilterView
            * @alias module:facetResultsFilter.FacetResultsFilterView#updateFacet
            */
            updateFacet: function (param) {
                var currentFacet = $(param.currentTarget),
                    facetArray = this.model.get('facetArray'),
                    properties = this.model.get('dataProperties'),
                    facetClose = this.$el.find('.facet-heading > span'),
                    facetGroup = currentFacet.parents('.component-content').find('.facet-search-filter'),
                    facetName = properties.f.toLowerCase(),
                    facetDataValue = currentFacet.data('facetvalue'),
                    facetValue = typeof facetDataValue !== "undefined" ? decodeURIComponent(facetDataValue) : facetDataValue,
                    sig = this.model.get('sig'),
                    index,
                    hash = {},
                    i;

                if (properties.multi) {
                    if (facetValue) {
                        if (currentFacet.is(':not(.active-facet)')) {
                            this.setActiveFacet(facetName, facetValue);
                            facetArray.push(facetValue);
                        } else {
                            currentFacet.removeClass('active-facet');

                            currentFacet.find('[type=checkbox]').prop('checked', false);
                            currentFacet.find('[type=checkbox] + label:after').css({ 'background': '#fff' });

                            index = facetArray.indexOf(facetValue);
                            if (index > -1) {
                                facetArray.splice(index, 1);
                            }

                            if (facetArray.length == 0) {
                                facetClose.removeClass('has-active-facet');
                            }
                        }
                        this.model.set({ facetArray: facetArray });

                        if(facetArray.length > 0) {
                            this.enableClearAll();
                        }
                        else {
                            this.disableClearAll();
                        }
                    }

                    //is there any better way to check what action start method?
                    if (currentFacet[0].type == "button") {
                        for (i = 0; i < sig.length; i++) {
                            hash[sig[i]] = _.uniq(facetArray, function (item) {
                                return JSON.stringify(item);
                            }).join("||");
                        }
                        queryModel.updateHash(hash);

                        //cadence custom - close the dropdown if its open
                        this.closeDetails();
                    }
                } else {
                    if (facetValue) {
                        for (i = 0; i < sig.length; i++) {
                            hash[sig[i]] = facetValue;
                        }
                        facetGroup.data('active-facet', hash);
                        this.setActiveFacet(facetName, facetValue);
                        queryModel.updateHash(hash);
                    }
                }
            },

            /**
            * Sets default values for Search Result Filter and updates hash accordingly
            * @param {Event} evt Event object
            * @memberof module:facetResultsFilter.FacetResultsFilterView
            * @alias module:facetResultsFilter.FacetResultsFilterView#removeFacet
            */
            removeFacet: function (evt) {
                evt.preventDefault();

                var facets = this.$el,
                    facetClose = facets.find('.facet-heading > span'),
                    facetValues = facets.find('.facet-value'),
                    sig = this.model.get('sig');

                queryModel.updateHash(this.updateSignaturesHash(sig, "", {}));
                
                //cadence custom - close the dropdown if its open
                this.closeDetails();
                
                facetClose.removeClass('has-active-facet');

                _.each(facetValues, function (single) {
                    var $single = $(single);
                    if ($single.hasClass('active-facet')) {
                        $single.removeClass('active-facet');
                        $single.find('[type=checkbox]').prop('checked', false);
                        $single.find('[type=checkbox] + label:after').css({ 'background': '#fff' });
                    }
                });

                this.model.set({ facetArray: [] });
                
                this.disableClearAll();
            },
            /**
            * Renders view
            * @memberof module:facetResultsFilter.FacetResultsFilterView
            * @alias module:facetResultsFilter.FacetResultsFilterView#render
            */
            render: function () {
                var inst = this,
                    resultData = this.model.get("resultData"),
                    facetClose = this.$el.find('.facet-heading > span'),
                    facetNames = this.model.get('dataProperties').f.split('|'),
                    emptyValueText = this.model.get('dataProperties').emptyValueText,
                    highlightThreshold = this.model.get('dataProperties').highlightThreshold,
                    showLessText = this.model.get('dataProperties').showLessText,
                    showMoreText = this.model.get('dataProperties').showMoreText,
                    highlightBehaviour = parseInt(this.model.get('dataProperties').highlightBehaviour),
                    showAllFacets = this.model.get('showAllFacets'),
                    hash = queryModel.parseHashParameters(window.location.hash),
                    sig = this.model.get('sig'),
                    template, facetName, templateResult;

                //checks if page is opened from disc - if yes then we are in Creative Exchange mode
                if (window.location.href.startsWith("file://")) {
                    return;
                }
                this.manageVisibilityByData(this.$el, resultData)
                if (resultData !== undefined) {
                    if (inst.model.get('dataProperties').multi === true) {
                        template = _.template(inst.model.get("templateMulti"));
                    } else {
                        template = _.template(inst.model.get("template"));
                    }
                    templateResult = template({
                        facet: resultData, emptyText: emptyValueText, showLessText: showLessText,
                        showMoreText: showMoreText, highlightBehaviour: highlightBehaviour, showAllFacets: showAllFacets,
                        resultsCount: resultData.Values ? resultData.Values.length : -1
                    });
                }

                inst.$el.find(".contentContainer").html(templateResult);

                //checks url hash for facets and runs setActiveFacet method for each facet filter
                _.each(facetNames, function (val) {
                    facetName = val.toLowerCase();
                    for (var i = 0; i < sig.length; i++) {
                        if (!jQuery.isEmptyObject(_.pick(hash, sig))) {
                            var values = _.values(_.pick(hash, sig))[0];
                            if (values) {
                                inst.setActiveFacet(facetName, values);

                                //cadence custom
                                //if this is an insights-dropdown, update the summary text
                                if(inst.$el.hasClass("insights-dropdown"))
                                {
                                    var summary = inst.$el.find("summary");
                                    //var clearFilter = inst.$el.find("span.clear-filter");

                                    var activeFacetCount = values.split("||").length;

                                    if(activeFacetCount > 0)
                                    {
                                        summary.text(summary.data("originaltitle") + " (" + activeFacetCount +")");
                                    }
                                    else{
                                        summary.text(summary.data("originaltitle"));
                                    }
                                }
                                //If this rendering is supporting multiple signatures, we will mark active facet once.
                                return;
                            }
                        }
                        else{
                            //cadence custom
                            if(inst.$el.hasClass("insights-dropdown"))
                            {
                                var summary = inst.$el.find("summary");
                                summary.text(summary.data("originaltitle"));
                            }
                        }
                    }
                });

                //highlights facets count greater than chosen threshold
                if (highlightThreshold) {
                    this.handleThreshold(highlightThreshold);
                }

                //if no facet is selected, remove previously highlighted cross icon (while back button)
                if (this.model.get("facetArray").length === 0) {
                    facetClose.removeClass('has-active-facet');
                    this.disableClearAll();
                } else {
                    facetClose.addClass('has-active-facet');
                    this.enableClearAll();
                }
            },
            /**
            * Manages search result filter active state
            * @param {String} facetGroupName facet name
            * @param {String} facetValueName facet value
            * @memberof module:facetResultsFilter.FacetResultsFilterView
            * @alias module:facetResultsFilter.FacetResultsFilterView#setActiveFacet
            */
            setActiveFacet: function (facetGroupName, facetValueName) {
                var properties = this.model.get('dataProperties'),
                    facetChildren = this.$el.find('p[data-facetvalue]'),
                    facetClose = this.$el.find('.facet-heading > span'),
                    inst = this,
                    facetValue,
                    values;

                facetValueName = facetValueName.toString().toLowerCase();
                facetValue = this.$el.find("[data-facetvalue]").filter(function () {
                    return decodeURIComponent($(this).attr("data-facetvalue").toLowerCase()) === facetValueName;
                });


                if (typeof (facetValueName) !== "undefined" && facetValueName !== null) {
                    values = facetValueName.split("||");
                } else {
                    return;
                }

                if (values.length > 1) {
                    properties.multi = true;
                }

                if (properties.multi) {
                    //multi selection facet search results
                    _.each(facetChildren, function (val) {

                        if (values.length > 1) {
                            for (var i = 0, l = values.length; i < l; i++) {
                                facetValue = inst.$el.find("[data-facetvalue]").filter(function () {
                                    return decodeURIComponent($(this).attr('data-facetvalue').toLowerCase()) === values[i];
                                });
                                if (val === facetValue[0]) {
                                    $(val).addClass('active-facet');
                                    $(val).find('[type=checkbox]').prop('checked', true);
                                }
                            }
                        }

                        if (val === facetValue[0]) {
                            $(val).addClass('active-facet');
                            $(val).find('[type=checkbox]').prop('checked', true);
                        }


                    });
                } else {
                    //single selection facet search results filter allows only one facet type to be selected
                    _.each(facetChildren, function (val) {
                        if (val !== facetValue[0]) {
                            $(val).removeClass('active-facet');
                            $(val).find('[type=checkbox]').prop('checked', false);
                            $(val).find('[type=checkbox] + label:after').css({ 'background': '#fff' });
                        } else {
                            $(val).addClass('active-facet');
                        }
                    });
                }

                //adds active class to group close button
                facetClose.addClass('has-active-facet');
            },
            /**
            * Manages search result filter highlight of threshold
            * @param {Number} highlightThreshold value of threshold highlight
            * @memberof module:facetResultsFilter.FacetResultsFilterView
            * @alias module:facetResultsFilter.FacetResultsFilterView#handleThreshold
            */
            handleThreshold: function (highlightThreshold) {
                var facets = this.$el.find('.facet-search-filter').children('p');

                _.each(facets, function (single) {
                    var $facet = $(single),
                        $facetCount = $facet.find('.facet-count'),
                        facetCount = $facetCount.data('facetcount');

                    if (facetCount > highlightThreshold) {
                        $facetCount.addClass('highlighted');
                    }
                });
            },

            /*custom methods*/
            /**
            * Handles document click event to close details elements
            * @param {event} event the event
            * @memberof module:facetResultsFilter.FacetResultsFilterView
            * @alias module:facetResultsFilter.FacetResultsFilterView#documentClick
            */
            documentClick: function(event) {
                if(!this.$el[0].contains(event.target))
                {
                    var details = this.$el.find("details")[0];
                    if(details["open"])
                    {
                        this.closeDetails();
                    }
                }
                
                if(this.$el[0].contains(event.target))
                { 
                    if($(event.target).is("summary")){
                        var details = this.$el.find("details")[0];
                        if(details["open"])
                        {
                            this.closeDetails();
                            event.preventDefault();
                        }
                    }
                    else
                    {
                        if($(event.target).is("div"))
                        {
                            event.preventDefault();
                        }
                    }
                }
            },

            /**
            * Closes the details element
            * @memberof module:facetResultsFilter.FacetResultsFilterView
            * @alias module:facetResultsFilter.FacetResultsFilterView#closeDetails
            */
            closeDetails: function() {
                if(this.$el.hasClass("insights-dropdown"))
                {
                    var details = this.$el.find("details")[0];
                    if(details["open"])
                    {
                        details.removeAttribute("open");
                    }
                }
            },

            /**
            * Responds to the details element toggle
            * @param {event} event the event
            * @memberof module:facetResultsFilter.FacetResultsFilterView
            * @alias module:facetResultsFilter.FacetResultsFilterView#toggleDropdown
            */
            toggleDropdown: function (event) {
                var that = this;

                //if closing, reset checkboxes to hash
                if($(event.currentTarget)[0]["open"] == false)
                {
                    that.model.updateComponent(queryModel.parseHashParameters(window.location.hash));      
                    
                    if (that.model.get("facetArray").length === 0) {
                        this.disableClearAll();
                    }
                    else{
                        this.enableClearAll();
                    }
                }
            },

            disableClearAll: function () {
                var clearFilter = this.$el.find("span.clear-filter");
                $(clearFilter).removeClass("button-enabled").css("pointer-events", "none");
            },

            enableClearAll: function() {
                var clearFilter = this.$el.find("span.clear-filter");
                $(clearFilter).addClass("button-enabled").css("pointer-events", "auto");

            }
        });
    /**
    * For each search result component on a page creates instance of
    * ["FacetResultsFilterModel"]{@link module:facetResultsFilter.FacetResultsFilterModel} and
    * ["FacetResultsFilterView"]{@link module:facetResultsFilter.FacetResultsFilterView}
    * @memberOf module:facetResultsFilter
    * @alias module:facetResultsFilter.init
    */
    api.init = function () {
        if ($("body").hasClass("on-page-editor")) {
            return;
        }

        queryModel = XA.component.search.query;
        apiModel = XA.component.search.ajax;
        urlHelperModel = XA.component.search.url;

        var facetResultsFilterList = $(".facet-single-selection-list:not(.initialized)");

        _.each(facetResultsFilterList, function (elem) {
            var $el = $(elem),
                model = new FacetResultsFilterModel(),
                view = new FacetResultsFilterView({ el: $el, model: model });
        
            $el.addClass("initialized");
        });
    };
    /**
    * Returns information about facet component
    * @memberOf module:facetResultsFilter
    * @alias module:facetResultsFilter.getFacetDataRequestInfo
    * @returns {Array<FacetDataRequestInfo>} facet data needed for request
    */
    api.getFacetDataRequestInfo = function () {
        var facetList = $(".facet-single-selection-list"),
            result = [];

        _.each(facetList, function (elem) {
            var properties = $(elem).data().properties,
                signatures = properties.searchResultsSignature.split(','),
                i;

            for (i = 0; i < signatures.length; i++) {
                result.push({
                    signature: signatures[i] === null ? "" : signatures[i],
                    facetName: properties.f,
                    endpoint: properties.endpoint,
                    showMoreText: properties.showMoreText,
                    showLessText: properties.showLessText,
                    highlightBehaviour: properties.highlightBehaviour,
                    s: properties.s,
                    filterWithoutMe: !properties.collapseOnSelection
                });
            }
        });

        return result;
    };

    return api;

}(jQuery, document));

XA.register('facetResultsFilter', XA.component.search.facet.resultsfilter);

/*  component-search-sort  */
/**
 * Search sorting component functionality
 * @module searchSort
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with search sort component
*/
XA.component.search.sort = (function ($, document) {
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:searchSort
    */
    var api = {},
        queryModel,
        queryParameters,
        getSignature;
    /**
    * @name module:searchSort.SortModel
    * @constructor
    * @augments Backbone.Model
    */
    var SortModel = Backbone.Model.extend(
        /** @lends module:searchSort.SortModel.prototype **/
        {
            /**
            * Default model options
            * @default
            */
            defaults: {
                updateOrder: false,
                sig: []
            }
        });
    /**
    * @name module:searchSort.SortView
    * @constructor
    * @augments Backbone.View
    */
    var SortView = XA.component.search.baseView.extend(
        /** @lends module:searchSort.SortView.prototype **/
        {
            /**
            * Initially sets data to model and watches events on which
            * view should be updated
            * @listens module:XA.component.search.vent~event:hashChanged
            * @memberof module:searchSort.SortView
            * @alias module:searchSort.SortView#initialize
            */
            initialize: function () {
                var dataProperties = this.$el.data();

                if (this.model) {
                    this.model.set("sig", this.translateSignatures(dataProperties.properties.sig, "o"));
                }

                XA.component.search.vent.on("hashChanged", this.updateComponent.bind(this));
            },
            /**
             * list of events for Backbone View
            * @memberof module:searchSort.SortView
            * @alias module:searchSort.SortView#events
             */
            events: {
                'click .sort-results-group a': 'sortSearchResultsLink',
                'change select': 'sortSearchResultsSelect'
            },
            /**
             * prevents default event and calls
             * ['sortSearchResults']{@link module:searchSort.SortView.sortSearchResults}
             * method
             * @param {Event} event
             * @memberof module:searchSort.SortView
            * @alias module:searchSort.SortView#sortSearchResultsLink
             */
            sortSearchResultsLink: function (event) {
                event.preventDefault();
                this.sortSearchResults($(event.currentTarget).parent());
            },
            /**
             * call 
             * ['sortSearchResults']{@link module:searchSort.SortView.sortSearchResults}
             * method
             * @param {Event} event
             * @memberof module:searchSort.SortView
            * @alias module:searchSort.SortView#sortSearchResultsSelect
             */
            sortSearchResultsSelect: function (event) {
                this.sortSearchResults($(event.currentTarget[event.currentTarget.options.selectedIndex]));
            },
            /**
            * Updates hash with sorting value
            * @param {DomElement} element Element that contains data that is needed for sorting
            * @memberof module:searchSort.SortView
            * @alias module:searchSort.SortView#sortSearchResults
            */
            sortSearchResults: function (element) {
                var attributes = element.data(),
                    sig = this.model.get("sig"),
                    attrString = attributes.facet + ',' + attributes.direction;

                if (attributes.direction !== "") {
                    queryParameters.updateHash(this.updateSignaturesHash(sig, attrString, {}));
                } else {
                    queryParameters.updateHash(this.updateSignaturesHash(sig, "", {}));
                }
            },
            /**
            * Updates component selected value according to hash value
            * @param {Object} hash Hash value stores as an object
            * @memberof module:searchSort.SortView
            * @alias module:searchSort.SortView#updateComponent
            */
            updateComponent: function (hash) {
                var optionToSelect, sortData,
                    sig = this.model.get("sig"),
                    i;

                for (i = 0; i < sig.length; i++) {
                    if (hash.hasOwnProperty(sig[i])) {
                        sortData = hash[sig[i]].split(',');
                        optionToSelect = this.$el.find("[data-facet='" + sortData[0] + "'][data-direction='" + sortData[1] + "']");
                    } else {
                        optionToSelect = this.$el.find("[data-facet][data-direction]:first");
                    }
                }

                this.$el.find("[data-facet][data-direction]").removeAttr("selected");
                optionToSelect.attr("selected", "selected");
            }
        });
    /**
        * For each search sorting component on a page creates instance of 
        * ["SortModel"]{@link module:searchSort.SortModel} and 
        * ["SortView"]{@link module:searchSort.SortView} 
        * @memberOf module:searchSort
        * @alias module:searchSort.init
        */
    api.init = function () {
        if ($("body").hasClass("on-page-editor")) {
            return;
        }

        queryModel = XA.component.search.query;
        queryParameters = XA.component.search.parameters;

        var sort = $(".sort-results:not(.initialized)");
        
        _.each(sort, function (elem) {
            var $el = $(elem),
                sortModel = new SortModel(),
                view = new SortView({ el: $el, model: sortModel });
            
            $el.addClass("initialized");
        });
    };
    /**
    * Returns direction of sorting
    * @param {String} signature component signature
    * @memberOf module:searchSort
    * @alias module:searchSort.getFirstSortingOption
    * @retruns {String|Number} return string "facetName,direction" or -1
    */
    api.getFirstSortingOption = function (signature) {
        var sortResults = $(".sort-results"),
            firstSort,
            attributes,
            attrString,
            thisSignatures,
            data,
            i, j;

        for (i = 0; i < sortResults.length; i++) {
            if (typeof (signature) !== "undefined") {
                data = $(sortResults[i]).data();
                thisSignatures = data.properties.sig.split(',');

                for (j = 0; j < thisSignatures.length; j++) {
                    if (thisSignatures[j] === signature) {
                        firstSort = $(sortResults[i]).find("[data-facet][data-direction]");
                        attributes = firstSort.data();
                        if (attributes.direction !== "") {
                            attrString = attributes.facet + ',' + attributes.direction;
                            return attrString;
                        }
                    }
                }
            }
        }

        return -1;
    };

    return api;

}(jQuery, document));

XA.register('searchSort', XA.component.search.sort);


/*  component-search-url  */

/**
 * Creates instance of urlHelperModel
 * @module searchUrl
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with search url
*/
XA.component.search.url = (function ($, document) {
    /**
     * 
     * @param {String} data 
     * @returns {String}
     */
    var clearIdData = function (data) {
        data = data + "";
        var parts = data.split("||"), urlData = [], details;
        _.each(parts, function (part) {
            details = part.split('|');
            if (_.contains(part, '|') && details.length === 3) {
                urlData.push(details[1] + '|' + details[2]);
            } else {
                urlData.push(part);
            }
        });
        return urlData.join("||");
    };

    /**
    * @name module:searchUrl.UrlHelperModel
    * @constructor
    * @augments Backbone.Model
    */
    var UrlHelperModel = Backbone.Model.extend(
        /** @lends module:searchUrl.UrlHelperModel.prototype **/
        {
            /**
             * creates url for search components
             * @param {Object} dataProp component properties stored in data attribute
             * @param {String} signature component signature
             * @returns {String} created url
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#createSearchUrl
             */
            createSearchUrl: function (dataProp, signature) {
                var url = this.setEndpoint(dataProp.endpoint);

                url += (dataProp.l) ? "&l=" + dataProp.l : "";  //language
                url += (dataProp.s) ? "&s=" + dataProp.s : "";  //scope
                url += (dataProp.itemid) ? "&itemid=" + dataProp.itemid : "";
                url += this.getFacetParams(dataProp, signature);
                url = this.fixUrl(url);

                return url;
            },
            /**
             * creates url for search components for redirect
             * @param {String} redirectUrl target url
             * @param {Object} query query for redirect
             * @param {String} targetSignature target component signature
             * @param {String} signature component signature
             * @returns {String} created url
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#createSearchUrl
             */
            createRedirectSearchUrl: function (redirectUrl, query, signature, targetSignature) {
                var originalHash = XA.component.search.query.get("hashObj"),
                    updatedQuery = {},
                    hash = {},
                    index = 0,
                    clearParam,
                    paramSignature,
                    param,
                    url;

                if (signature !== "") {
                    //if signature is provided, then filter only params with the same signature
                    for (param in originalHash) {
                        clearParam = param.substring(param.indexOf('_') + 1);
                        paramSignature = param.substr(0, param.indexOf('_'));
                        if (paramSignature === signature) {
                            hash[param] = originalHash[param];
                        }
                    }
                } else {
                    hash = originalHash;
                }

                query = $.extend({}, hash, query);

                if (targetSignature !== "") {
                    //if target signature is provided, replace signature with target signature in all params
                    for (param in query) {
                        clearParam = param.substring(param.indexOf('_') + 1);
                        paramSignature = param.substr(0, param.indexOf('_'));
                        if (paramSignature === signature) {
                            updatedQuery[targetSignature + "_" + clearParam] = query[param];
                        } else if (paramSignature === targetSignature) {
                            updatedQuery[param] = query[param];
                        }
                    }
                } else {
                    updatedQuery = query;
                }

                url = this.setEndpoint(redirectUrl + "#");

                _.each(updatedQuery, function (item, key) {
                    url += (index === 0 ? "" : "&") + key + "=" + item;
                    index++;
                });

                return url;
            },
            /**
             * creates predictive url for search components
             * @param {String} endpoint target url
             * @param {Object} query query for redirect
             * @param {String} dataProp target component signature
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#createPredictiveSearchUrl
             * @returns {String} url predictive url
             */
            createPredictiveSearchUrl: function (endpoint, dataProp, query) {
                var url = this.setEndpoint(endpoint);

                url += "?q=" + encodeURIComponent(query);
                url += "&v=" + dataProp.v;
                url += "&p=" + dataProp.p;
                url += (dataProp.l) ? "&l=" + dataProp.l : "";
                url += (dataProp.s) ? "&s=" + dataProp.s : "";
                url += (dataProp.itemid) ? "&itemid=" + dataProp.itemid : "";

                return url;
            },
            /**
             * creates facet url for search components
             * @param {Object} query query for redirect
             * @param {String} dataProp target component signature
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#createFacetUrl
             * @returns {String} url for facets
             */
            createFacetUrl: function (dataProp, query) {
                var url = this.setEndpoint(dataProp.endpoint);

                url += "?f=" + dataProp.f.toLowerCase();
                url += (dataProp.s) ? "&s=" + dataProp.s : "";
                url += (dataProp.l) ? "&l=" + dataProp.l : "";
                url += (query) ? "&q=" + encodeURIComponent(query) : "";

                url += this.getFacetParams(dataProp);

                return url;
            },
            /**
             * creates facet url for search components when enabled multifacet search
             * @param {String} dataProp target component signature
             * @param {Array} facetList list of existing facets
             * @param {String} sig component signature
             * @param {String} itemId component id
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#createMultiFacetUrl
             * @returns {String} url for multi facet search
             */
            createMultiFacetUrl: function (dataProp, facetList, sig, itemId) {
                var url = this.setEndpoint(dataProp.endpoint);

                url += "?f=" + facetList.join("||").toLowerCase();
                url += (dataProp.s) ? "&s=" + dataProp.s : "";
                url += (dataProp.l) ? "&l=" + dataProp.l : "";
                url += itemId ? "&itemid=" + itemId : "";

                url += this.getFacetParams(dataProp, sig);

                url += "&sig=" + encodeURIComponent(sig);

                return url;
            },
            /**
             * Clears hash parameters
             * @param {String} dataProperties component properties
             * @param {Array} properties 
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#clearUrlParams
             * @returns {Array} array with cleared properties
             */
            clearUrlParams: function (dataProperties, properties) {
                var facetName = dataProperties.f.toLowerCase(),
                    hash = {};

                delete properties[facetName];
                delete hash[facetName];
                XA.component.search.query.updateHash(hash);

                return properties;
            },
            /**
             * Returns url with facet parameters
             * @param {Object} dataProp component properties
             * @param {String} searchResultsSignature signature of search result component
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#getFacetParams
             * @returns {String} url with facet parameters
             */
            getFacetParams: function (dataProp, searchResultsSignature) {
                var url = "",
                    clearFacetName,
                    facetSignature,
                    skipParams = ['endpoint', 'l', 's', 'e', 'f', 'sig', 'itemid'], //params which will be skipped while adding facet params
                    specialParams = ['g', 'o', 'q', 'p', 'e', 'v']; //params which are not facets but can have signature


                if (dataProp.hasOwnProperty("sig")) {
                    url += "&sig=" + encodeURIComponent(dataProp["sig"]);
                }

                for (facet in dataProp) {
                    if (skipParams.indexOf(facet) === -1 && facet && dataProp[facet]) {
                        clearFacetName = facet.substring(facet.indexOf('_') + 1);
                        facetSignature = facet.substr(0, facet.indexOf('_'));
                        if (searchResultsSignature === facetSignature && specialParams.indexOf(clearFacetName) === -1) {
                            url += "&" + clearFacetName + "=" + encodeURIComponent(clearIdData(dataProp[facet]));
                        }
                    }
                }

                //gets params that are not facets but can have signature
                url = this.getSpecialParams(dataProp, searchResultsSignature, specialParams, url);

                return url;
            },
            /**
             * Returns url with special parameters
             * @param {Object} dataProp component properties
             * @param {String} searchResultsSignature signature of search result component
             * @param {String} specialParams special parameters
             * @param {String} url url for special parameters
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#getSpecialParams
             * @returns {String} url with special parameters
             */
            getSpecialParams: function (dataProp, searchResultsSignature, specialParams, url) {
                var param,
                    paramValue,
                    clearParamName,
                    paramSignature;

                for (param in dataProp) {
                    clearParamName = param.substring(param.indexOf('_') + 1);
                    paramSignature = param.substr(0, param.indexOf('_'));
                    if (specialParams.indexOf(clearParamName) !== -1) {
                        paramValue = typeof dataProp[param] !== "undefined" ? dataProp[param] : "";
                        if (searchResultsSignature === paramSignature) {
                            url += "&" + clearParamName + "=" + encodeURIComponent(paramValue);
                        }
                    }
                }

                return url;
            },
            /**
             * Returns url POI content
             * @param {Object} dataProp component properties
             * @param {String} poiId POI ID
             * @param {String} poiVariantId POI variant ID
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#getSpecialParams
             * @returns {String} url with poi parameters
             */
            createGetPoiContentUrl: function (dataProp, poiId, poiVariantId) {
                var url = this.setEndpoint(dataProp.endpoint);
                url += "/" + poiVariantId + "/" + poiId;
                return url;
            },
            /**
             * Returns url with geo POI content
             * @param {Object} dataProp component properties
             * @param {String} poiId POI ID
             * @param {String} poiVariantId POI variant ID
             * @param {String} coordinates POI coordinates
             * @param {String} units unit of coordinates
             * @param {String} itemId context item ID
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#createGetGeoPoiContentUrl
             * @returns {String} url with geo poi content parameters
             */
            createGetGeoPoiContentUrl: function (dataProp, poiId, poiVariantId, coordinates, units, itemId) {
                var url = this.setEndpoint(dataProp.endpoint);
                url += "/" + poiVariantId + "/" + poiId + "/" + coordinates + "/" + units + "/" + itemId;
                return url;
            },
            /**
             * Returns url with geo POI content
             * @param {string} url url for site
             * @param {String} siteName site name
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#createSiteUrl
             * @returns {String} site url
             */
            createSiteUrl: function (url, siteName) {
                if (typeof siteName !== "undefined" && siteName !== null && siteName !== "") {
                    return url + "&sc_site=" + siteName;
                }
                return url;
            },
            /**
             * Returns url with endpoint
             * @param {string} endpoint endpoint parameter from component properties
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#setEndpoint
             * @returns {String} url with endpoint
             */
            setEndpoint: function (endpoint) {
                var url = window.location.origin;

                if (endpoint.startsWith("http")) {
                    return endpoint;
                }

                return url += endpoint;
            },
            /**
             * Fixes url with special characters
             * @param {string} url url string for fixing
             * @memberof searchUrl.UrlHelperModel
             * @alias searchUrl.UrlHelperModel#fixUrl
             * @returns {String} url with endpoint
             */
            fixUrl: function (url) {
                var index;

                url = url.replace(/[?]/g, "&");
                index = url.indexOf("&");
                url = url.substr(0, index) + "?" + url.substr(index + 1)

                return url;
            }
        });

    var urlHelperModel = new UrlHelperModel();

    return urlHelperModel;

}(jQuery, document));


/*  component-search-variant-filter  */
/**
 * Facet dropdown component functionality
 * @module variantFilter
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for variant filter component
*/
XA.component.search.variantFilter = (function ($, document) {
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:variantFilter
    */
    var api = {},
        queryModel,
        queryParameters,
        variantMappings = {};
    /**
    * @name module:variantFilter.VariantSelectorModel
    * @constructor
    * @augments Backbone.Model
    */
    var VariantSelectorModel = Backbone.Model.extend(
        /** @lends module:variantFilter.VariantSelectorModel.prototype **/
        {
            /**
            * Default model options
            * @default
            */
            defaults: {
                dataProperties: {},
                sig: []
            }
        });
    /**
    * @name module:variantFilter.VariantSelectorView
    * @constructor
    * @augments Backbone.View
    */
    var VariantSelectorView = XA.component.search.baseView.extend(
        /** @lends module:variantFilter.VariantSelectorView.prototype **/
        {
            /**
            * Initially sets data to model and watches events on which
            * view should be updated
            * @listens module:XA.component.search.vent~event:results-loaded
            * @memberof module:variantFilter.VariantSelectorView
            * @alias module:variantFilter.VariantSelectorView#initialize
            */
            initialize: function () {
                var hashObj = queryModel.parseHashParameters(window.location.hash),
                    dataProperties = this.$el.data(),
                    sig = this.translateSignatures(dataProperties.properties.searchResultsSignature, "v"),
                    inst = this,
                    i;

                if (dataProperties.properties.searchResultsSignature === null) {
                    dataProperties.properties.searchResultsSignature = "";
                }

                this.model.set({ dataProperties: dataProperties });
                this.model.set("sig", sig);

                for (i = 0; i < sig.length; i++) {
                    if (hashObj.hasOwnProperty(sig[i])) {
                        this.selectVariantIcon(hashObj[sig[i]]);
                    }
                }

                XA.component.search.vent.on("results-loaded", function (resultsData) {
                    var rawSignatures = dataProperties.properties.searchResultsSignature.split(','),
                        signature,
                        startingVariant,
                        signatureVariants;

                    hashObj = queryModel.parseHashParameters(window.location.hash);

                    for (i = 0; i < rawSignatures.length; i++) {
                        signature = rawSignatures[i];
                        if (hashObj.hasOwnProperty(sig[i]) && variantMappings.hasOwnProperty(signature)) {
                            signatureVariants = variantMappings[signature];
                            startingVariant = signatureVariants[hashObj[sig[i]]];
                            inst.triggerAddVariant(startingVariant.cssClass, signature);
                            inst.selectVariantIcon(hashObj[sig[i]]);
                        } else {
                            //selects first variant if there is no variant param in the hash
                            if (variantMappings.hasOwnProperty(signature) && variantMappings[signature].hasOwnProperty(0)) {
                                setTimeout(function () {
                                    inst.triggerAddVariant(variantMappings[signature][0].cssClass, signature);
                                    inst.selectVariantIcon(0);
                                }, 100);
                            }
                        }
                    }
                });
            },
            /**
            * list of events for Backbone View
            * @memberof module:variantFilter.VariantSelectorView
            * @alias module:variantFilter.VariantSelectorView#events
            */
            events: {
                "click div > div": "changeVariant"
            },
            /**
            * Changes variant of search result view
            * @param {Event} EVent object with selected variant as a current Target
            * @listens module:XA.component.search.vent~event:results-loaded
            * @memberof module:variantFilter.VariantSelectorView
            * @alias module:variantFilter.VariantSelectorView#changeVariant
            */
            changeVariant: function (event) {
                var $target = $(event.currentTarget),
                    sig = this.model.get("sig"),
                    props = $target.data();

                this.$el.find(".active-variant").removeClass("active-variant");
                $target.addClass("active-variant");

                queryParameters.updateHash(this.updateSignaturesHash(sig, props.variantindex, {}));
                this.triggerAddVariant($(event.currentTarget).attr("class"));
            },
            /**
            * Changes variant of search result view
            * @param {String} classes css class of chosen variant
            * @param {String} signature component signature
            * @fires module:XA.component.search.vent~event:add-variant-class
            * @memberof module:variantFilter.VariantSelectorView
            * @alias module:variantFilter.VariantSelectorView#triggerAddVariant
            */
            triggerAddVariant: function (classes, signature) {
                XA.component.search.vent.trigger("add-variant-class", {
                    classes: classes,
                    sig: signature
                });
            },
            /**
            * Manages active-variant class for variant icons
            * @param {String} v data variant index of selected element 
            * @fires module:XA.component.search.vent~event:add-variant-class
            * @memberof module:variantFilter.VariantSelectorView
            * @alias module:variantFilter.VariantSelectorView#selectVariantIcon
            */
            selectVariantIcon: function (v) {
                this.$el.find("div[data-variantIndex]").removeClass("active-variant");
                this.$el.find("div[data-variantIndex=" + v + "]").addClass("active-variant");
            }
        });

    /**
    * For each search variant filters component on a page creates instance of 
    * ["VariantSelectorModel"]{@link module:variantFilter.VariantSelectorModel} and 
    * ["VariantSelectorView"]{@link module:variantFilter.VariantSelectorView} 
    * @memberOf module:variantFilter
    * @alias module:variantFilter.init
    */
    api.init = function () {
        if ($("body").hasClass("on-page-editor")) {
            return;
        }

        queryModel = XA.component.search.query;
        queryParameters = XA.component.search.parameters;

        var variantSelectors = $(".variant-selector:not(.initialized)");

        _.each(variantSelectors, function (elem) {
            var $el = $(elem),
                view = new VariantSelectorView({ el: $el, model: new VariantSelectorModel() });
        
            $el.addClass("initialized");
        });
    };

    /**
    * 
    * @param signature
    * @param {String} signature component signature
    * @memberOf module:variantFilter
    * @alias module:variantFilter.getVariantMappings
    */
    api.getVariantMappings = function (signature) {
        var variantSelectors = $(".variant-selector"),
            componentVariants = {},
            allVariantItems,
            signatures = [],
            properties,
            i;

        if (!variantMappings.hasOwnProperty(signature)) {
            for (i = 0; i < variantSelectors.length; i++) {
                properties = $(variantSelectors[i]).data().properties;
                signatures = properties.searchResultsSignature.split(',');

                if (signatures.indexOf(signature) !== -1) {
                    allVariantItems = $(variantSelectors[i]).find(".variant-option");
                    _.each(allVariantItems, function (variant) {
                        var data = $(variant).data();
                        componentVariants[data.variantindex] = { id: data.variant, cssClass: $(variant).attr("class") };
                    });
                    variantMappings[signature] = componentVariants;
                }
            }
        }

        if (variantMappings.hasOwnProperty(signature)) {
            return variantMappings[signature];
        }

        return {};
    };

    return api;

}(jQuery, document));

XA.register('variantFilter', XA.component.search.variantFilter);



/*  component-search-service  */
/**
 * Model for search services
 * @module SearchServiceModel
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {SearchServiceModel} instance of search service model
*/
XA.component.search.service = (function ($, document) {

    'use strict';

    var searchResultsModels = XA.component.search.results.searchResultModels,
        urlHelperModel = XA.component.search.url,
        queryModel = XA.component.search.query,
        queryParameters = XA.component.search.parameters,
        apiModel = XA.component.search.ajax,
        SearchServiceModel;
    /**
     * @name module:SearchServiceModel.SearchServiceModel
     * @constructor
     * @augments Backbone.Model
     */
    SearchServiceModel = Backbone.Model.extend(
        /** @lends module:SearchServiceModel.SearchServiceModel.prototype **/
        {
            /**
            * Default model options
            * @default
            */
            defaults: {
            },
            /**
            * Watches for event 'orderChanged' and trigger
            * ['getData']{@link module:SearchServiceModel.SearchServiceModel#getData}
            * @listens module:XA.component.search.vent~event:orderChanged
            * @memberof module:SearchServiceModel.SearchServiceModel
            */
            initialize: function () {
                var that = this;
                XA.component.search.vent.on('orderChanged', function (data) {
                    that.getData(data);
                });
            },
            /**
            * If search result model created call
            * ['getSearchResultsData']{@link module:SearchServiceModel.SearchServiceModel#getSearchResultsData}
            * in other case call 
            * ['getEndpointAndSearch']{@link module:SearchServiceModel.SearchServiceModel#getEndpointAndSearch} 
            * @param {Object} overrideProps new properties fro overriding
            * @memberof module:SearchServiceModel.SearchServiceModel
            * @alias module:SearchServiceModel.SearchServiceModel#getData
            */
            getData: function (overrideProps) {
                if (searchResultsModels.length > 0) {
                    this.getSearchResultsData(overrideProps);
                } else {
                    this.getEndpointAndSearch(overrideProps);
                }
            },
            /**
            * Processes search result data
            * @param {Object} overrideProps properties for overriding
            * @memberof module:SearchServiceModel.SearchServiceModel
            * @alias module:SearchServiceModel.SearchServiceModel#getSearchResultsData
            */
            getSearchResultsData: function (overrideProps) {
                var hash = queryModel.parseHashParameters(window.location.hash),
                    that = this,
                    defaultSortOrder,
                    offsetSignature,
                    searchResultsDefaultPageSize,
                    searchResultsDefaultVariant,
                    signature,
                    mergedProps,
                    url;

                _.each(searchResultsModels, function (searchModel) {
                    signature = searchModel.get('dataProperties').sig !== null
                        ? encodeURIComponent(searchModel.get('dataProperties').sig)
                        : "";
                    offsetSignature = signature !== '' ? signature + "_e" : "e";

                    if (!searchModel.get('dataProperties').autoFireSearch) {
                        //If hash is not present or no criteria with search model signature is present in hash, then do not fire search
                        if (!hash || !queryModel.isSignaturePresentInHash(hash, signature)) {
                            return;
                        }
                    }

                    defaultSortOrder = searchModel.get('dataProperties').defaultSortOrder;
                    searchResultsDefaultPageSize = searchModel.get('dataProperties').p;
                    searchResultsDefaultVariant = searchModel.get('dataProperties').v;

                    //if we have singleRequestMode with signature, then we are getting data just for that signature
                    if (typeof overrideProps !== "undefined" &&
                        overrideProps.hasOwnProperty("singleRequestMode") &&
                        overrideProps["singleRequestMode"] != signature) {
                        return;
                    }

                    //if there is no page size param in the query, but we have page size component on the page, then take
                    //default page size or first (if default isn't set)
                    hash = that.getDefaultDefaultPageSize(signature, searchResultsDefaultPageSize, hash);

                    //in case of facet that filters items by language
                    //when someone is filtering by certain language, and removes that filter, then without
                    //this if statement, search results language parameter would not be considered - because l="" from hash object
                    //will override default language from dataProperties
                    if (hash.hasOwnProperty('l') && hash.l === '') {
                        hash.l = searchModel.get('dataProperties').l;
                    }

                    mergedProps = $.extend({}, searchModel.get('dataProperties'), hash);
                    mergedProps = $.extend(mergedProps, overrideProps);

                    if (mergedProps.hasOwnProperty('loadMore')) {
                        searchModel.set('loadMore', true);
                        delete mergedProps.loadMore;

                        var newOffset = searchModel.get('loadMoreOffset') + mergedProps.p;
                        searchModel.set('loadMoreOffset', newOffset);
                        mergedProps[offsetSignature] = newOffset;
                    }
                    else if (searchModel.get('loadMoreOffset')) { //if results reload is caused by variant change, loadMoreOffset should be considered in request, so after changing variant, the same number of results will be visible
                        if (mergedProps.p !== 0) {
                            mergedProps.p = mergedProps.p * (1 + searchModel.get('loadMoreOffset') / mergedProps.p);
                            mergedProps[offsetSignature] = 0;
                            searchModel.set('loadMoreOffset', 0);
                        }
                        delete mergedProps.variantChanged;
                    }

                    mergedProps = that.getSortOrder(signature, mergedProps, defaultSortOrder);
                    mergedProps = that.setVariant(signature, mergedProps, searchResultsDefaultVariant);

                    url = urlHelperModel.createSearchUrl(mergedProps, signature);
                    if (!url) {
                        return;
                    }

                    if (!searchModel.checkBlockingRequest()) {
                        searchModel.blockRequests(true);
                        XA.component.search.vent.trigger('results-loading', searchModel.cid);
                        apiModel.getData({
                            callback: function (data) {
                                data.Signature = data.Signature !== null ? data.Signature : '';
                                XA.component.search.vent.trigger('results-loaded', {
                                    dataCount: data.Count,
                                    data: data.Results,
                                    pageSize: data.Signature !== '' && mergedProps.hasOwnProperty(data.Signature + '_p') ? mergedProps[data.Signature + '_p'] : mergedProps.p,
                                    offset: data.Signature !== '' && mergedProps.hasOwnProperty(data.Signature + '_e') ? mergedProps[data.Signature + '_e'] : mergedProps.e,
                                    searchResultsSignature: data.Signature,
                                    loadMore: searchModel.get('loadMore')
                                });
                            },
                            url: url
                        });
                    }
                });
            },
            /**
            * Processes search result data. Call for apiModel.getData
            * @param {Object} overrideProps properties for overriding
            * @fires XA.component.search.vent#results-loaded
            * @listens module:XA.component.search.vent~event:orderChanged
            * @memberof module:SearchServiceModel.SearchServiceModel
            * @alias module:SearchServiceModel.SearchServiceModel#getSearchData
            */
            getSearchData: function (overrideProps) {
                var hash = queryModel.parseHashParameters(window.location.hash),
                    mergedProps = $.extend({}, hash, mergedProps, overrideProps),
                    signatures = [],
                    firstSortingOption,
                    url,
                    i;


                mergedProps = this.getDefaultDefaultPageSize("", 0, mergedProps);


                //if there is no sort param in the query, but we have sort results component on the page, then take first sorting option
                if (!mergedProps.hasOwnProperty('o') && typeof XA.component.search.sort !== 'undefined') {
                    firstSortingOption = XA.component.search.sort.getFirstSortingOption();
                    if (firstSortingOption !== -1) {
                        mergedProps.o = firstSortingOption;
                    }
                }

                //currently we are taking endpoint from Map component (atm it's only which can handle showing of search results)
                mergedProps.endpoint = XA.component.map.getSearchEndpoint();
                //the same with signature
                signatures = XA.component.map.getSignatures();

                for (i = 0; i < signatures.length; i++) {
                    mergedProps.sig = signatures[i];
                    url = urlHelperModel.createSearchUrl(mergedProps, signatures[i]);

                    if (!url) {
                        return;
                    }

                    apiModel.getData({
                        callback: function (data) {
                            data.Signature = data.Signature !== null ? data.Signature : '';
                            XA.component.search.vent.trigger('results-loaded', {
                                dataCount: data.Count,
                                data: data.Results,
                                pageSize: mergedProps.p,
                                offset: 0,
                                searchResultsSignature: data.Signature
                            });
                        },
                        url: url
                    });
                }
            },
            /**
           * Checks if map component exists and getSearchEndpoint returns not undefined value. If yes
           * call ["getSearchData"]{@link module:SearchServiceModel.SearchServiceModel#getSearchData}
           * @memberof module:SearchServiceModel.SearchServiceModel
           * @alias module:SearchServiceModel.SearchServiceModel#getEndpointAndSearch
           */
            getEndpointAndSearch: function () {
                if (XA.component.map && typeof (XA.component.map.getSearchEndpoint()) !== 'undefined') {
                    this.getSearchData();
                } else {
                    setTimeout(this.getEndpointAndSearch.bind(this), 100);
                }
            },
            /**
            * Sorts search result due to selected properties
            * @param {String} signature component signature
            * @param {Object} mergedProps 
            * @param {String} searchResultsDefaultSortOrder default sorting order
            * @memberof module:SearchServiceModel.SearchServiceModel
            * @alias module:SearchServiceModel.SearchServiceModel#getSortOrder
            * @returns {Object} merged properties
            */
            getSortOrder: function (signature, mergedProps, searchResultsDefaultSortOrder) {
                var firstSortingOption = XA.component.search.sort.getFirstSortingOption(signature),
                    paramName = signature !== '' ? signature + '_o' : 'o',
                    obj = {},
                    defaultSortOrder = "",
                    defaultSigned = {};


                if (firstSortingOption !== -1) {
                    defaultSortOrder = firstSortingOption;
                } else if (searchResultsDefaultSortOrder !== "") {
                    defaultSortOrder = searchResultsDefaultSortOrder;
                }

                if (!mergedProps.hasOwnProperty(paramName) && defaultSortOrder !== "") {
                    delete mergedProps.defaultSortOrder;
                    delete mergedProps.o;

                    mergedProps[paramName] = defaultSortOrder;
                    obj[paramName] = defaultSortOrder;
                }
                defaultSigned[paramName] = defaultSortOrder;
                queryParameters.registerDefault(defaultSigned);

                return mergedProps;
            },

            /**
            * Sets default page size into hash
            * @param {String} signature component signature
            * @param {Number} searchResultsDefaultPageSize  default page size
            * @param {Object} hash hash represented as an object
            * @memberof module:SearchServiceModel.SearchServiceModel
            * @alias module:SearchServiceModel.SearchServiceModel#getDefaultDefaultPageSize
            * @returns {Object} hash object
            */
            getDefaultDefaultPageSize: function (signature, searchResultsDefaultPageSize, hash) {
                var defaultPageSizes = XA.component.search.pageSize.getDefaultPageSizes(),
                    paramName = signature !== "" ? signature + "_p" : "p",
                    pageSizes;

                if (!hash.hasOwnProperty(paramName) && typeof XA.component.search.pageSize !== 'undefined') {
                    if (defaultPageSizes !== -1) {
                        pageSizes = defaultPageSizes.filter(function (x) {
                            return x.signatures.indexOf(signature) !== -1;
                        });
                        if (pageSizes.length > 0) {
                            hash[paramName] = pageSizes[0].defaultPageSize;
                        }
                    }
                }

                if (!hash.hasOwnProperty(paramName) && searchResultsDefaultPageSize != 0) {
                    hash[paramName] = searchResultsDefaultPageSize;
                }

                var defaultParam = {};
                defaultParam[paramName] = searchResultsDefaultPageSize;
                queryParameters.registerDefault(defaultParam);
                return hash;
            },
            /**
            * Sets variant of view search result based on variant selector component
            * @param {String} signature component signature
            * @param {Number} searchResultsDefaultPageSize  default page size
            * @param {Object} hash hash represented as an object
            * @memberof module:SearchServiceModel.SearchServiceModel
            * @alias module:SearchServiceModel.SearchServiceModel#setVariant
            * @returns {Object} hash object
            */
            setVariant: function (signature, mergedProps, searchResultsDefaultVariant) {
                var variantMappings = XA.component.search.variantFilter.getVariantMappings(signature),
                    paramName = signature !== "" ? signature + "_v" : "v",
                    variantIndex;

                if (signature === "" && $.isEmptyObject(variantMappings)) {
                    //there is no signature, and no variant selector on the page, therefore, do nothing - mergedPropes should have v param
                } else if (signature === "" && mergedProps.hasOwnProperty(paramName) && variantMappings.hasOwnProperty(mergedProps[paramName])) {
                    //there is no signature, but there is variant selector on the page, and it was clicked
                    mergedProps[paramName] = variantMappings[mergedProps[paramName]].id;
                } else if (signature === "" && variantMappings.hasOwnProperty(0)) {
                    //there is no signature, but there is variant selector on the page, therefore take its first variant
                    mergedProps[paramName] = variantMappings[0].id;
                } else {
                    delete mergedProps.v;
                    if (mergedProps.hasOwnProperty(paramName)) {
                        //variant switcher was clicked
                        mergedProps[paramName] = variantMappings[mergedProps[paramName]].id;
                    } else {
                        //if there is variant selector on the page, take first variant form the component, otherwise use search results default variant
                        if (variantMappings.hasOwnProperty(0)) {
                            mergedProps[paramName] = variantMappings[0].id;
                        } else {
                            mergedProps[paramName] = searchResultsDefaultVariant;
                        }
                    }
                }

                var defaultVariant = {};
                defaultVariant[paramName] = 0;
                queryParameters.registerDefault(defaultVariant);

                return mergedProps;
            }
        });

    return new SearchServiceModel();

}(jQuery, document));


/*  component-search-router  */
/**
 * Search router based on backbone router
 * @module searchRouter
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with backbone routing
*/
XA.component.search.router = (function ($, document) {

    "use strict";
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:searchRouter
    */
    var api = {},
        queryModel,
        initialized = false,
        lastHashObj,
        Router;
    /**
     * @name module:searchRouter.Router
     * @constructor
     * @augments Backbone.Model
     */
    Router = Backbone.Router.extend(
        /** @lends module:searchRouter.Router.prototype **/
        {
            routes: {
                "*params": "checkUrl"
            },
            /**
             * check url for changes in hash
             * @fires XA.component.search.vent#hashChanged
             * @param {Object} params parameter from url
             * @param {Boolean} force should trigger 'hashChanged' event if no changes in hash
             */
            checkUrl: function (params, force) {
                var hashObj = queryModel.parseHashParameters(window.location.hash);

                XA.component.search.service.getData();

                if (!hashObj) {
                    XA.component.search.facet.data.getInitialFacetData();
                    XA.component.search.vent.trigger("hashChanged", hashObj);
                } else {
                    if (force || (JSON.stringify(hashObj) !== JSON.stringify(lastHashObj))) {
                        XA.component.search.facet.data.filterFacetData(hashObj);
                        lastHashObj = hashObj;
                        XA.component.search.vent.trigger("hashChanged", hashObj);
                    }
                }                
            }
        });

    /**
    * Creates instance of Backbone Router and start
    * backbone history
    * @memberOf module:searchRouter
    * @alias module:searchRouter.init
    */
    api.init = function () {
        if ($("body").hasClass("on-page-editor") || initialized) {
            return;
        }

        queryModel = XA.component.search.query;

        this.api.routerInstance = new Router();

        if (!Backbone.History.started) {
            Backbone.history.start();
        }

        initialized = true;
    };

    return api;

}(jQuery, document));

XA.register('searchRouter', XA.component.search.router);


