/**
 * @module ngeo.statemanager.WfsPermalink
 */
import googAsserts from 'goog/asserts.js';
import * as olExtent from 'ol/extent.js';
import * as olFormatFilter from 'ol/format/filter.js';
import olFormatWFS from 'ol/format/WFS.js';

/**
 * WFS permalink service that can be used to load features with a WFS
 * GetFeature request given query parameters.
 *
 * Resulting features are then highlighted and
 * the map is zoomed to the nearest map extent.
 *
 * Parameters:
 *
 * - ``wfs_layer`` tells what layer will be queried
 * - ``wfs_showFeatures`` (boolean) tells if the features should be
 *   highlighted and listed (when true) or if the map should only be
 *   recentered on the features (when false). Default is true.
 * - other parameters will be considered as WFS attribute/values filters and
 *   must be of the form:
 *   ``wfs_<layer attribute name>=<a comma-separated list of values>``
 *
 * Example:
 * http://example.com?wfs_layer=parcels&wfs_city=Oslo&wfs_number=12,34,56
 * will load parcels #12, 34 and 56 of the city of Oslo.
 *
 * It is possible to define several groups of filtering parameters by:
 *
 * - adding a ``wfs_ngroups`` parameter telling how many groups are defined
 * - prefixing all filtering parameters by the number of each group,
 *   starting at 0. For instance ``wfs_0_<layer attribute name>``
 *
 * Example:
 * http://example.com?wfs_layer=parcels&wfs_ngroups=2
 * &wfs_0_city=Oslo&wfs_0_number=12,34,56&wfs_1_city=Paris&wfs_1_number=78,90
 * will load parcels #12, 34 and 56 of the city of Oslo as well as
 * parcels #78 and 90 of the city of Paris.
 *
 * @constructor
 * @struct
 * @param {angular.$http} $http Angular $http service.
 * @param {ngeox.QueryResult} ngeoQueryResult The ngeo query result service.
 * @param {ngeox.WfsPermalinkOptions} ngeoWfsPermalinkOptions The options to
 *     configure the ngeo wfs permalink service with.
 * @ngdoc service
 * @ngname ngeoWfsPermalink
 * @ngInject
 */
const WfsPermalinkService = function($http, ngeoQueryResult, ngeoWfsPermalinkOptions) {

  const options = ngeoWfsPermalinkOptions;

  /**
   * @type {string}
   * @private
   */
  this.url_ = options.url;

  /**
   * @type {number}
   * @private
   */
  this.maxFeatures_ = options.maxFeatures !== undefined ? options.maxFeatures : 50;

  /**
   * @type {Object<string, ngeox.WfsType>}
   * @private
   */
  this.wfsTypes_ = {};

  googAsserts.assertArray(options.wfsTypes, 'wfsTypes is not correctly set');
  options.wfsTypes.forEach((wfsType) => {
    this.wfsTypes_[wfsType.featureType] = wfsType;
  });

  /**
   * @type {string}
   * @private
   */
  this.defaultFeatureNS_ = options.defaultFeatureNS;

  /**
   * @type {string}
   * @private
   */
  this.defaultFeaturePrefix_ = options.defaultFeaturePrefix;

  /**
   * @type {number|undefined}
   * @private
   */
  this.pointRecenterZoom_ = options.pointRecenterZoom;

  /**
   * @type {angular.$http}
   * @private
   */
  this.$http_ = $http;

  /**
   * @type {ngeox.QueryResult}
   * @private
   */
  this.result_ = ngeoQueryResult;
};


/**
 * Clear the results.
 * @export
 */
WfsPermalinkService.prototype.clear = function() {
  this.clearResult_();
};


/**
 * Build a WFS GetFeature request for the given query parameter data, send the
 * request and add the received features to {@link ngeox.QueryResult}.
 *
 * @param {ngeox.WfsPermalinkData} queryData Query data for the WFS request.
 * @param {ol.Map} map The ol3 map object to get the current projection from.
 * @export
 */
WfsPermalinkService.prototype.issue = function(queryData, map) {
  googAsserts.assert(this.url_,
    'url is not set. to use the wfs permalink service, ' +
      'set the value `ngeoWfsPermalinkOptions`');
  this.clearResult_();

  const typeName = queryData.wfsType;
  if (!this.wfsTypes_.hasOwnProperty(typeName)) {
    return;
  }
  const wfsType = this.wfsTypes_[typeName];

  const filters = this.createFilters_(queryData.filterGroups);
  if (filters === null) {
    return;
  }

  this.issueRequest_(wfsType, filters, map, queryData.showFeatures);
};


/**
 * @param {ngeox.WfsType} wfsType Type.
 * @param {ol.format.filter.Filter} filter Filter.
 * @param {ol.Map} map The ol3 map object to get the current projection from.
 * @param {boolean} showFeatures Show features or only zoom to feature extent?
 * @private
 */
WfsPermalinkService.prototype.issueRequest_ = function(wfsType, filter, map, showFeatures) {
  const wfsFormat = new olFormatWFS();
  const featureRequestXml = wfsFormat.writeGetFeature({
    srsName: map.getView().getProjection().getCode(),
    featureNS: (wfsType.featureNS !== undefined) ?
      wfsType.featureNS : this.defaultFeatureNS_,
    featurePrefix: (wfsType.featurePrefix !== undefined) ?
      wfsType.featurePrefix : this.defaultFeaturePrefix_,
    featureTypes: [wfsType.featureType],
    outputFormat: 'GML3',
    filter: filter,
    maxFeatures: this.maxFeatures_
  });

  const featureRequest = new XMLSerializer().serializeToString(featureRequestXml);
  const config = {
    headers: {'Content-Type': 'text/xml; charset=UTF-8'}
  };
  this.$http_.post(this.url_, featureRequest, config).then((response) => {
    const features = wfsFormat.readFeatures(response.data);
    if (features.length == 0) {
      return;
    }

    // zoom to features
    const size = map.getSize();
    if (size !== undefined) {
      const maxZoom = this.pointRecenterZoom_;
      const padding = [10, 10, 10, 10];
      map.getView().fit(this.getExtent_(features), {size, maxZoom, padding});
    }

    // then show if requested
    if (showFeatures) {
      const resultSource = /** @type {ngeox.QueryResultSource} */ ({
        'features': features,
        'id': wfsType.featureType,
        'identifierAttributeField': wfsType.label,
        'label': wfsType.featureType,
        'pending': false
      });

      this.result_.sources.push(resultSource);
      this.result_.total = features.length;
    }
  });
};


/**
 * @param {Array.<ol.Feature>} features Features.
 * @return {ol.Extent} The extent of all features.
 * @private
 */
WfsPermalinkService.prototype.getExtent_ = function(features) {
  return features.reduce((extent, feature) => olExtent.extend(extent, feature.getGeometry().getExtent()), olExtent.createEmpty());
};

/**
 * Create OGC filters for the filter groups extracted from the query params.
 *
 * @param {Array.<ngeox.WfsPermalinkFilterGroup>} filterGroups Filter groups.
 * @return {ol.format.filter.Filter} OGC filters.
 * @private
 */
WfsPermalinkService.prototype.createFilters_ = function(filterGroups) {
  if (filterGroups.length == 0) {
    return null;
  }
  const f = olFormatFilter;
  const createFiltersForGroup = function(filterGroup) {
    const filters = filterGroup.filters.map((filterDef) => {
      const condition = filterDef.condition;
      if (Array.isArray(condition)) {
        return WfsPermalinkService.or_(condition.map(cond => f.equalTo(filterDef.property, cond)));
      } else {
        return f.equalTo(filterDef.property, filterDef.condition);
      }
    });
    return WfsPermalinkService.and_(filters);
  };
  return WfsPermalinkService.or_(filterGroups.map(createFiltersForGroup));
};


/**
 * Join a list of filters with `and(...)`.
 *
 * @param {Array.<ol.format.filter.Filter>} filters The filters to join.
 * @return {ol.format.filter.Filter} The joined filters.
 * @private
 */
WfsPermalinkService.and_ = function(filters) {
  return WfsPermalinkService.joinFilters_(filters, olFormatFilter.and);
};


/**
 * Join a list of filters with `or(...)`.
 *
 * @param {Array.<ol.format.filter.Filter>} filters The filters to join.
 * @return {ol.format.filter.Filter} The joined filters.
 * @private
 */
WfsPermalinkService.or_ = function(filters) {
  return WfsPermalinkService.joinFilters_(filters, olFormatFilter.or);
};


/**
 * Join a list of filters with a given join function.
 *
 * @param {Array.<ol.format.filter.Filter>} filters The filters to join.
 * @param {function(!ol.format.filter.Filter, !ol.format.filter.Filter):
 *    ol.format.filter.Filter} joinFn The function to join two filters.
 * @return {ol.format.filter.Filter} The joined filters.
 * @private
 */
WfsPermalinkService.joinFilters_ = function(filters, joinFn) {
  return filters.reduce((combinedFilters, currentFilter) => {
    if (combinedFilters === null) {
      return currentFilter;
    } else {
      googAsserts.assert(currentFilter !== null);
      return joinFn(combinedFilters, currentFilter);
    }
  }, null);
};


/**
 * Clear every features for all result sources and reset the total counter
 * as well.
 * @private
 */
WfsPermalinkService.prototype.clearResult_ = function() {
  this.result_.total = 0;
  this.result_.sources.forEach((source) => {
    source.features.length = 0;
  });
};


/**
 * @type {!angular.Module}
 */
WfsPermalinkService.module = angular.module('ngeoWfsPermalink', [
  // FIXME add dependencies
]);


/**
 * Value that is supposed to be set in applications to enable the WFS
 * permalink functionality.
 */
WfsPermalinkService.module.value('ngeoWfsPermalinkOptions',
  /** @type {ngeox.WfsPermalinkOptions} */ ({
    url: '',
    wfsTypes: [],
    defaultFeatureNS: '',
    defaultFeaturePrefix: ''
  })
);


WfsPermalinkService.module.service('ngeoWfsPermalink', WfsPermalinkService);


export default WfsPermalinkService;
