text: 'use strict';
  
  /*global require*/
  
  var clone = require('terriajs-cesium/Source/Core/clone');
  var defined = require('terriajs-cesium/Source/Core/defined');
  var defineProperties = require('terriajs-cesium/Source/Core/defineProperties');
  var formatError = require('terriajs-cesium/Source/Core/formatError');
  var freezeObject = require('terriajs-cesium/Source/Core/freezeObject');
  var knockout = require('terriajs-cesium/Source/ThirdParty/knockout');
  var loadJson = require('terriajs-cesium/Source/Core/loadJson');
  var proxyCatalogItemUrl = require('./proxyCatalogItemUrl');
  var Rectangle = require('terriajs-cesium/Source/Core/Rectangle');
  var when = require('terriajs-cesium/Source/ThirdParty/when');
  
  var inherit = require('../Core/inherit');
  
  var WebMapServiceCatalogItem = require('./WebMapServiceCatalogItem');
  var GeoJsonCatalogItem = require('./GeoJsonCatalogItem');
  var CatalogGroup = require('./CatalogGroup');
  var TerriaError = require('../Core/TerriaError');
  
  /**
   * A {@link CatalogGroup} representing a collection of layers from a [Socrata](http://Socrata.org) server. Only spatial layers with a defined Map
   * visualisation are shown, using WMS.
   *
   * @alias SocrataCatalogGroup
   * @constructor
   * @extends CatalogGroup
   *
   * @param {Terria} terria The Terria instance.
   */
  var SocrataCatalogGroup = function(terria) {
      CatalogGroup.call(this, terria, 'socrata');
  
      /**
       * Gets or sets the URL of the Socrata server.  This property is observable.
       * @type {String}
       */
      this.url = '';
      /**
       * Gets or sets the filter query to pass to Socrata when querying the available data sources and their groups.  Each string in the
       * array is passed to Socrata as an independent search string and the results are concatenated to create the complete list.
       * @type {String[]}
       */
      this.filterQuery = ['limitTo=MAPS'];
  
      /**
       * Gets or sets a description of the custodian of the data sources in this group.
       * This property is an HTML string that must be sanitized before display to the user.
       * This property is observable.
       * @type {String}
       */
      this.dataCustodian = undefined;
  
      /**
       * Gets or sets a value indicating how datasets should be grouped.  Valid values are:
       * * `none` - Datasets are put in a flat list; they are not grouped at all.
       * * `category` - Datasets are grouped according to their category in Socrata.
       * @type {String}
       */
      this.groupBy = 'category';
  
      knockout.track(this, ['url', 'filterQuery', 'dataCustodian','category']);
  
  };
  
  inherit(CatalogGroup, SocrataCatalogGroup);
  
  defineProperties(SocrataCatalogGroup.prototype, {
  
      /**
       * Gets the type of data member represented by this instance.
       * @memberOf SocrataCatalogGroup.prototype
       * @type {String}
       */
      type : {
          get : function() {
              return 'socrata';
          }
      },
  
      /**
       * Gets a human-readable name for this type of data source, such as 'Web Map Service (WMS)'.
       * @memberOf SocrataCatalogGroup.prototype
       * @type {String}
       */
      typeName : {
          get : function() {
              return 'Socrata Server';
          }
      },
  
      /**
       * Gets the set of functions used to update individual properties in {@link CatalogMember#updateFromJson}.
       * When a property name in the returned object literal matches the name of a property on this instance, the value
       * will be called as a function and passed a reference to this instance, a reference to the source JSON object
       * literal, and the name of the property.
       * @memberOf SocrataCatalogGroup.prototype
       * @type {Object}
       */
      updaters : {
          get : function() {
              return SocrataCatalogGroup.defaultUpdaters;
          }
      },
  
      /**
       * Gets the set of functions used to serialize individual properties in {@link CatalogMember#serializeToJson}.
       * When a property name on the model matches the name of a property in the serializers object literal,
       * the value will be called as a function and passed a reference to the model, a reference to the destination
       * JSON object literal, and the name of the property.
       * @memberOf SocrataCatalogGroup.prototype
       * @type {Object}
       */
      serializers : {
          get : function() {
              return SocrataCatalogGroup.defaultSerializers;
          }
      }
  });
  
  /**
   * Gets or sets the set of default updater functions to use in {@link CatalogMember#updateFromJson}.  Types derived from this type
   * should expose this instance - cloned and modified if necesary - through their {@link CatalogMember#updaters} property.
   * @type {Object}
   */
  SocrataCatalogGroup.defaultUpdaters = clone(CatalogGroup.defaultUpdaters);
  
  freezeObject(SocrataCatalogGroup.defaultUpdaters);
  
  /**
   * Gets or sets the set of default serializer functions to use in {@link CatalogMember#serializeToJson}.  Types derived from this type
   * should expose this instance - cloned and modified if necesary - through their {@link CatalogMember#serializers} property.
   * @type {Object}
   */
  SocrataCatalogGroup.defaultSerializers = clone(CatalogGroup.defaultSerializers);
  
  SocrataCatalogGroup.defaultSerializers.items = CatalogGroup.enabledShareableItemsSerializer;
  
  SocrataCatalogGroup.defaultSerializers.isLoading = function(socrataGroup, json, propertyName, options) {};
  
  
  SocrataCatalogGroup.prototype._getValuesThatInfluenceLoad = function() {
      return [this.url, this.filterQuery, this.groupBy, this.dataCustodian];
  };
  
  SocrataCatalogGroup.prototype._load = function() {
      if (!defined(this.url) || this.url.length === 0) {
          return undefined;
      }
  
      var that = this;
  
      var promises = [];
      for (var i = 0; i < this.filterQuery.length; i++) {
          // Socrata always has CORS enabled, but we may proxy anyway in IE9 or if we want to cache.
          var url = proxyCatalogItemUrl(that, this.url + '/api/search/views?' + this.filterQuery[i], '1d');
  
          var promise = loadJson(url);
  
          promises.push(promise);
      }
  
      return when.all(promises).then( function(queryResults) {
          if (!defined(queryResults)) {
              return;
          }
          var allResults = queryResults[0];
          for (var p = 1; p < queryResults.length; p++) {
              allResults.result.results = allResults.result.results.concat(queryResults[p].result.results);
          }
  
          populateGroupFromResults(that, allResults);
      }).otherwise(function(e) {
          throw new TerriaError({
              sender: that,
              title: that.name,
              message: '\
  Couldn\'t retrieve packages from this Socrata server.<br/><br/>\
  If you entered the URL manually, please double-check it.<br/><br/>\
  Otherwise, if reloading doesn\'t fix it, please report the problem by sending an email to <a href="mailto:'+that.terria.supportEmail+'">'+that.terria.supportEmail+'</a> with the technical details below.  Thank you!<br/><br/>\
  <pre>' + formatError(e) + '</pre>'
          });
      });
  };
  
  
  function populateGroupFromResults(socrataGroup, json) {
      var items = json.results;
      for (var itemIndex = 0; itemIndex < items.length; ++itemIndex) {
          var item = items[itemIndex].view;
  
          var geo = item.metadata.geo;
          // Currently we only support spatial layers, which are identified by a geo {} object. TODO support other kinds of layers?
          if (!geo || !defined(item.childViews)) { // items without a 'childViews' seem to be themselves child views
              continue;
          }
          var id = item.category ?
          socrataGroup.uniqueId + '/' + item.category + '/' + item.id :
          socrataGroup.uniqueId + '/' + item.id;
  
          var newItem = socrataGroup.terria.catalog.shareKeyIndex[id];
  
          var alreadyExists = defined(newItem);
          if (!alreadyExists) {
              // Socrata is currently transitioning from a Geoserver/OWS tiling system to a "new backend" with GeoJSON download but no
              // reliable public tiling endpoint.
              if (item.newBackend) {
                  newItem = new GeoJsonCatalogItem(socrataGroup.terria);
                  // We have to choose a number of features to truncate to. We can go as high as 50,000 but in the case of Melbourne's
                  // urban forest canopies dataset, the file becomes 71MB.
                  newItem.url = socrataGroup.url + '/resource/' + item.childViews[0] + '.geojson' + '?$limit=10000';
              } else {
                  newItem = new WebMapServiceCatalogItem(socrataGroup.terria);
                  newItem.url = socrataGroup.url + geo.owsUrl;
                  newItem.layers = geo.layers;
                  if (geo.namespace) {
                      // Socrata gives us a list of layers like 'geo_foo,geo_bar', but we need to prepend them with the WMS namespace.
                      newItem.layers = geo.namespace + ':' + newItem.layers.replace(/,/g, ',' + geo.namespace + ':');
                  }
                  if (geo.bboxCrs === 'EPSG:4326' && defined(geo.bbox)) {
                      var parts = geo.bbox.split(',');
                      if (parts.length === 4) {
                          newItem.rectangle = Rectangle.fromDegrees(parts[0], parts[1], parts[2], parts[3]);
                      }
                  }
              }
          }
  
          newItem.name = item.name;
          newItem.id = id;
  
          if (defined(item.description)) {
              newItem.info.push({
                  name: 'Description',
                  content: item.description
              });
          }
          if (defined(item.license) && defined(item.license.name)) {
              newItem.info.push({
                  name:'Licence',
                  content:
                      (item.license.logoUrl ? '<img src=' + socrataGroup.url + '/' + item.license.logoUrl + ' /> &nbsp;' : '') +
                      (item.license.termsLink ? '<a href="' + item.license.termsLink + '">' + item.license.name + '</a>' : item.license.name)
              });
          }
          if (item.columns.length > 0) {
              newItem.info.push({
                  name: 'Attributes',
                  content: item.columns.map(function(e) { return e.name; }).join()
              });
          }
          if (defined(item.tags) && item.tags.length > 0) {
              newItem.info.push({
                  name: 'Tags',
                  content: item.tags.join()
              });
          }
  
          newItem.dataUrlType = 'direct'; // should really be landingpage or something
          newItem.dataUrl = socrataGroup.url + '/resource/' + item.id;
  
          if (defined(socrataGroup.dataCustodian)) {
              newItem.dataCustodian = socrataGroup.dataCustodian;
          } else {
              newItem.dataCustodian = item.attribution; // not quite right
          }
  
          if (socrataGroup.groupBy === 'category' && defined(item.category)) {
              var existingGroup = socrataGroup.findFirstItemByName(item.category);
              if (!defined(existingGroup)) {
                  existingGroup = new CatalogGroup(socrataGroup.terria);
                  existingGroup.name = item.category;
                  existingGroup.id = item.category;
                  socrataGroup.add(existingGroup);
              }
              existingGroup.add(newItem);
          } else {
              socrataGroup.add(newItem);
          }
      }
  }
  
  module.exports = SocrataCatalogGroup;
  