# Source: js/models/Stats.js

    /*global define */
    define(['jquery', 'underscore', 'backbone', 'models/LogsSearch', 'promise'],
      function($, _, Backbone, LogsSearch, Promise) {
      'use strict';

      /**
      * @class Stats
      * @classdesc This model contains all a collection of statistics/metrics about a collection of DataONE objects
      * @name Stats
      * @extends Backbone.Model
      * @constructor
      */
      var Stats = Backbone.Model.extend(
        /** @lends Stats.prototype */{

        /**
        * Default attributes for Stats models
        * @type {Object}
        * @property {string} query - The base query that defines the data collection to get statistis about.
        * @property {string} postQuery - A copy of the `query`, but without any URL encoding
        * @property {boolean} isSystemMetadataQuery - If true, the `query` set on this model is only filtering on system metadata fields which are common between both metadata and data objects.
        * @property {number} metadataCount - The number of metadata objects in this data collection @readonly
        * @property {number} dataCount - The number of data objects in this data collection
        * @property {number} totalCount - The number of metadata and data objects in this data collection. Essentially this is the sum of metadataCount and dataCount
        * @property {number|string[]} metadataFormatIDs - An array of metadata formatIds and the number of metadata objects with that formatId. Uses same structure as Solr facet counts: ["text/csv", 5]
        * @property {number|string[]} dataFormatIDs - An array of data formatIds and the number of data objects with that formatId. Uses same structure as Solr facet counts: ["text/csv", 5]
        * @property {string} firstUpdate - The earliest upload date for any object in this collection, excluding uploads of obsoleted objects
        * @property {number|string[]} dataUpdateDates - An array of date strings and the number of data objects uploaded on that date. Uses same structure as Solr facet counts: ["2015-08-02", 5]
        * @property {number|string[]} metadataUpdateDates An array of date strings and the number of data objects uploaded on that date. Uses same structure as Solr facet counts: ["2015-08-02", 5]
        * @property {string} firstBeginDate - An ISO date string of the earliest year that this data collection describes, from the science metadata
        * @property {string} lastEndDate - An ISO date string of the latest year that this data collection describes, from the science metadata
        * @property {string} firstPossibleDate - The first possible date (as a string) that data could have been collected. This is to weed out badly formatted dates when sending queries.
        * @property {object} temporalCoverage A simple object of date ranges (the object key) and the number of metadata objects uploaded in that date range (the object value). Example: { "1990-2000": 5 }
        * @property {number} queryCoverageFrom - The year to start the temporal coverage range query
        * @property {number} queryCoverageUntil - The year to end the temporal coverage range query
        * @property {number} metadataTotalSize - The total number of bytes of all metadata files
        * @property {number} dataTotalSize - The total number of bytes of all data files
        * @property {number} totalSize - The total number of bytes or metadata and data files
        * @property {boolean} hideMetadataAssessment - If true, metadata assessment scores will not be retrieved
        * @property {Image} mdqScoresImage - The Image objet of an aggregated metadata assessment chart
        * @property {number} maxQueryLength - The maximum query length that will be sent via GET to the query service. Queries that go beyond this length will be sent via POST, if POST is enabled in the AppModel
        */
        defaults: function(){
          return{
          query: "*:* ",
          postQuery: "",
          isSystemMetadataQuery: false,

          metadataCount: 0,
          dataCount: 0,
          totalCount: 0,

          metadataFormatIDs: [],
          dataFormatIDs: [],

          firstUpload: 0,
          totalUploads: 0,
          metadataUploads: null,
          dataUploads: null,
          metadataUploadDates: null,
          dataUploadDates: null,

          firstUpdate: null,
          dataUpdateDates: null,
          metadataUpdateDates: null,

          firstBeginDate: null,
          lastEndDate : null,
          firstPossibleDate: "1800-01-01T00:00:00Z",
          temporalCoverage: 0,
          queryCoverageFrom: null,
          queryCoverageUntil: null,

          metadataTotalSize: null,
          dataTotalSize: null,
          totalSize: null,

          hideMetadataAssessment: false,
          mdqScoresImage: null,

          //HTTP GET requests are typically limited to 2,083 characters. So query lengths
          // should have this maximum before switching over to HTTP POST
          maxQueryLength: 2000
        }},

        /**
        * This function serves as a shorthand way to get all of the statistics stored in the model
        */
        getAll: function(){

          // Only get the MetaDIG scores if MetacatUI is configured to display metadata assesments
          // AND this model has them enabled, too.
          if ( !MetacatUI.appModel.get("hideSummaryMetadataAssessment") && !this.get("hideMetadataAssessment") ){
            this.getMdqScores();
          }

          //Send the call the get both the metadata and data stats
          this.getMetadataStats();
          this.getDataStats();

        },

        /**
        * Queries for statistics about metadata objects
        */
        getMetadataStats: function(){

          var query = this.get("query"),
              //Filter out the portal and collection documents
              filterQuery = "-formatId:*dataone.org/collections* AND -formatId:*dataone.org/portals* AND formatType:METADATA AND -obsoletedBy:*",
              //Use the stats feature to get the sum of the file size
              stats = "true",
              statsField = "size",
              //Get the facet counts for formatIds
              facet = "true",
              facetFormatIdField = "formatId",
              facetFormatIdMin = "1",
              facetFormatIdMissing = "false",
              facetLimit = "-1",
              //Get the upload counts for each month
              facetRange = "dateUploaded",
              facetRangeGap = "+1MONTH",
              facetRangeStart = "1900-01-01T00:00:00.000Z",
              facetRangeEnd = (new Date()).toISOString(),
              facetMissing = "true",
              //Query for the temporal coverage ranges
              facetQueries = [],
              facetBeginDateField = "beginDate",
              facetEndDateField = "endDate",
              facetDateMin = "1",
              facetDateMissing = "false",
              //Don't return any result docs
              rows = "0",
              //Use JSON for the response format
              wt = "json";

          //How many years back should we look for temporal coverage?
          var lastYear =  this.get('queryCoverageUntil') || new Date().getUTCFullYear(),
              firstYear = this.get('queryCoverageFrom')  || 1950,
              totalYears = lastYear - firstYear,
              today = new Date().getUTCFullYear(),
              yearsFromToday = {
               fromBeginning: today - firstYear,
               fromEnd: today - lastYear
              };

          //Determine our year range/bin size
          var binSize = 1;

          if((totalYears > 10) && (totalYears <= 20)){
            binSize = 2;
          }
          else if((totalYears > 20) && (totalYears <= 50)){
            binSize = 5;
          }
          else if((totalYears > 50) && (totalYears <= 100)){
            binSize = 10;
          }
          else if(totalYears > 100){
            binSize = 25;
          }

          //Count all the datasets with coverage before the first year in the year range queries
          var beginDateLimit = new Date(Date.UTC(firstYear-1, 11, 31, 23, 59, 59, 999));
          facetQueries.push("{!key=<" + firstYear + "}(beginDate:[* TO " +
                            beginDateLimit.toISOString() +"/YEAR])");

          //Construct our facet.queries for the beginDate and endDates, starting with all years before this current year
          var key = "";

          for(var yearsAgo = yearsFromToday.fromBeginning; (yearsAgo >= yearsFromToday.fromEnd && yearsAgo > 0); yearsAgo -= binSize){
            // The query logic here is: If the beginnning year is anytime before or
            //  during the last year of the bin AND the ending year is anytime after
            //  or during the first year of the bin, it counts.
            if(binSize == 1){
              //Querying for just the current year needs to be treated a bit differently
              // and won't be caught in our for loop
              if(lastYear == today){
                var oneYearFromNow = new Date(Date.UTC(today+1, 0, 1));
                var now = new Date();

                facetQueries.push("{!key=" + lastYear + "}(beginDate:[* TO " +
                                  oneYearFromNow.toISOString() + "/YEAR] AND endDate:[" +
                                  now.toISOString() + "/YEAR TO *])");
              }
              else{
                key = today - yearsAgo;

                //The coverage should start sometime in this year range or earlier.
                var beginDateLimit = new Date(Date.UTC(today-(yearsAgo-1), 0, 1));
                //The coverage should end sometime in this year range or later.
                var endDateLimit = new Date(Date.UTC(today-yearsAgo, 0, 1));

                facetQueries.push("{!key=" + key + "}(beginDate:[* TO " +
                                  beginDateLimit.toISOString() + "/YEAR] AND endDate:[" +
                                  endDateLimit.toISOString() + "/YEAR TO *])");
              }
            }
            //If this is the last date range
            else if (yearsAgo <= binSize){
              //Get the last year that will be included in this bin
              var firstYearInBin = (today - yearsAgo),
                  lastYearInBin  = lastYear;

              //Label the facet query with a key for easier parsing
              // Because this is the last year range, which could be uneven with the other year ranges, use the exact end year
              key = firstYearInBin + "-" + lastYearInBin;

              //The coverage should start sometime in this year range or earlier.
              // Because this is the last year range, which could be uneven with the other year ranges, use the exact end year
              var beginDateLimit = new Date(Date.UTC(lastYearInBin, 11, 31, 23, 59, 59, 999));
              //The coverage should end sometime in this year range or later.
              var endDateLimit = new Date(Date.UTC(firstYearInBin, 0, 1));

              facetQueries.push("{!key=" + key + "}(beginDate:[* TO " +
                                beginDateLimit.toISOString() +"/YEAR] AND endDate:[" +
                                endDateLimit.toISOString() + "/YEAR TO *])");
            }
            //For all other bins,
            else{
              //Get the last year that will be included in this bin
              var firstYearInBin = (today - yearsAgo),
                  lastYearInBin  = (today - yearsAgo + binSize-1);

              //Label the facet query with a key for easier parsing
              key = firstYearInBin + "-" + lastYearInBin;

              //The coverage should start sometime in this year range or earlier.
            //  var beginDateLimit = new Date(Date.UTC(today - (yearsAgo - binSize), 0, 1));
              var beginDateLimit = new Date(Date.UTC(lastYearInBin, 11, 31, 23, 59, 59, 999));
              //The coverage should end sometime in this year range or later.
              var endDateLimit = new Date(Date.UTC(firstYearInBin, 0, 1));

              facetQueries.push("{!key=" + key + "}(beginDate:[* TO " +
                             beginDateLimit.toISOString() + "/YEAR] AND endDate:[" +
                             endDateLimit.toISOString() + "/YEAR TO *])");
            }
          }

          var model = this;
          var successCallback = function(data, textStatus, xhr) {

            if( !data || !data.response || !data.response.numFound ){
              //Store falsey data
              model.set("totalCount", 0);
              model.trigger("change:totalCount");
              model.set('metadataCount', 0);
              model.trigger("change:metadataCount");
              model.set('metadataFormatIDs', ["", 0]);
              model.set('firstUpdate', null);
              model.set("metadataUpdateDates", []);
              model.set("temporalCoverage", 0);
              model.trigger("change:temporalCoverage");
            }
            else{
              //Save tthe number of metadata docs found
              model.set('metadataCount', data.response.numFound);
              model.set("totalCount", model.get("dataCount") + data.response.numFound);

              //Save the format ID facet counts
              if( data.facet_counts && data.facet_counts.facet_fields && data.facet_counts.facet_fields.formatId ){
                model.set("metadataFormatIDs", data.facet_counts.facet_fields.formatId);
              }
              else{
                model.set("metadataFormatIDs", ["", 0]);
              }

              //Save the metadata update date counts
              if( data.facet_counts && data.facet_counts.facet_ranges && data.facet_counts.facet_ranges.dateUploaded ){

                //Find the index of the first update date
                var updateFacets = data.facet_counts.facet_ranges.dateUploaded.counts,
                    cropAt = 0;

                for( var i=1; i<updateFacets.length; i+=2 ){
                  //If there was at least one update/upload in this date range, then save this as the first update
                  if( typeof updateFacets[i] == "number" && updateFacets[i] > 0){
                    //Save the first first update date
                    cropAt = i;
                    model.set('firstUpdate', updateFacets[i-1]);
                    //Save the update dates, but crop out months that are empty
                    model.set("metadataUpdateDates", updateFacets.slice(cropAt+1));
                    i = updateFacets.length;
                  }
                }

                //If no update dates were found, save falsey values
                if( cropAt === 0 ){
                  model.set('firstUpdate', null);
                  model.set("metadataUpdateDates", []);
                }
              }

              //Save the temporal coverage dates
              if( data.facet_counts && data.facet_counts.facet_queries ){

                //Find the beginDate and facets so we can store the earliest beginDate
                if( data.facet_counts.facet_fields && data.facet_counts.facet_fields.beginDate ){
                  var earliestBeginDate = _.find(data.facet_counts.facet_fields.beginDate, function(value){
                    return ( typeof value == "string" && parseInt(value.substring(0,4)) > 1000 );
                  });
                  if( earliestBeginDate ){
                    model.set("firstBeginDate", earliestBeginDate);
                  }
                }

                //Find the endDate and facets so we can store the latest endDate
                if( data.facet_counts.facet_fields && data.facet_counts.facet_fields.endDate ){
                  var latestEndDate,
                      endDates = data.facet_counts.facet_fields.endDate,
                      nextYear = (new Date()).getUTCFullYear() + 1,
                      i = 0;

                  //Iterate over each endDate and find the first valid one. (After year 1000 but not after today)
                  while( !latestEndDate && i<endDates.length ){
                    var endDate = endDates[i];
                    if( typeof endDate == "string" ){
                      endDate = parseInt(endDate.substring(0,3));
                      if( endDate > 1000 && endDate < nextYear){
                        latestEndDate = endDate;
                      }
                    }
                    i++;
                  }

                  //Save the latest endDate if one was found
                  if( latestEndDate ){
                    model.set("lastEndDate", latestEndDate);
                  }
                }

                //Save the temporal coverage year ranges
                var tempCoverages = data.facet_counts.facet_queries;
                model.set("temporalCoverage", tempCoverages);
              }

              //Get the total size of all the files in the index
              if( data.stats && data.stats.stats_fields && data.stats.stats_fields.size && data.stats.stats_fields.size.sum ){
                //Save the size sum
                model.set("metadataTotalSize", data.stats.stats_fields.size.sum);
                //If there is a data size sum,
                if( typeof model.get("dataTotalSize") == "number" ){
                  //Add it to the metadata size sum as the total sum
                  model.set("totalSize", model.get("dataTotalSize") + data.stats.stats_fields.size.sum);
                }
              }
            }
          }

          //Construct the full URL for the query
          var fullQueryURL = MetacatUI.appModel.get('queryServiceUrl') +
              "q=" + query +
              "&fq=" + filterQuery +
              "&stats=" + stats +
              "&stats.field=" + statsField +
              "&facet=" + facet +
              "&facet.field=" + facetFormatIdField +
              "&facet.field=" + facetBeginDateField +
              "&facet.field=" + facetEndDateField +
              "&f." + facetFormatIdField  + ".facet.mincount=" + facetFormatIdMin +
              "&f." + facetFormatIdField  + ".facet.missing=" + facetFormatIdMissing +
              "&f." + facetBeginDateField + ".facet.mincount=" + facetDateMin +
              "&f." + facetEndDateField   + ".facet.mincount=" + facetDateMin +
              "&f." + facetBeginDateField + ".facet.missing=" + facetDateMissing +
              "&f." + facetEndDateField   + ".facet.missing=" + facetDateMissing +
              "&facet.limit=" + facetLimit +
              "&f." + facetRange + ".facet.missing=" + facetMissing +
              "&facet.range=" + facetRange +
              "&facet.range.start=" + facetRangeStart +
              "&facet.range.end=" + facetRangeEnd +
              "&facet.range.gap=" + encodeURIComponent(facetRangeGap) +
              "&facet.query=" + facetQueries.join("&facet.query=") +
              "&rows=" + rows +
              "&wt=" + wt;

          if( this.getRequestType(fullQueryURL) == "POST" ){

            if( this.get("postQuery") ){
              query = this.get("postQuery");
            }
            else if( this.get("searchModel") ){
              query = this.get("searchModel").getQuery(undefined, { forPOST: true });
              this.set("postQuery", query);
            }

            var queryData = new FormData();
            queryData.append("q", query);
            queryData.append("fq", filterQuery);
            queryData.append("stats", stats);
            queryData.append("stats.field", statsField);
            queryData.append("facet", facet);
            queryData.append("facet.field", facetFormatIdField);
            queryData.append("facet.field", facetBeginDateField);
            queryData.append("facet.field", facetEndDateField);
            queryData.append("f." + facetFormatIdField  + ".facet.mincount", facetFormatIdMin);
            queryData.append("f." + facetFormatIdField  + ".facet.missing", facetFormatIdMissing);
            queryData.append("f." + facetBeginDateField + ".facet.mincount", facetDateMin);
            queryData.append("f." + facetEndDateField   + ".facet.mincount", facetDateMin);
            queryData.append("f." + facetBeginDateField + ".facet.missing", facetDateMissing);
            queryData.append("f." + facetEndDateField   + ".facet.missing", facetDateMissing);
            queryData.append("facet.limit", facetLimit);
            queryData.append("facet.range", facetRange);
            queryData.append("facet.range.start", facetRangeStart);
            queryData.append("facet.range.end", facetRangeEnd);
            queryData.append("facet.range.gap", facetRangeGap);
            queryData.append("f." + facetRange + ".facet.missing", facetMissing);
            queryData.append("rows", rows);
            queryData.append("wt", wt);

            //Add the facet queries to the POST body
            _.each(facetQueries, function(facetQuery){
              queryData.append("facet.query", facetQuery);
            });

            //Create the request settings for POST requests
            var requestSettings = {
              url: MetacatUI.appModel.get('queryServiceUrl'),
              type: "POST",
              contentType: false,
              processData: false,
              data: queryData,
              dataType: "json",
              success: successCallback
            }
          }
          else{
            //Create the request settings for GET requests
            var requestSettings = {
              url: fullQueryURL,
              type: "GET",
              dataType: "json",
              success: successCallback
            }
          }

          //Send the request
          $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));

        },

        /**
        * Queries for statistics about data objects
        */
        getDataStats: function(){

          //Get the query string from this model
          var query = this.get("query") || "";
          //If there is a query set on the model, do a join on the resourceMap field
          if((query.trim() !== "*:*" && query.trim().length > 0 && !this.get("isSystemMetadataQuery"))
             && MetacatUI.appModel.get("enableSolrJoins")){
            query = "{!join from=resourceMap to=resourceMap}" + query;
          }

              //Filter out resource maps and metatdata objects
          var filterQuery = "formatType:DATA AND -obsoletedBy:*",
              //Use the stats feature to get the sum of the file size
              stats = "true",
              statsField = "size",
              //Get the facet counts for formatIds
              facet = "true",
              facetField = "formatId",
              facetFormatIdMin = "1",
              facetFormatIdMissing = "false",
              facetLimit = "-1",
              //Get the upload counts for each month
              facetRange = "dateUploaded",
              facetRangeGap = "+1MONTH",
              facetRangeStart = "1900-01-01T00:00:00.000Z",
              facetRangeEnd = (new Date()).toISOString(),
              facetRangeMissing = "true",
              //Don't return any result docs
              rows = "0",
              //Use JSON for the response format
              wt = "json";

          var fullQueryURL = MetacatUI.appModel.get('queryServiceUrl') +
              "q=" + query +
              "&fq=" + filterQuery +
              "&stats=" + stats +
              "&stats.field=" + statsField +
              "&facet=" + facet +
              "&facet.field=" + facetField +
              "&facet.limit=" + facetLimit +
              "&f." + facetField + ".facet.mincount=" + facetFormatIdMin +
              "&f." + facetField + ".facet.missing=" + facetFormatIdMissing +
              "&f." + facetRange + ".facet.missing=" + facetRangeMissing +
              "&facet.range=" + facetRange +
              "&facet.range.start=" + facetRangeStart +
              "&facet.range.end=" + facetRangeEnd +
              "&facet.range.gap=" + encodeURIComponent(facetRangeGap) +
              "&rows=" + rows +
              "&wt=" + wt;

          var model = this;
          var successCallback = function(data, textStatus, xhr) {

            if( !data || !data.response || !data.response.numFound ){
              //Store falsey data
              model.set('dataCount', 0);
              model.trigger("change:dataCount");
              model.set('dataFormatIDs', ["", 0]);
              model.set("dataUpdateDates", []);
              model.set("dataTotalSize", 0);

              if( typeof model.get("metadataTotalSize") == "number" ){
                //Use the metadata total size as the total size
                model.set("totalSize", model.get("metadataTotalSize"));
              }
            }
            else{
              //Save the number of data docs found
              model.set('dataCount', data.response.numFound);
              model.set("totalCount", model.get("metadataCount") + data.response.numFound);

              //Save the format ID facet counts
              if( data.facet_counts && data.facet_counts.facet_fields && data.facet_counts.facet_fields.formatId ){
                model.set("dataFormatIDs", data.facet_counts.facet_fields.formatId);
              }
              else{
                model.set("dataFormatIDs", ["", 0]);
              }

              //Save the data update date counts
              if( data.facet_counts && data.facet_counts.facet_ranges && data.facet_counts.facet_ranges.dateUploaded ){

                //Find the index of the first update date
                var updateFacets = data.facet_counts.facet_ranges.dateUploaded.counts,
                    cropAt = 0;

                for( var i=1; i<updateFacets.length; i+=2 ){
                  //If there was at least one update/upload in this date range, then save this as the first update
                  if( typeof updateFacets[i] == "number" && updateFacets[i] > 0){
                    //Save the first first update date
                    cropAt = i;
                    model.set('firstUpdate', updateFacets[i-1]);
                    //Save the update dates, but crop out months that are empty
                    model.set("dataUpdateDates", updateFacets.slice(cropAt+1));
                    i = updateFacets.length;
                  }
                }

                //If no update dates were found, save falsey values
                if( cropAt === 0 ){
                  model.set('firstUpdate', null);
                  model.set("dataUpdateDates", []);
                }
              }

              //Get the total size of all the files in the index
              if( data.stats && data.stats.stats_fields && data.stats.stats_fields.size && data.stats.stats_fields.size.sum ){
                //Save the size sum
                model.set("dataTotalSize", data.stats.stats_fields.size.sum);
                //If there is a metadata size sum,
                if( model.get("metadataTotalSize") > 0 ){
                  //Add it to the data size sum as the total sum
                  model.set("totalSize", model.get("metadataTotalSize") + data.stats.stats_fields.size.sum);
                }
              }
            }
          }

          if( this.getRequestType(fullQueryURL) == "POST" ){

            if( this.get("postQuery") ){
              query = this.get("postQuery");
            }
            else if( this.get("searchModel") ){
              query = this.get("searchModel").getQuery(undefined, { forPOST: true });
              this.set("postQuery", query);
            }

            var queryData = new FormData();
            queryData.append("q", query);
            queryData.append("fq", filterQuery);
            queryData.append("stats", stats);
            queryData.append("stats.field", statsField);
            queryData.append("facet", facet);
            queryData.append("facet.field", facetField);
            queryData.append("facet.limit", facetLimit);
            queryData.append("f." + facetField + ".facet.mincount", facetFormatIdMin);
            queryData.append("f." + facetField + ".facet.missing", facetFormatIdMissing);
            queryData.append("f." + facetRange + ".facet.missing", facetRangeMissing);
            queryData.append("facet.range", facetRange);
            queryData.append("facet.range.start", facetRangeStart);
            queryData.append("facet.range.end", facetRangeEnd);
            queryData.append("facet.range.gap", facetRangeGap);
            queryData.append("rows", rows);
            queryData.append("wt", wt);

            //Create the request settings for POST requests
            var requestSettings = {
              url: MetacatUI.appModel.get('queryServiceUrl'),
              type: "POST",
              contentType: false,
              processData: false,
              data: queryData,
              dataType: "json",
              success: successCallback
            }
          }
          else{
            //Create the request settings for GET requests
            var requestSettings = {
              url: fullQueryURL,
              type: "GET",
              dataType: "json",
              success: successCallback
            }
          }

          //Send the request
          $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));

        },

        /**
        * Retrieves an image of the metadata assessment scores
        */
        getMdqScores: function(){
          try{
            var myImage = new Image();
            var model = this;
            myImage.crossOrigin = ""; // or "anonymous"
            if(MetacatUI.appView.currentView === null) return;

            // Call the function with the URL we want to load, but then chain the
            // promise then() method on to the end of it. This contains two callbacks
            var serviceUrl = MetacatUI.appModel.get('mdqScoresServiceUrl');

            if( !serviceUrl ){
              this.set("mdqScoresImage", this.defaults().mdqScoresImage);
              this.trigger("change:mdqScoresImage");
              return;
            }

            if( Array.isArray(MetacatUI.appModel.get('mdqAggregatedSuiteIds')) && MetacatUI.appModel.get('mdqAggregatedSuiteIds').length ){
              var suite = MetacatUI.appModel.get('mdqAggregatedSuiteIds')[0];
              var id = MetacatUI.appView.currentView.model.get("id");
              var url = serviceUrl + "?collection=" + id + "&suite=" + suite;
              this.imgLoad(url).then(function (response) {
                  // The first runs when the promise resolves, with the request.reponse specified within the resolve() method.
                  var imageURL = window.URL.createObjectURL(response);
                  myImage.src = imageURL;
                  model.set('mdqScoresImage', myImage);
                  // The second runs when the promise is rejected, and logs the Error specified with the reject() method.
              }, function (Error) {
                  console.error(Error);
              });
            }
            else{
              this.set("mdqScoresImage", this.defaults().mdqScoresImage);
            }
          }
          catch(e){
            this.set("mdqScoresImage", this.defaults().mdqScoresImage);
            this.trigger("change:mdqScoresImage");
            console.error("Cannot get the Metadata Assessment scores: ", e);
          }
        },

        /**
        * Retrieves an image via a Promise. Primarily used by {@link Stats#getMdqScores}
        * @param {string} url - The URL of the image
        */
        imgLoad: function(url) {
            // Create new promise with the Promise() constructor;
            // This has as its argument a function with two parameters, resolve and reject
            var model = this;
            return new Promise(function (resolve, reject) {
                // Standard XHR to load an image
                var request = new XMLHttpRequest();
                request.open('GET', url);
                request.responseType = 'blob';

                // When the request loads, check whether it was successful
                request.onload = function () {
                    if (request.status === 200) {
                        // If successful, resolve the promise by passing back the request response
                        resolve(request.response);
                    } else {
                        // If it fails, reject the promise with a error message
                        reject(new Error('Image didn\'t load successfully; error code:' + request.statusText));
                        model.set('mdqScoresError', request.statusText);
                    }
                };

                request.onerror = function () {
                    console.log("onerror");
                    // Also deal with the case when the entire request fails to begin with
                    // This is probably a network error, so reject the promise with an appropriate message
                    reject(new Error('There was a network error.'));
                };

                // Send the request
                request.send();
            });
        },

        /**
        * Sends a Solr query to get the earliest beginDate. If there are no beginDates in the index, then it
        * searches for the earliest endDate.
        */
        getFirstBeginDate: function(){
          var model = this;

          //Define a success callback when the query is successful
          var successCallback = function(data, textStatus, xhr) {

            //If nothing was found...
            if( !data || !data.response || !data.response.numFound ){

              //Construct a query to find the earliest endDate
              var query = model.get('query') +
                    " AND endDate:[" + this.get("firstPossibleDate") + " TO " + (new Date()).toISOString() + "]" + //Use date filter to weed out badly formatted data
                    " AND -obsoletedBy:*",
                  //Get one row only
                  rows = "1",
                  //Sort the results in ascending order
                  sort = "endDate asc",
                  //Return only the endDate field
                  fl = "endDate";

              var successCallback = function(endDateData, textStatus, xhr) {
                //If not endDates or beginDates are found, there is no temporal data in the index, so save falsey values
                if( !endDateData || !endDateData.response || !endDateData.response.numFound){
                  model.set('firstBeginDate', null);
                  model.set('lastEndDate', null);
                }
                else{
                  model.set('firstBeginDate', new Date(endDateData.response.docs[0].endDate));
                }
              }

              if( model.get("usePOST") ){

                var queryData = new FormData();
                queryData.append("q", query);
                queryData.append("rows", rows);
                queryData.append("sort", sort);
                queryData.append("fl", fl);
                queryData.append("wt", "json");

                var requestSettings = {
                  url: MetacatUI.appModel.get('queryServiceUrl'),
                  type: "POST",
                  contentType: false,
                  processData: false,
                  data: queryData,
                  dataType: "json",
                  success: successCallback
                }

              }
              else{
                //Find the earliest endDate if there are no beginDates
                var requestSettings = {
                  url: MetacatUI.appModel.get('queryServiceUrl') + "q=" + query +
                       "&rows=" + rows + "&sort=" + sort + "&fl=" + fl + "&wt=json",
                  type: "GET",
                  dataType: "json",
                  success: successCallback
                }
              }

              $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
            }
            else{
              // Save the earliest beginDate
              model.set('firstBeginDate', new Date(data.response.docs[0].beginDate));
              model.trigger("change:firstBeginDate");
            }
          }

          //Construct a query
          var specialQueryParams = " AND beginDate:[" + this.get("firstPossibleDate") + " TO " + (new Date()).toISOString() + "] AND -obsoletedBy:* AND -formatId:*dataone.org/collections* AND -formatId:*dataone.org/portals*",
              query = this.get("query") + specialQueryParams,
              //Get one row only
              rows = "1",
              //Sort the results in ascending order
              sort = "beginDate asc",
              //Return only the beginDate field
              fl = "beginDate";

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

            //Get the unencoded query string
            if( this.get("postQuery") ){
              query = this.get("postQuery") + specialQueryParams;
            }
            else if( this.get("searchModel") ){
              query = this.get("searchModel").getQuery(undefined, { forPOST: true });
              this.set("postQuery", query);
              query = query + specialQueryParams;
            }

            var queryData = new FormData();
            queryData.append("q", query);
            queryData.append("rows", rows);
            queryData.append("sort", sort);
            queryData.append("fl", fl);
            queryData.append("wt", "json");

            var requestSettings = {
              url: MetacatUI.appModel.get('queryServiceUrl'),
              type: "POST",
              contentType: false,
              processData: false,
              data: queryData,
              dataType: "json",
              success: successCallback
            }

          }
          else{

            var requestSettings = {
              url: MetacatUI.appModel.get('queryServiceUrl') + "q=" + query +
                   "&rows=" + rows +
                   "&fl=" + fl +
                   "&sort=" + sort +
                   "&wt=json",
              type: "GET",
              dataType: "json",
              success: successCallback
            }

          }

          //Send the query
          $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));

        },

        /**
        * Gets the latest endDate from the Solr index
        */
        getLastEndDate: function(){
          var model = this;

          var now = new Date();

          //Get the latest temporal data coverage year
          var specialQueryParams = " AND endDate:[" + this.get("firstPossibleDate") + " TO " + now.toISOString() + "]" + //Use date filter to weed out badly formatted data
                " AND -obsoletedBy:* AND -formatId:*dataone.org/collections* AND -formatId:*dataone.org/portals*",
              query = this.get('query') + specialQueryParams,
              rows = 1,
              fl   = "endDate",
              sort = "endDate desc",
              wt   = "json";

          var successCallback = function(data, textStatus, xhr) {
            if(typeof data == "string"){
              data = JSON.parse(data);
            }

            if(!data || !data.response || !data.response.numFound){
              //Save some falsey values if none are found
              model.set('lastEndDate', null);
            }
            else{
              // Save the earliest beginDate and total found in our model - but do not accept a year greater than this current year
              var now = new Date();
              if(new Date(data.response.docs[0].endDate).getUTCFullYear() > now.getUTCFullYear()){
                model.set('lastEndDate', now);
              }
              else{
                model.set('lastEndDate', new Date(data.response.docs[0].endDate));
              }

              model.trigger("change:lastEndDate");
            }
          }

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

            //Get the unencoded query string
            if( this.get("postQuery") ){
              query = this.get("postQuery") + specialQueryParams;
            }
            else if( this.get("searchModel") ){
              query = this.get("searchModel").getQuery(undefined, { forPOST: true });
              this.set("postQuery", query);
              query = query + specialQueryParams;
            }

            var queryData = new FormData();
            queryData.append("q", query);
            queryData.append("rows", rows);
            queryData.append("sort", sort);
            queryData.append("fl", fl);
            queryData.append("wt", "json");

            var requestSettings = {
              url: MetacatUI.appModel.get('queryServiceUrl'),
              type: "POST",
              contentType: false,
              processData: false,
              data: queryData,
              dataType: "json",
              success: successCallback
            }
          }
          else{
            //Query for the latest endDate
            var requestSettings = {
              url: MetacatUI.appModel.get('queryServiceUrl') + "q=" + query +
                   "&rows=" + rows + "&fl=" + fl + "&sort=" + sort + "&wt=" + wt,
              type: "GET",
              dataType: "json",
              success: successCallback
            }
          }

          $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
        },

        /**
        * Given the query or URL, determine whether this model should send GET or POST
        * requests, because of URL length restrictions in browsers.
        * @param {string} queryOrURLString - The full query or URL that will be sent to the query service
        * @returns {string} The request type to use. Either `GET` or `POST`
        */
        getRequestType: function(queryOrURLString){
          //If POSTs to the query service are disabled completely, use GET
          if( MetacatUI.appModel.get("disableQueryPOSTs") ){
            return "GET";
          }
          //If POSTs are enabled and the URL is over the maximum, use POST
          else if( queryOrURLString && queryOrURLString.length > this.get("maxQueryLength") ){
            return "POST";
          }
          //Otherwise, default to GET
          else{
            return "GET";
          }
        },

        /**
        * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} and {@link Stats#getDataStats} to get the formatTypes.
        * This function may be removed in a future release.
        */
        getFormatTypes: function(){
          this.getMetadataStats();
          this.getDataStats();
        },

        /**
        * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getDataStats} to get the formatTypes.
        * This function may be removed in a future release.
        */
        getDataFormatIDs: function(){
          this.getDataStats();
        },

        /**
        * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} to get the formatTypes.
        * This function may be removed in a future release.
        */
        getMetadataFormatIDs: function(){
          this.getMetadataStats();
        },

        /**
        * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} and {@link Stats#getDataStats} to get the formatTypes.
        * This function may be removed in a future release.
        */
        getUpdateDates: function(){
          this.getMetadataStats();
          this.getDataStats();
        },

        /**
        * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} to get the formatTypes.
        * This function may be removed in a future release.
        */
        getCollectionYearFacets: function(){
          this.getMetadataStats();
        }

      });
      return Stats;
    });