# Source: src/js/models/filters/DateFilter.js

    /* global define */
    define(['jquery', 'underscore', 'backbone', 'models/filters/Filter'],
        function($, _, Backbone, Filter) {

      /**
      * @class DateFilter
      * @classdesc A search filter whose search term is an exact date or date range
      * @classcategory Models/Filters
      * @constructs DateFilter
      * @extends Filter
      */
    	var DateFilter = Filter.extend(
        /** @lends DateFilter.prototype */{

        type: "DateFilter",

        /**
        * The Backbone Model attributes set on this DateFilter
        * @type {object}
        * @extends Filter#defaultts
        * @property {Date} min - The minimum Date to use in the query for this filter
        * @property {Date} max - The maximum Date to use in the query for this filter
        * @property {Date} rangeMin - The earliest possible Date that 'min' can be
        * @property {Date} rangeMax - The latest possible Date that 'max' can be
        * @property {Boolean} matchSubstring - Will always be stet to false, since Dates don't have substrings
        * @property {string} nodeName - The XML node name to use when serializing this model into XML
        */
        defaults: function(){
          return _.extend(Filter.prototype.defaults(), {
            min: 0,
            max: (new Date()).getUTCFullYear(),
            rangeMin: 1800,
            rangeMax: (new Date()).getUTCFullYear(),
            matchSubstring: false,
            nodeName: "dateFilter"
          });
        },

        /**
        * Parses the dateFilter XML node into JSON
        *
        * @param {Element} xml - The XML Element that contains all the DateFilter elements
        * @return {JSON} - The JSON object literal to be set on the model
        */
        parse: function(xml){

          try{
            var modelJSON = Filter.prototype.parse.call(this, xml);

            //Get the rangeMin and rangeMax nodes
            var rangeMinNode = $(xml).find("rangeMin"),
                rangeMaxNode = $(xml).find("rangeMax");

            //Parse the range min
            if( rangeMinNode.length ){
              modelJSON.rangeMin = new Date(rangeMinNode[0].textContent).getUTCFullYear();
            }
            //Parse the range max
            if( rangeMaxNode.length ){
              modelJSON.rangeMax = new Date(rangeMaxNode[0].textContent).getUTCFullYear();
            }

            //If this Filter is in a filter group, don't parse the values
            if( !this.get("isUIFilterType") ){
              //Get the min, max, and value nodes
              var minNode = $(xml).find("min"),
                  maxNode = $(xml).find("max"),
                  valueNode = $(xml).find("value");

              //Parse the min value
              if( minNode.length ){
                modelJSON.min = new Date(minNode[0].textContent).getUTCFullYear();
              }
              //Parse the max value
              if( maxNode.length ){
                modelJSON.max = new Date(maxNode[0].textContent).getUTCFullYear();
              }
              //Parse the value
              if( valueNode.length ){
                modelJSON.values = [new Date(valueNode[0].textContent).getUTCFullYear()];
              }
            }

            //If a range min and max was given, or if a min and max value was given,
            // then this DateFilter should be presented as a date range (rather than
           // an exact date value).
            if( rangeMinNode.length || rangeMinNode.length || minNode || maxNode ){
              //Set the range attribute on the JSON
              modelJSON.range = true;
            }
            else{
              //Set the range attribute on the JSON
              modelJSON.range = false;
            }
          }
          catch(e){
            //If an error occured while parsing the XML, return a blank JS object
            //(i.e. this model will just have the default values).
            return {};
          }

          return modelJSON;

        },

        /**
         * Builds a query string that represents this filter.
         *
         * @return {string} The query string to send to Solr
         */
        getQuery: function(){

          //Start the query string
          var queryString = "";

          //Only construct the query if the min or max is different than the default
          if( ((this.get("min") != this.defaults().min) && (this.get("min") != this.get("rangeMin"))) ||
               ((this.get("max") != this.defaults().max)) && (this.get("max") != this.get("rangeMax")) ){

            //Iterate over each filter field and add to the query string
            _.each(this.get("fields"), function(field, i, allFields){

              //Add the date range for this field to the query string
              queryString += field + ":" + this.getRangeQuery().replace(/ /g, "%20");

              //If there is another field, add an operator
              if( allFields[i+1] ){
                queryString += "%20" + this.get("fieldsOperator") + "%20";
              }

            }, this);

            //If there is more than one field, wrap the query in parenthesis
            if( this.get("fields").length > 1 ){
              queryString = "(" + queryString + ")";
            }

          }

          return queryString;

        },

        /**
        * Constructs a subquery string from the minimum and maximum dates.
        * @return {string} - THe subquery string
        */
        getRangeQuery: function(){
          //Get the minimum and maximum values
          var max = this.get("max"),
              min = this.get("min");

          //If no min or max was set, but there is a value, construct an exact value match query
          if( !min && min !== 0 && !max && max !== 0 &&
                   (this.get("values")[0] || this.get("values")[0] === 0) ){
            return this.get("values")[0];
          }
          //If there is no min or max or value, set an empty query string
          else if( !min && min !== 0 && !max && max !== 0 &&
                   ( !this.get("values")[0] && this.get("values")[0] !== 0) ){
             return "";
          }
          //If there is at least a min or max
          else{
            //If there's a min but no max, set the max to a wildcard (unbounded)
            if( (min || min === 0) && !max ){
              max = "*";
            }
            //If there's a max but no min, set the min to a wildcard (unbounded)
            else if ( !min && min !== 0 && max ){
              min = "*";
            }
            //If the max is higher than the min, set the max to a wildcard (unbounded)
            else if( (max || max === 0) && (min || min === 0) && (max < min) ){
              max = "*";
            }

            if(min != "*"){
              min = min + "-01-01T00:00:00Z";
            }
            if(max != "*"){
              max = max + "-12-31T23:59:59Z";
            }

            //Add the range for this field to the query string
            return "[" + min + "%20TO%20" + max + "]";
          }

        },

        /**
         * Updates the XML DOM with the new values from the model
         *
         *  @inheritdoc
        */
        updateDOM: function(options){

          var objectDOM = Filter.prototype.updateDOM.call(this, options);

          //Date Filters don't use matchSubstring nodes, and the value node will be recreated later
          $(objectDOM).children("matchSubstring, value").remove();

          //Get a clone of the original DOM
          var originalDOM;
          if( this.get("objectDOM") ){
            originalDOM = this.get("objectDOM").cloneNode(true);
          }

          if( typeof options == "undefined" ){
            var options = {};
          }

          // Get min and max dates
          var dateData = {
            min: this.get("min"),
            max: this.get("max"),
            value: this.get("values") ? this.get("values")[0] : null
          };

          var isRange = false;

          // Make subnodes <min> and <max> and append to DOM
          _.map(dateData, function(value, nodeName){

            // dateFilters don't have a min or max when the values should range from
            // a min to infinity, or from a max to infinity (e.g. "date is before...")
            if(!value){
              return
            }

            if( nodeName == "min" ){
              var dateTime = "-01-01T00:00:00Z";
            }
            else{
              var dateTime = "-12-31T23:59:59Z";
            }

            //If this value is the same as the default value, but it wasn't previously serialized,
            if( (value == this.defaults()[nodeName]) &&
                ( !$(originalDOM).children(nodeName).length ||
                  ($(originalDOM).children(nodeName).text() != value + dateTime) )){
                return;
            }

            //Create an XML node
            var nodeSerialized = objectDOM.ownerDocument.createElement(nodeName);

            //Add the date string to the XML node
            $(nodeSerialized).text( value + dateTime );

            $(objectDOM).append(nodeSerialized);

            //If either a min or max was serialized, then mark this as a range
            isRange = true;

          }, this);

          //If a value is set on this model,
          if( !isRange && this.get("values").length ){

            //Create a value XML node
            var valueNode = $(objectDOM.ownerDocument.createElement("value"));
            //Get a Date object for this value
            var date = new Date();
            date.setUTCFullYear(this.get("values")[0] + "-12-31T23:59:59Z");
            //Set the text of the XML node to the date string
            valueNode.text( date.toISOString() );
            $(objectDOM).append( valueNode );

          }

          if( this.get("isUIFilterType") ){

            // Get new date data
            var dateData = {
              rangeMin: this.get("rangeMin"),
              rangeMax: this.get("rangeMax")
            };

            // Make subnodes <min> and <max> and append to DOM
            _.map(dateData, function(value, nodeName){

              if( nodeName == "rangeMin" ){
                var dateTime = "-01-01T00:00:00Z";
              }
              else{
                var dateTime = "-12-31T23:59:59Z";
              }

              //If this value is the same as the default value, but it wasn't previously serialized,
              if( (value == this.defaults()[nodeName]) &&
                  ( !$(originalDOM).children(nodeName).length ||
                    ($(originalDOM).children(nodeName).text() != value + dateTime) )){
                  return;
              }

              //Create an XML node
              var nodeSerialized = objectDOM.ownerDocument.createElement(nodeName);

              //Add the date string to the XML node
              $(nodeSerialized).text( value + dateTime );

              //Remove existing nodes and add the new one
              $(objectDOM).children(nodeName).remove();
              $(objectDOM).append(nodeSerialized);

            }, this);

            //Move the filterOptions node to the end of the filter node
            var filterOptionsNode = $(objectDOM).find("filterOptions");
            filterOptionsNode.detach();
            $(objectDOM).append(filterOptionsNode);
          }
          //Remove filterOptions for Date filters in collection definitions
          else{
            $(objectDOM).find("filterOptions").remove();
          }

          return objectDOM;
        },

        /**
        * Creates a human-readable string that represents the value set on this model
        * @return {string}
        */
        getReadableValue: function(){

          var readableValue = "";

          var min = this.get("min"),
              max = this.get("max"),
              value = this.get("values")[0];

          if( !value && value !== 0 ){
            //If there is a min and max
            if( (min || min === 0) && (max || max === 0) ){
              readableValue = min + " to " + max;
            }
            //If there is only a max
            else if(max || max === 0){
              readableValue = "Before " + max;
            }
            else{
              readableValue = "After " + min;
            }
          }
          else{
            readableValue = value;
          }

          return readableValue;

        },

        /**
        * @inheritdoc
        */
        hasChangedValues: function(){

          return ((this.get("min") > this.get("rangeMin") && this.get("min") !== this.defaults().min) ||
                  (this.get("max") < this.get("rangeMax") && this.get("max") !== this.defaults().max))

        },

        /**
        * Checks if the values set on this model are valid and expected
        * @return {object} - Returns a literal object with the invalid attributes and their corresponding error message
        */
        validate: function(){

          //Validate most of the DateFilter attributes using the parent validate function
          var errors = Filter.prototype.validate.call(this);

          //If everything is valid so far, then we have to create a new object to store errors
          if (typeof errors != "object") {
            errors = {};
          }

          //Delete error messages for the attributes that are going to be validated specially for the DateFilter
          delete errors.values;
          delete errors.min;
          delete errors.max;

          // Check that there is a rangeMin and a rangeMax. If there isn't, then just set to
          // the default rather than creating an error.
          if (!this.get("rangeMin") && this.get("rangeMin") !== 0) {
            this.set("rangeMin", this.defaults().rangeMin)
          }
          if (!this.get("rangeMax") && this.get("rangeMax") !== 0) {
            this.set("rangeMax", this.defaults().rangeMax)
          }

          //Check that there aren't any negative numbers
          if( this.get("min") < 0 ){
            errors.min = "The minimum year cannot be a negative number."
          }
          if( this.get("max") < 0 ){
            errors.max = "The maximum year cannot be a negative number."
          }
          if( this.get("rangeMin") < 0 ){
            errors.rangeMin = "The range minimum year cannot be a negative number."
          }
          if( this.get("rangeMax") < 0 ){
            errors.rangeMax = "The range maximum year cannot be a negative number."
          }

          //Check that the min and max values are in order, if the minimum is not the default value of 0
          if( this.get("min") > this.get("max") && this.get("min") != 0 ){
            errors.min = "The minimum year is after the maximum year. The minimum year must be a year before the maximum year of " + this.get("max");
          }

          //Check that all the values are numbers
          if( !errors.min && typeof this.get("min") != "number" ){
            errors.min = "The minimum year must be a number.";
          }
          if( !errors.max && typeof this.get("max") != "number" ){
            errors.max = "The maximum year must be a number.";
          }
          if( !errors.rangeMax && typeof this.get("rangeMax") != "number" ){
            errors.rangeMax = "The maximum year in the date slider must be a number.";
          }

          if( !errors.rangeMin && typeof this.get("rangeMin") != "number" ){
            errors.rangeMin = "The minimum year in the date slider must be a number.";
          }

          if (Object.keys(errors).length)
            return errors;
          else {
            return;
          }

        }

      });

      return DateFilter;
    });