# Source: js/collections/Filters.js

    define(["jquery", "underscore", "backbone", "models/filters/Filter", "models/filters/BooleanFilter",
        "models/filters/ChoiceFilter", "models/filters/DateFilter", "models/filters/NumericFilter",
        "models/filters/ToggleFilter"],
        function($, _, Backbone, Filter, BooleanFilter, ChoiceFilter, DateFilter, NumericFilter, ToggleFilter) {
            "use strict";

            /**
             * @class Filters
             * @classdesc A collection of Filter models that represents a full search
             * @name Filters
             * @extends Backbone.Collection
            * @constructor
             */
            var Filters = Backbone.Collection.extend(
              /** @lends Filters.prototype */{

                /**
                * Is executed when a new Filters collection is created
                */
                initialize: function(models, options) {
                    if (typeof options === "undefined") {
                        var options = {};
                    }

                    if (options.catalogSearch) {
                        this.createCatalogFilters();
                    }
                },

                /**
                *  Creates the type of Filter Model based on the given filter type. This
                * function is typically not called directly. It is used by Backbone.js when adding
                * a new model to the collection.
                * @param {object} attrs - A literal object that contains the attributes to pass to the model
                * @property {string} attrs.filterType - The type of Filter to create
                * @param {object} options - A literal object of additional options to pass to the model
                * @returns {Filter|BooleanFilter|ChoiceFilter|DateFilter|NumericFilter|ToggleFilter}
                */
                model: function(attrs, options){

                  //If no filterType was specified, but an objectDOM exists (from parsing a Collection
                  // or Portal document), get the filter type from the objectDOM node name
                  if( !attrs.filterType && attrs.objectDOM ){
                    switch( attrs.objectDOM.nodeName ){
                      case "booleanFilter":
                        return new BooleanFilter(attrs, options);

                      case "dateFilter":
                        return new DateFilter(attrs, options);

                      case "numericFilter":
                        return new NumericFilter(attrs, options);

                      default:
                        return new Filter(attrs, options);
                    }
                  }

                  switch ( attrs.filterType ) {

                    case "BooleanFilter":
                        return new BooleanFilter(attrs, options);

                    case "ChoiceFilter":
                        return new ChoiceFilter(attrs, options);

                    case "DateFilter":
                        return new DateFilter(attrs, options);

                    case "NumericFilter":
                        return new NumericFilter(attrs, options);

                    case "ToggleFilter":
                        return new ToggleFilter(attrs, options);

                    default:
                      return new Filter(attrs, options);
                  }

                },

                /**
                 * Builds the query string to send to the query engine. Iterates over each filter
                 * in the collection and adds to the query string.
                 *
                 * @return {string} The query string to send to Solr
                 */
                getQuery: function() {

                  //Create an array to store all the query pieces
                  var allGroupsQueryFragments = [],
                      //The complete query string that eventually gets returned
                      completeQuery = "",
                      //Get the list of filters that use the 'id' field, since these are used differently
                      idFilters = this.filter(function(filter){
                        return filter.get("fields").includes("id");
                      }),
                      otherFilters = this.difference(idFilters),
                      //Separate the filter models in this collection by their query group.
                      groupedFilters = _.groupBy(otherFilters, function(m){
                        return m.get("queryGroup");
                      });

                  //Filters that are used in the data catalog are treated specially
                  var catalogFilters = groupedFilters.catalog;
                  delete groupedFilters.catalog;

                  //Create a query string for each group of filters
                  _.mapObject(groupedFilters, function(filterModels, groupName) {

                    //Get a query string for this group of Filters
                    var groupQuery = this.getGroupQuery(filterModels);

                    //If there is a query string, add it to the array
                    if( groupQuery ){
                      allGroupsQueryFragments.push(groupQuery);
                    }

                  }, this);

                  //Join the query fragments with an OR. By default, Filter model groups are ORed together
                  if( allGroupsQueryFragments.length ){
                    completeQuery += "(" + allGroupsQueryFragments.join("%20OR%20") + ")";
                  }

                  //Add the Data Catalog filters, if there are any
                  if( Array.isArray(catalogFilters) && catalogFilters.length ){

                    //If there are other filters besides the catalog filters, AND them
                    if( completeQuery.trim().length ){
                      completeQuery += "%20AND%20";
                    }

                    //Get the query string for the catalog filters
                    completeQuery += this.getGroupQuery(catalogFilters);
                  }

                  //Create the grouped query for the id filters
                  var idFilterQuery = this.getGroupQuery(idFilters, "OR");

                  //Add the grouped query for the id filters
                  if( completeQuery.length && idFilterQuery.length ){
                    completeQuery = "(" + completeQuery + ")%20OR%20" + idFilterQuery;
                  }
                  //If the query is ONLY made of id filters, then the id filter query is the complete query
                  else if( !completeQuery.length && idFilterQuery.length ){
                    completeQuery = idFilterQuery;
                  }

                  //Return the completed query
                  return completeQuery;

                },

                /**
                * Get a query string for a group of Filters.
                * The Filters will be ANDed together, unless a different operator is given.
                * @param {Filter[]} filterModels - The Filters to turn into a query string
                * @param {string} [operator] - The oeprator to use between filter models
                * @return {string} The query string
                */
                getGroupQuery: function(filterModels, operator){

                  //Default to the AND operator
                  if(typeof operator != "string"){
                    var operator = "AND";
                  }

                  //Start an array to contian the query fragments
                  var groupQueryFragments = [];

                  //For each Filter in this group, get the query string
                  _.each(filterModels, function(filterModel){

                    //Get the Solr query string from this model
                    var filterQuery = filterModel.getQuery();

                    //Add the filter query string to the overall array
                    if ( filterQuery && filterQuery.length > 0 ) {
                      groupQueryFragments.push(filterQuery);
                    }
                  }, this);

                  //Join this group's query fragments with an OR operator
                  if( groupQueryFragments.length ){
                    return "(" + groupQueryFragments.join("%20" + operator + "%20") + ")"
                  }
                  //Otherwise, return an empty string
                  else{
                    return "";
                  }

                },

                /**
                 * Given a Solr field name, determines if that field is set as a filter option
                 */
                filterIsAvailable: function(field) {
                    var matchingFilter = this.find(function(filterModel) {
                        return _.contains(filterModel.fields, field);
                    });

                    if (matchingFilter) {
                        return true;
                    } else {
                        return false;
                    }
                },

                /*
                 * Returns an array of filter models in this collection that have a value set
                 *
                 * @return {Array} - an array of filter models in this collection that have a value set
                 */
                getCurrentFilters: function() {
                    var currentFilters = new Array();

                    this.each(function(filterModel) {
                        //If the filter model has values set differently than the default AND it is
                        // not an invisible filter, then add it to the current filters array
                        if (!filterModel.get("isInvisible") &&
                            ((Array.isArray(filterModel.get("values")) && filterModel.get("values").length &&
                                    _.difference(filterModel.get("values"), filterModel.defaults().values).length) ||
                                (!Array.isArray(filterModel.get("values")) && filterModel.get("values") !== filterModel.defaults().values))
                        ) {
                            currentFilters.push(filterModel);
                        }
                    });

                    return currentFilters;
                },

                /*
                 * Clear the values of all geohash-related models in the collection
                 */
                resetGeohash: function() {
                    //Find all the filters in this collection that are related to geohashes
                    this.each(function(filterModel) {
                        if (!filterModel.get("isInvisible") &&
                            _.intersection(filterModel.fields, ["geohashes", "geohashLevel", "geohashGroups"]).length) {
                            filterModel.resetValue();
                        }
                    });
                },

                /*
                 * Creates and adds FilterModels to this collection that are standard filters
                 * to be sent with every Data Catalog query.
                 */
                createCatalogFilters: function() {

                    //Exclude obsoleted objects from the search
                    this.add(new Filter({
                        fields: ["obsoletedBy"],
                        values: ["*"],
                        exclude: true,
                        isInvisible: true,
                        queryGroup: "catalog"
                    }));

                    //Only search for metadata objects
                    this.add(new Filter({
                        fields: ["formatType"],
                        values: ["METADATA"],
                        matchSubstring: false,
                        isInvisible: true,
                        queryGroup: "catalog"
                    }));
                },

                /**
                * Removes Filter models from this collection if they match the given field
                * @param {string} field - The field whose matching filters that should be removed from this collection
                */
                removeFiltersByField: function(field){

                  var toRemove = [];

                  this.each(function(filter){
                    if(filter.get("fields").includes(field)){
                      toRemove.push(filter);
                    }
                  });

                  this.remove(toRemove);

                }
                /*
                hasGeohashFilter: function() {

                    var currentFilters = this.getCurrentFilters();
                    var geohashFilter = _.find(currentFilters, function(filterModel){
                        return (_.intersection(filterModel.get("fields"),
                            ["geohashes", "geohash"]).length > 0);
                    });

                    if(geohashFilter) {
                        return true;
                    } else {
                        return false;
                    }
                }
                */
            });
            return Filters;
        });