goog.provide('ol.parser.ogc.GML_v3');

goog.require('goog.array');
goog.require('goog.object');
goog.require('ol.geom.GeometryType');
goog.require('ol.parser.ogc.GML');



/**
 * @constructor
 * @param {ol.parser.GMLOptions=} opt_options Optional configuration object.
 * @extends {ol.parser.ogc.GML}
 */
ol.parser.ogc.GML_v3 = function(opt_options) {
  this.schemaLocation = 'http://www.opengis.net/gml ' +
      'http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' +
      '1.0.0/gmlsf.xsd';
  goog.base(this, opt_options);
  this.featureNSWiters_['_geometry'] = function(geometry) {
    var node = this.createElementNS('feature:' + this.geometryName,
        this.featureNS);
    var type = geometry.getType(), child;
    if (type === ol.geom.GeometryType.POINT) {
      child = this.writeNode('Point', geometry, null, node);
    } else if (type === ol.geom.GeometryType.MULTIPOINT) {
      child = this.writeNode('MultiPoint', geometry, null, node);
    } else if (type === ol.geom.GeometryType.LINESTRING) {
      if (this.curve === true) {
        child = this.writeNode('Curve', geometry, null, node);
      } else {
        child = this.writeNode('LineString', geometry, null, node);
      }
    } else if (type === ol.geom.GeometryType.LINEARRING) {
      child = this.writeNode('LinearRing', geometry.getCoordinates(), null,
          node);
    } else if (type === ol.geom.GeometryType.MULTILINESTRING) {
      if (this.multiCurve === false) {
        child = this.writeNode('MultiLineString', geometry, null, node);
      } else {
        child = this.writeNode('MultiCurve', geometry, null, node);
      }
    } else if (type === ol.geom.GeometryType.POLYGON) {
      if (this.surface === true) {
        child = this.writeNode('Surface', geometry, null, node);
      } else {
        child = this.writeNode('Polygon', geometry, null, node);
      }
    } else if (type === ol.geom.GeometryType.MULTIPOLYGON) {
      if (this.multiSurface === false) {
        child = this.writeNode('MultiPolygon', geometry, null, node);
      } else {
        child = this.writeNode('MultiSurface', geometry, null, node);
      }
    } else if (type === ol.geom.GeometryType.GEOMETRYCOLLECTION) {
      child = this.writeNode('MultiGeometry', geometry, null, node);
    }
    if (goog.isDef(this.srsName)) {
      this.setAttributeNS(child, null, 'srsName', this.srsName);
    }
    return node;
  };
  goog.object.extend(this.readers['http://www.opengis.net/gml'], {
    '_inherit': function(node, obj, container) {
      // SRSReferenceGroup attributes
      var dim = parseInt(node.getAttribute('srsDimension'), 10) ||
          (container && container.srsDimension);
      if (dim) {
        obj.srsDimension = dim;
      }
    },
    'featureMembers': function(node, obj) {
      this.readChildNodes(node, obj);
    },
    'Curve': function(node, container) {
      var coordinates = [];
      this.readers[this.defaultNamespaceURI]['_inherit'].apply(this,
          [node, coordinates, container]);
      this.readChildNodes(node, coordinates);
      var linestring = {
        type: ol.geom.GeometryType.LINESTRING,
        coordinates: coordinates[0]
      };
      // in the case of a multi geometry this is parts
      if (goog.isArray(container)) {
        container.push(linestring);
      } else {
        container.geometry = linestring;
      }
    },
    'segments': function(node, obj) {
      this.readChildNodes(node, obj);
    },
    'LineStringSegment': function(node, container) {
      var coordinates = [];
      this.readChildNodes(node, coordinates);
      container.push(coordinates[0]);
    },
    'pos': function(node, obj) {
      var str = this.getChildValue(node).replace(
          this.regExes.trimSpace, '');
      var coords = goog.array.map(str.split(this.regExes.splitSpace),
          parseFloat);
      if (this.axisOrientation.substr(0, 2) === 'en') {
        obj.push([coords]);
      } else {
        if (coords.length === 2) {
          obj.push([coords.reverse()]);
        } else if (coords.length === 3) {
          obj.push([coords[1], coords[0], coords[2]]);
        }
      }
    },
    'posList': function(node, obj) {
      var str = this.getChildValue(node).replace(
          this.regExes.trimSpace, '');
      var coords = str.split(this.regExes.splitSpace);
      // The "dimension" attribute is from the GML 3.0.1 spec.
      var dim = obj.srsDimension ||
          parseInt(node.getAttribute('srsDimension') ||
          node.getAttribute('dimension'), 10) || 2;
      var x, y, z;
      var numPoints = coords.length / dim;
      var points = new Array(numPoints);
      for (var i = 0, ii = coords.length; i < ii; i += dim) {
        x = parseFloat(coords[i]);
        y = parseFloat(coords[i + 1]);
        var xy = this.axisOrientation.substr(0, 2) === 'en';
        if (dim === 3) {
          if (xy) {
            points[i / dim] = [x, y, parseFloat(coords[i + 2])];
          } else {
            points[i / dim] = [y, x, parseFloat(coords[i + 2])];
          }
        } else if (dim === 2) {
          if (xy) {
            points[i / dim] = [x, y];
          } else {
            points[i / dim] = [y, x];
          }
        }
      }
      obj.push(points);
    },
    'Surface': function(node, obj) {
      this.readChildNodes(node, obj);
    },
    'patches': function(node, obj) {
      this.readChildNodes(node, obj);
    },
    'PolygonPatch': function(node, obj) {
      this.readers[this.defaultNamespaceURI]['Polygon'].apply(this,
          [node, obj]);
    },
    'exterior': function(node, container) {
      var coordinates = [];
      this.readChildNodes(node, coordinates);
      container.outer = coordinates[0][0];
    },
    'interior': function(node, container) {
      var coordinates = [];
      this.readChildNodes(node, coordinates);
      container.inner.push(coordinates[0][0]);
    },
    'MultiCurve': function(node, container) {
      var parts = [];
      this.readers[this.defaultNamespaceURI]['_inherit'].apply(this,
          [node, parts, container]);
      this.readChildNodes(node, parts);
      container.geometry = {
        type: ol.geom.GeometryType.MULTILINESTRING,
        parts: parts
      };
    },
    'curveMember': function(node, obj) {
      this.readChildNodes(node, obj);
    },
    'MultiSurface': function(node, container) {
      var parts = [];
      this.readers[this.defaultNamespaceURI]['_inherit'].apply(this,
          [node, parts, container]);
      this.readChildNodes(node, parts);
      container.geometry = {
        type: ol.geom.GeometryType.MULTIPOLYGON,
        parts: parts
      };
    },
    'surfaceMember': function(node, obj) {
      this.readChildNodes(node, obj);
    },
    'surfaceMembers': function(node, obj) {
      this.readChildNodes(node, obj);
    },
    'pointMembers': function(node, obj) {
      this.readChildNodes(node, obj);
    },
    'lineStringMembers': function(node, obj) {
      this.readChildNodes(node, obj);
    },
    'polygonMembers': function(node, obj) {
      this.readChildNodes(node, obj);
    },
    'geometryMembers': function(node, obj) {
      this.readChildNodes(node, obj);
    },
    'Envelope': function(node, container) {
      var coordinates = [];
      this.readChildNodes(node, coordinates);
      container.bounds = [coordinates[0][0][0][0], coordinates[1][0][0][0],
        coordinates[0][0][0][1], coordinates[1][0][0][1]];
    },
    'lowerCorner': function(node, envelope) {
      var coordinates = [];
      this.readers[this.defaultNamespaceURI]['pos'].apply(this,
          [node, coordinates]);
      envelope.push(coordinates);
    },
    'upperCorner': function(node, envelope) {
      var coordinates = [];
      this.readers[this.defaultNamespaceURI]['pos'].apply(this,
          [node, coordinates]);
      envelope.push(coordinates);
    }
  });
  goog.object.extend(this.writers['http://www.opengis.net/gml'], {
    'featureMembers': function(features) {
      var node = this.createElementNS('gml:featureMembers');
      for (var i = 0, ii = features.length; i < ii; ++i) {
        this.writeNode('_typeName', features[i], this.featureNS, node);
      }
      return node;
    },
    'Point': function(geometry) {
      var node = this.createElementNS('gml:Point');
      this.writeNode('pos', geometry.getCoordinates(), null, node);
      return node;
    },
    'pos': function(point) {
      // only 2d for simple features profile
      var pos;
      if (this.axisOrientation.substr(0, 2) === 'en') {
        pos = (point[0] + ' ' + point[1]);
      } else {
        pos = (point[1] + ' ' + point[0]);
      }
      var node = this.createElementNS('gml:pos');
      node.appendChild(this.createTextNode(pos));
      return node;
    },
    'LineString': function(geometry) {
      var node = this.createElementNS('gml:LineString');
      this.writeNode('posList', geometry.getCoordinates(), null, node);
      return node;
    },
    'Curve': function(geometry) {
      var node = this.createElementNS('gml:Curve');
      this.writeNode('segments', geometry, null, node);
      return node;
    },
    'segments': function(geometry) {
      var node = this.createElementNS('gml:segments');
      this.writeNode('LineStringSegment', geometry, null, node);
      return node;
    },
    'LineStringSegment': function(geometry) {
      var node = this.createElementNS('gml:LineStringSegment');
      this.writeNode('posList', geometry.getCoordinates(), null, node);
      return node;
    },
    'posList': function(points) {
      // only 2d for simple features profile
      var len = points.length;
      var parts = new Array(len);
      var point;
      for (var i = 0; i < len; ++i) {
        point = points[i];
        if (this.axisOrientation.substr(0, 2) === 'en') {
          parts[i] = point[0] + ' ' + point[1];
        } else {
          parts[i] = point[1] + ' ' + point[0];
        }
      }
      var node = this.createElementNS('gml:posList');
      node.appendChild(this.createTextNode(parts.join(' ')));
      return node;
    },
    'Surface': function(geometry) {
      var node = this.createElementNS('gml:Surface');
      this.writeNode('patches', geometry, null, node);
      return node;
    },
    'patches': function(geometry) {
      var node = this.createElementNS('gml:patches');
      this.writeNode('PolygonPatch', geometry, null, node);
      return node;
    },
    'PolygonPatch': function(geometry) {
      var node = this.createElementNS('gml:PolygonPatch');
      node.setAttribute('interpolation', 'planar');
      var coordinates = geometry.getCoordinates();
      this.writeNode('exterior', coordinates[0], null, node);
      for (var i = 1, len = coordinates.length; i < len; ++i) {
        this.writeNode('interior', coordinates[i], null, node);
      }
      return node;
    },
    'Polygon': function(geometry) {
      var node = this.createElementNS('gml:Polygon');
      var coordinates = geometry.getCoordinates();
      this.writeNode('exterior', coordinates[0], null, node);
      for (var i = 1, len = coordinates.length; i < len; ++i) {
        this.writeNode('interior', coordinates[i], null, node);
      }
      return node;
    },
    'exterior': function(ring) {
      var node = this.createElementNS('gml:exterior');
      this.writeNode('LinearRing', ring, null, node);
      return node;
    },
    'interior': function(ring) {
      var node = this.createElementNS('gml:interior');
      this.writeNode('LinearRing', ring, null, node);
      return node;
    },
    'LinearRing': function(ring) {
      var node = this.createElementNS('gml:LinearRing');
      this.writeNode('posList', ring, null, node);
      return node;
    },
    'MultiCurve': function(geometry) {
      var node = this.createElementNS('gml:MultiCurve');
      for (var i = 0, len = geometry.components.length; i < len; ++i) {
        this.writeNode('curveMember', geometry.components[i], null, node);
      }
      return node;
    },
    'curveMember': function(geometry) {
      var node = this.createElementNS('gml:curveMember');
      if (this.curve) {
        this.writeNode('Curve', geometry, null, node);
      } else {
        this.writeNode('LineString', geometry, null, node);
      }
      return node;
    },
    'MultiSurface': function(geometry) {
      var node = this.createElementNS('gml:MultiSurface');
      for (var i = 0, len = geometry.components.length; i < len; ++i) {
        this.writeNode('surfaceMember', geometry.components[i], null, node);
      }
      return node;
    },
    'surfaceMember': function(polygon) {
      var node = this.createElementNS('gml:surfaceMember');
      if (this.surface) {
        this.writeNode('Surface', polygon, null, node);
      } else {
        this.writeNode('Polygon', polygon, null, node);
      }
      return node;
    },
    'Envelope': function(bounds) {
      var node = this.createElementNS('gml:Envelope');
      this.writeNode('lowerCorner', bounds, null, node);
      this.writeNode('upperCorner', bounds, null, node);
      // srsName attribute is required for gml:Envelope
      if (this.srsName) {
        node.setAttribute('srsName', this.srsName);
      }
      return node;
    },
    'lowerCorner': function(bounds) {
      // only 2d for simple features profile
      var pos;
      if (this.axisOrientation.substr(0, 2) === 'en') {
        pos = (bounds.left + ' ' + bounds.bottom);
      } else {
        pos = (bounds.bottom + ' ' + bounds.left);
      }
      var node = this.createElementNS('gml:lowerCorner');
      node.appendChild(this.createTextNode(pos));
      return node;
    },
    'upperCorner': function(bounds) {
      // only 2d for simple features profile
      var pos;
      if (this.axisOrientation.substr(0, 2) === 'en') {
        pos = (bounds.right + ' ' + bounds.top);
      } else {
        pos = (bounds.top + ' ' + bounds.right);
      }
      var node = this.createElementNS('gml:upperCorner');
      node.appendChild(this.createTextNode(pos));
      return node;
    }
  });
};
goog.inherits(ol.parser.ogc.GML_v3, ol.parser.ogc.GML);


/**
 * @param {Object} obj Object structure to write out as XML.
 * @return {string} An string representing the XML document.
 */
ol.parser.ogc.GML_v3.prototype.write = function(obj) {
  var root = this.writeNode('featureMembers', obj.features);
  this.setAttributeNS(
      root, 'http://www.w3.org/2001/XMLSchema-instance',
      'xsi:schemaLocation', this.schemaLocation);
  return this.serialize(root);
};
