'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _index = require('../index.js');

var _index2 = _interopRequireDefault(_index);

var _array = require('../array.js');

var _array2 = _interopRequireDefault(_array);

var _feature = require('../feature.js');

var _feature2 = _interopRequireDefault(_feature);

var _feature3 = require('../format/feature.js');

var _feature4 = _interopRequireDefault(_feature3);

var _xmlfeature = require('../format/xmlfeature.js');

var _xmlfeature2 = _interopRequireDefault(_xmlfeature);

var _geometrylayout = require('../geom/geometrylayout.js');

var _geometrylayout2 = _interopRequireDefault(_geometrylayout);

var _linestring = require('../geom/linestring.js');

var _linestring2 = _interopRequireDefault(_linestring);

var _linearring = require('../geom/linearring.js');

var _linearring2 = _interopRequireDefault(_linearring);

var _multilinestring = require('../geom/multilinestring.js');

var _multilinestring2 = _interopRequireDefault(_multilinestring);

var _multipoint = require('../geom/multipoint.js');

var _multipoint2 = _interopRequireDefault(_multipoint);

var _multipolygon = require('../geom/multipolygon.js');

var _multipolygon2 = _interopRequireDefault(_multipolygon);

var _point = require('../geom/point.js');

var _point2 = _interopRequireDefault(_point);

var _polygon = require('../geom/polygon.js');

var _polygon2 = _interopRequireDefault(_polygon);

var _obj = require('../obj.js');

var _obj2 = _interopRequireDefault(_obj);

var _proj = require('../proj.js');

var _proj2 = _interopRequireDefault(_proj);

var _xml = require('../xml.js');

var _xml2 = _interopRequireDefault(_xml);

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}

/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Feature base format for reading and writing data in the GML format.
 * This class cannot be instantiated, it contains only base content that
 * is shared with versioned format classes ol.format.GML2 and
 * ol.format.GML3.
 *
 * @constructor
 * @abstract
 * @param {olx.format.GMLOptions=} opt_options
 *     Optional configuration object.
 * @extends {ol.format.XMLFeature}
 */
// FIXME Envelopes should not be treated as geometries! readEnvelope_ is part
// of GEOMETRY_PARSERS_ and methods using GEOMETRY_PARSERS_ do not expect
// envelopes/extents, only geometries!
var _ol_format_GMLBase_ = function (opt_options) {
  var options = /** @type {olx.format.GMLOptions} */
  opt_options ? opt_options : {};

  /**
   * @protected
   * @type {Array.<string>|string|undefined}
   */
  this.featureType = options.featureType;

  /**
   * @protected
   * @type {Object.<string, string>|string|undefined}
   */
  this.featureNS = options.featureNS;

  /**
   * @protected
   * @type {string}
   */
  this.srsName = options.srsName;

  /**
   * @protected
   * @type {string}
   */
  this.schemaLocation = '';

  /**
   * @type {Object.<string, Object.<string, Object>>}
   */
  this.FEATURE_COLLECTION_PARSERS = {};
  this.FEATURE_COLLECTION_PARSERS[_ol_format_GMLBase_.GMLNS] = {
    'featureMember': _xml2.default.makeReplacer(_ol_format_GMLBase_.prototype.readFeaturesInternal),
    'featureMembers': _xml2.default.makeReplacer(_ol_format_GMLBase_.prototype.readFeaturesInternal)
  };

  _xmlfeature2.default.call(this);
};

_index2.default.inherits(_ol_format_GMLBase_, _xmlfeature2.default);

/**
 * @const
 * @type {string}
 */
_ol_format_GMLBase_.GMLNS = 'http://www.opengis.net/gml';

/**
 * A regular expression that matches if a string only contains whitespace
 * characters. It will e.g. match `''`, `' '`, `'\n'` etc. The non-breaking
 * space (0xa0) is explicitly included as IE doesn't include it in its
 * definition of `\s`.
 *
 * Information from `goog.string.isEmptyOrWhitespace`: https://github.com/google/closure-library/blob/e877b1e/closure/goog/string/string.js#L156-L160
 *
 * @const
 * @type {RegExp}
 * @private
 */
_ol_format_GMLBase_.ONLY_WHITESPACE_RE_ = /^[\s\xa0]*$/;

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Array.<ol.Feature> | undefined} Features.
 */
_ol_format_GMLBase_.prototype.readFeaturesInternal = function (node, objectStack) {
  var localName = node.localName;
  var features = null;
  if (localName == 'FeatureCollection') {
    if (node.namespaceURI === 'http://www.opengis.net/wfs') {
      features = _xml2.default.pushParseAndPop([], this.FEATURE_COLLECTION_PARSERS, node, objectStack, this);
    } else {
      features = _xml2.default.pushParseAndPop(null, this.FEATURE_COLLECTION_PARSERS, node, objectStack, this);
    }
  } else if (localName == 'featureMembers' || localName == 'featureMember') {
    var context = objectStack[0];
    var featureType = context['featureType'];
    var featureNS = context['featureNS'];
    var i,
        ii,
        prefix = 'p',
        defaultPrefix = 'p0';
    if (!featureType && node.childNodes) {
      featureType = [], featureNS = {};
      for (i = 0, ii = node.childNodes.length; i < ii; ++i) {
        var child = node.childNodes[i];
        if (child.nodeType === 1) {
          var ft = child.nodeName.split(':').pop();
          if (featureType.indexOf(ft) === -1) {
            var key = '';
            var count = 0;
            var uri = child.namespaceURI;
            for (var candidate in featureNS) {
              if (featureNS[candidate] === uri) {
                key = candidate;
                break;
              }
              ++count;
            }
            if (!key) {
              key = prefix + count;
              featureNS[key] = uri;
            }
            featureType.push(key + ':' + ft);
          }
        }
      }
      if (localName != 'featureMember') {
        // recheck featureType for each featureMember
        context['featureType'] = featureType;
        context['featureNS'] = featureNS;
      }
    }
    if (typeof featureNS === 'string') {
      var ns = featureNS;
      featureNS = {};
      featureNS[defaultPrefix] = ns;
    }
    var parsersNS = {};
    var featureTypes = Array.isArray(featureType) ? featureType : [featureType];
    for (var p in featureNS) {
      var parsers = {};
      for (i = 0, ii = featureTypes.length; i < ii; ++i) {
        var featurePrefix = featureTypes[i].indexOf(':') === -1 ? defaultPrefix : featureTypes[i].split(':')[0];
        if (featurePrefix === p) {
          parsers[featureTypes[i].split(':').pop()] = localName == 'featureMembers' ? _xml2.default.makeArrayPusher(this.readFeatureElement, this) : _xml2.default.makeReplacer(this.readFeatureElement, this);
        }
      }
      parsersNS[featureNS[p]] = parsers;
    }
    if (localName == 'featureMember') {
      features = _xml2.default.pushParseAndPop(undefined, parsersNS, node, objectStack);
    } else {
      features = _xml2.default.pushParseAndPop([], parsersNS, node, objectStack);
    }
  }
  if (features === null) {
    features = [];
  }
  return features;
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.Geometry|undefined} Geometry.
 */
_ol_format_GMLBase_.prototype.readGeometryElement = function (node, objectStack) {
  var context = /** @type {Object} */objectStack[0];
  context['srsName'] = node.firstElementChild.getAttribute('srsName');
  context['srsDimension'] = node.firstElementChild.getAttribute('srsDimension');
  /** @type {ol.geom.Geometry} */
  var geometry = _xml2.default.pushParseAndPop(null, this.GEOMETRY_PARSERS_, node, objectStack, this);
  if (geometry) {
    return (
      /** @type {ol.geom.Geometry} */_feature4.default.transformWithOptions(geometry, false, context)
    );
  } else {
    return undefined;
  }
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.Feature} Feature.
 */
_ol_format_GMLBase_.prototype.readFeatureElement = function (node, objectStack) {
  var n;
  var fid = node.getAttribute('fid') || _xml2.default.getAttributeNS(node, _ol_format_GMLBase_.GMLNS, 'id');
  var values = {},
      geometryName;
  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
    var localName = n.localName;
    // Assume attribute elements have one child node and that the child
    // is a text or CDATA node (to be treated as text).
    // Otherwise assume it is a geometry node.
    if (n.childNodes.length === 0 || n.childNodes.length === 1 && (n.firstChild.nodeType === 3 || n.firstChild.nodeType === 4)) {
      var value = _xml2.default.getAllTextContent(n, false);
      if (_ol_format_GMLBase_.ONLY_WHITESPACE_RE_.test(value)) {
        value = undefined;
      }
      values[localName] = value;
    } else {
      // boundedBy is an extent and must not be considered as a geometry
      if (localName !== 'boundedBy') {
        geometryName = localName;
      }
      values[localName] = this.readGeometryElement(n, objectStack);
    }
  }
  var feature = new _feature2.default(values);
  if (geometryName) {
    feature.setGeometryName(geometryName);
  }
  if (fid) {
    feature.setId(fid);
  }
  return feature;
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.Point|undefined} Point.
 */
_ol_format_GMLBase_.prototype.readPoint = function (node, objectStack) {
  var flatCoordinates = this.readFlatCoordinatesFromNode_(node, objectStack);
  if (flatCoordinates) {
    var point = new _point2.default(null);
    point.setFlatCoordinates(_geometrylayout2.default.XYZ, flatCoordinates);
    return point;
  }
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.MultiPoint|undefined} MultiPoint.
 */
_ol_format_GMLBase_.prototype.readMultiPoint = function (node, objectStack) {
  /** @type {Array.<Array.<number>>} */
  var coordinates = _xml2.default.pushParseAndPop([], this.MULTIPOINT_PARSERS_, node, objectStack, this);
  if (coordinates) {
    return new _multipoint2.default(coordinates);
  } else {
    return undefined;
  }
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.MultiLineString|undefined} MultiLineString.
 */
_ol_format_GMLBase_.prototype.readMultiLineString = function (node, objectStack) {
  /** @type {Array.<ol.geom.LineString>} */
  var lineStrings = _xml2.default.pushParseAndPop([], this.MULTILINESTRING_PARSERS_, node, objectStack, this);
  if (lineStrings) {
    var multiLineString = new _multilinestring2.default(null);
    multiLineString.setLineStrings(lineStrings);
    return multiLineString;
  } else {
    return undefined;
  }
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
 */
_ol_format_GMLBase_.prototype.readMultiPolygon = function (node, objectStack) {
  /** @type {Array.<ol.geom.Polygon>} */
  var polygons = _xml2.default.pushParseAndPop([], this.MULTIPOLYGON_PARSERS_, node, objectStack, this);
  if (polygons) {
    var multiPolygon = new _multipolygon2.default(null);
    multiPolygon.setPolygons(polygons);
    return multiPolygon;
  } else {
    return undefined;
  }
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
_ol_format_GMLBase_.prototype.pointMemberParser_ = function (node, objectStack) {
  _xml2.default.parseNode(this.POINTMEMBER_PARSERS_, node, objectStack, this);
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
_ol_format_GMLBase_.prototype.lineStringMemberParser_ = function (node, objectStack) {
  _xml2.default.parseNode(this.LINESTRINGMEMBER_PARSERS_, node, objectStack, this);
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
_ol_format_GMLBase_.prototype.polygonMemberParser_ = function (node, objectStack) {
  _xml2.default.parseNode(this.POLYGONMEMBER_PARSERS_, node, objectStack, this);
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.LineString|undefined} LineString.
 */
_ol_format_GMLBase_.prototype.readLineString = function (node, objectStack) {
  var flatCoordinates = this.readFlatCoordinatesFromNode_(node, objectStack);
  if (flatCoordinates) {
    var lineString = new _linestring2.default(null);
    lineString.setFlatCoordinates(_geometrylayout2.default.XYZ, flatCoordinates);
    return lineString;
  } else {
    return undefined;
  }
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<number>|undefined} LinearRing flat coordinates.
 */
_ol_format_GMLBase_.prototype.readFlatLinearRing_ = function (node, objectStack) {
  var ring = _xml2.default.pushParseAndPop(null, this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack, this);
  if (ring) {
    return ring;
  } else {
    return undefined;
  }
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.LinearRing|undefined} LinearRing.
 */
_ol_format_GMLBase_.prototype.readLinearRing = function (node, objectStack) {
  var flatCoordinates = this.readFlatCoordinatesFromNode_(node, objectStack);
  if (flatCoordinates) {
    var ring = new _linearring2.default(null);
    ring.setFlatCoordinates(_geometrylayout2.default.XYZ, flatCoordinates);
    return ring;
  } else {
    return undefined;
  }
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.Polygon|undefined} Polygon.
 */
_ol_format_GMLBase_.prototype.readPolygon = function (node, objectStack) {
  /** @type {Array.<Array.<number>>} */
  var flatLinearRings = _xml2.default.pushParseAndPop([null], this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this);
  if (flatLinearRings && flatLinearRings[0]) {
    var polygon = new _polygon2.default(null);
    var flatCoordinates = flatLinearRings[0];
    var ends = [flatCoordinates.length];
    var i, ii;
    for (i = 1, ii = flatLinearRings.length; i < ii; ++i) {
      _array2.default.extend(flatCoordinates, flatLinearRings[i]);
      ends.push(flatCoordinates.length);
    }
    polygon.setFlatCoordinates(_geometrylayout2.default.XYZ, flatCoordinates, ends);
    return polygon;
  } else {
    return undefined;
  }
};

/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<number>} Flat coordinates.
 */
_ol_format_GMLBase_.prototype.readFlatCoordinatesFromNode_ = function (node, objectStack) {
  return _xml2.default.pushParseAndPop(null, this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack, this);
};

/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
_ol_format_GMLBase_.prototype.MULTIPOINT_PARSERS_ = {
  'http://www.opengis.net/gml': {
    'pointMember': _xml2.default.makeArrayPusher(_ol_format_GMLBase_.prototype.pointMemberParser_),
    'pointMembers': _xml2.default.makeArrayPusher(_ol_format_GMLBase_.prototype.pointMemberParser_)
  }
};

/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
_ol_format_GMLBase_.prototype.MULTILINESTRING_PARSERS_ = {
  'http://www.opengis.net/gml': {
    'lineStringMember': _xml2.default.makeArrayPusher(_ol_format_GMLBase_.prototype.lineStringMemberParser_),
    'lineStringMembers': _xml2.default.makeArrayPusher(_ol_format_GMLBase_.prototype.lineStringMemberParser_)
  }
};

/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
_ol_format_GMLBase_.prototype.MULTIPOLYGON_PARSERS_ = {
  'http://www.opengis.net/gml': {
    'polygonMember': _xml2.default.makeArrayPusher(_ol_format_GMLBase_.prototype.polygonMemberParser_),
    'polygonMembers': _xml2.default.makeArrayPusher(_ol_format_GMLBase_.prototype.polygonMemberParser_)
  }
};

/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
_ol_format_GMLBase_.prototype.POINTMEMBER_PARSERS_ = {
  'http://www.opengis.net/gml': {
    'Point': _xml2.default.makeArrayPusher(_ol_format_GMLBase_.prototype.readFlatCoordinatesFromNode_)
  }
};

/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
_ol_format_GMLBase_.prototype.LINESTRINGMEMBER_PARSERS_ = {
  'http://www.opengis.net/gml': {
    'LineString': _xml2.default.makeArrayPusher(_ol_format_GMLBase_.prototype.readLineString)
  }
};

/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
_ol_format_GMLBase_.prototype.POLYGONMEMBER_PARSERS_ = {
  'http://www.opengis.net/gml': {
    'Polygon': _xml2.default.makeArrayPusher(_ol_format_GMLBase_.prototype.readPolygon)
  }
};

/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @protected
 */
_ol_format_GMLBase_.prototype.RING_PARSERS = {
  'http://www.opengis.net/gml': {
    'LinearRing': _xml2.default.makeReplacer(_ol_format_GMLBase_.prototype.readFlatLinearRing_)
  }
};

/**
 * @inheritDoc
 */
_ol_format_GMLBase_.prototype.readGeometryFromNode = function (node, opt_options) {
  var geometry = this.readGeometryElement(node, [this.getReadOptions(node, opt_options ? opt_options : {})]);
  return geometry ? geometry : null;
};

/**
 * Read all features from a GML FeatureCollection.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Options.
 * @return {Array.<ol.Feature>} Features.
 * @api
 */
_ol_format_GMLBase_.prototype.readFeatures;

/**
 * @inheritDoc
 */
_ol_format_GMLBase_.prototype.readFeaturesFromNode = function (node, opt_options) {
  var options = {
    featureType: this.featureType,
    featureNS: this.featureNS
  };
  if (opt_options) {
    _obj2.default.assign(options, this.getReadOptions(node, opt_options));
  }
  var features = this.readFeaturesInternal(node, [options]);
  return features || [];
};

/**
 * @inheritDoc
 */
_ol_format_GMLBase_.prototype.readProjectionFromNode = function (node) {
  return _proj2.default.get(this.srsName ? this.srsName : node.firstElementChild.getAttribute('srsName'));
};
exports.default = _ol_format_GMLBase_;
module.exports = exports['default'];