'use strict';

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

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

var _index2 = _interopRequireDefault(_index);

var _asserts = require('../asserts.js');

var _asserts2 = _interopRequireDefault(_asserts);

var _pbf = require('pbf');

var _pbf2 = _interopRequireDefault(_pbf);

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

var _feature2 = _interopRequireDefault(_feature);

var _formattype = require('../format/formattype.js');

var _formattype2 = _interopRequireDefault(_formattype);

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

var _geometrylayout2 = _interopRequireDefault(_geometrylayout);

var _geometrytype = require('../geom/geometrytype.js');

var _geometrytype2 = _interopRequireDefault(_geometrytype);

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

var _linestring2 = _interopRequireDefault(_linestring);

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 _orient = require('../geom/flat/orient.js');

var _orient2 = _interopRequireDefault(_orient);

var _projection = require('../proj/projection.js');

var _projection2 = _interopRequireDefault(_projection);

var _units = require('../proj/units.js');

var _units2 = _interopRequireDefault(_units);

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

var _feature4 = _interopRequireDefault(_feature3);

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

/**
 * @classdesc
 * Feature format for reading data in the Mapbox MVT format.
 *
 * @constructor
 * @extends {ol.format.Feature}
 * @param {olx.format.MVTOptions=} opt_options Options.
 * @api
 */
var _ol_format_MVT_ = function (opt_options) {

  _feature2.default.call(this);

  var options = opt_options ? opt_options : {};

  /**
   * @type {ol.proj.Projection}
   */
  this.defaultDataProjection = new _projection2.default({
    code: 'EPSG:3857',
    units: _units2.default.TILE_PIXELS
  });

  /**
   * @private
   * @type {function((ol.geom.Geometry|Object.<string,*>)=)|
   *     function(ol.geom.GeometryType,Array.<number>,
   *         (Array.<number>|Array.<Array.<number>>),Object.<string,*>,number)}
   */
  this.featureClass_ = options.featureClass ? options.featureClass : _feature4.default;

  /**
   * @private
   * @type {string|undefined}
   */
  this.geometryName_ = options.geometryName;

  /**
   * @private
   * @type {string}
   */
  this.layerName_ = options.layerName ? options.layerName : 'layer';

  /**
   * @private
   * @type {Array.<string>}
   */
  this.layers_ = options.layers ? options.layers : null;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.extent_ = null;
}; //FIXME Implement projection handling

_index2.default.inherits(_ol_format_MVT_, _feature2.default);

/**
 * Reader callbacks for parsing the PBF.
 * @type {Object.<string, function(number, Object, ol.ext.PBF)>}
 */
_ol_format_MVT_.pbfReaders_ = {
  layers: function (tag, layers, pbf) {
    if (tag === 3) {
      var layer = {
        keys: [],
        values: [],
        features: []
      };
      var end = pbf.readVarint() + pbf.pos;
      pbf.readFields(_ol_format_MVT_.pbfReaders_.layer, layer, end);
      layer.length = layer.features.length;
      if (layer.length) {
        layers[layer.name] = layer;
      }
    }
  },
  layer: function (tag, layer, pbf) {
    if (tag === 15) {
      layer.version = pbf.readVarint();
    } else if (tag === 1) {
      layer.name = pbf.readString();
    } else if (tag === 5) {
      layer.extent = pbf.readVarint();
    } else if (tag === 2) {
      layer.features.push(pbf.pos);
    } else if (tag === 3) {
      layer.keys.push(pbf.readString());
    } else if (tag === 4) {
      var value = null;
      var end = pbf.readVarint() + pbf.pos;
      while (pbf.pos < end) {
        tag = pbf.readVarint() >> 3;
        value = tag === 1 ? pbf.readString() : tag === 2 ? pbf.readFloat() : tag === 3 ? pbf.readDouble() : tag === 4 ? pbf.readVarint64() : tag === 5 ? pbf.readVarint() : tag === 6 ? pbf.readSVarint() : tag === 7 ? pbf.readBoolean() : null;
      }
      layer.values.push(value);
    }
  },
  feature: function (tag, feature, pbf) {
    if (tag == 1) {
      feature.id = pbf.readVarint();
    } else if (tag == 2) {
      var end = pbf.readVarint() + pbf.pos;
      while (pbf.pos < end) {
        var key = feature.layer.keys[pbf.readVarint()];
        var value = feature.layer.values[pbf.readVarint()];
        feature.properties[key] = value;
      }
    } else if (tag == 3) {
      feature.type = pbf.readVarint();
    } else if (tag == 4) {
      feature.geometry = pbf.pos;
    }
  }
};

/**
 * Read a raw feature from the pbf offset stored at index `i` in the raw layer.
 * @suppress {missingProperties}
 * @private
 * @param {ol.ext.PBF} pbf PBF.
 * @param {Object} layer Raw layer.
 * @param {number} i Index of the feature in the raw layer's `features` array.
 * @return {Object} Raw feature.
 */
_ol_format_MVT_.readRawFeature_ = function (pbf, layer, i) {
  pbf.pos = layer.features[i];
  var end = pbf.readVarint() + pbf.pos;

  var feature = {
    layer: layer,
    type: 0,
    properties: {}
  };
  pbf.readFields(_ol_format_MVT_.pbfReaders_.feature, feature, end);
  return feature;
};

/**
 * Read the raw geometry from the pbf offset stored in a raw feature's geometry
 * proeprty.
 * @suppress {missingProperties}
 * @private
 * @param {ol.ext.PBF} pbf PBF.
 * @param {Object} feature Raw feature.
 * @param {Array.<number>} flatCoordinates Array to store flat coordinates in.
 * @param {Array.<number>} ends Array to store ends in.
 */
_ol_format_MVT_.readRawGeometry_ = function (pbf, feature, flatCoordinates, ends) {
  pbf.pos = feature.geometry;

  var end = pbf.readVarint() + pbf.pos;
  var cmd = 1;
  var length = 0;
  var x = 0;
  var y = 0;
  var coordsLen = 0;
  var currentEnd = 0;

  while (pbf.pos < end) {
    if (!length) {
      var cmdLen = pbf.readVarint();
      cmd = cmdLen & 0x7;
      length = cmdLen >> 3;
    }

    length--;

    if (cmd === 1 || cmd === 2) {
      x += pbf.readSVarint();
      y += pbf.readSVarint();

      if (cmd === 1) {
        // moveTo
        if (coordsLen > currentEnd) {
          ends.push(coordsLen);
          currentEnd = coordsLen;
        }
      }

      flatCoordinates.push(x, y);
      coordsLen += 2;
    } else if (cmd === 7) {

      if (coordsLen > currentEnd) {
        // close polygon
        flatCoordinates.push(flatCoordinates[currentEnd], flatCoordinates[currentEnd + 1]);
        coordsLen += 2;
      }
    } else {
      _asserts2.default.assert(false, 59); // Invalid command found in the PBF
    }
  }

  if (coordsLen > currentEnd) {
    ends.push(coordsLen);
    currentEnd = coordsLen;
  }
};

/**
 * @suppress {missingProperties}
 * @private
 * @param {number} type The raw feature's geometry type
 * @param {number} numEnds Number of ends of the flat coordinates of the
 * geometry.
 * @return {ol.geom.GeometryType} The geometry type.
 */
_ol_format_MVT_.getGeometryType_ = function (type, numEnds) {
  /** @type {ol.geom.GeometryType} */
  var geometryType;
  if (type === 1) {
    geometryType = numEnds === 1 ? _geometrytype2.default.POINT : _geometrytype2.default.MULTI_POINT;
  } else if (type === 2) {
    geometryType = numEnds === 1 ? _geometrytype2.default.LINE_STRING : _geometrytype2.default.MULTI_LINE_STRING;
  } else if (type === 3) {
    geometryType = _geometrytype2.default.POLYGON;
    // MultiPolygon not relevant for rendering - winding order determines
    // outer rings of polygons.
  }
  return geometryType;
};

/**
 * @private
 * @param {ol.ext.PBF} pbf PBF
 * @param {Object} rawFeature Raw Mapbox feature.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.Feature|ol.render.Feature} Feature.
 */
_ol_format_MVT_.prototype.createFeature_ = function (pbf, rawFeature, opt_options) {
  var type = rawFeature.type;
  if (type === 0) {
    return null;
  }

  var feature;
  var id = rawFeature.id;
  var values = rawFeature.properties;
  values[this.layerName_] = rawFeature.layer.name;

  var flatCoordinates = [];
  var ends = [];
  _ol_format_MVT_.readRawGeometry_(pbf, rawFeature, flatCoordinates, ends);

  var geometryType = _ol_format_MVT_.getGeometryType_(type, ends.length);

  if (this.featureClass_ === _feature4.default) {
    feature = new this.featureClass_(geometryType, flatCoordinates, ends, values, id);
  } else {
    var geom;
    if (geometryType == _geometrytype2.default.POLYGON) {
      var endss = [];
      var offset = 0;
      var prevEndIndex = 0;
      for (var i = 0, ii = ends.length; i < ii; ++i) {
        var end = ends[i];
        if (!_orient2.default.linearRingIsClockwise(flatCoordinates, offset, end, 2)) {
          endss.push(ends.slice(prevEndIndex, i));
          prevEndIndex = i;
        }
        offset = end;
      }
      if (endss.length > 1) {
        ends = endss;
        geom = new _multipolygon2.default(null);
      } else {
        geom = new _polygon2.default(null);
      }
    } else {
      geom = geometryType === _geometrytype2.default.POINT ? new _point2.default(null) : geometryType === _geometrytype2.default.LINE_STRING ? new _linestring2.default(null) : geometryType === _geometrytype2.default.POLYGON ? new _polygon2.default(null) : geometryType === _geometrytype2.default.MULTI_POINT ? new _multipoint2.default(null) : geometryType === _geometrytype2.default.MULTI_LINE_STRING ? new _multilinestring2.default(null) : null;
    }
    geom.setFlatCoordinates(_geometrylayout2.default.XY, flatCoordinates, ends);
    feature = new this.featureClass_();
    if (this.geometryName_) {
      feature.setGeometryName(this.geometryName_);
    }
    var geometry = _feature2.default.transformWithOptions(geom, false, this.adaptOptions(opt_options));
    feature.setGeometry(geometry);
    feature.setId(id);
    feature.setProperties(values);
  }

  return feature;
};

/**
 * @inheritDoc
 * @api
 */
_ol_format_MVT_.prototype.getLastExtent = function () {
  return this.extent_;
};

/**
 * @inheritDoc
 */
_ol_format_MVT_.prototype.getType = function () {
  return _formattype2.default.ARRAY_BUFFER;
};

/**
 * @inheritDoc
 * @api
 */
_ol_format_MVT_.prototype.readFeatures = function (source, opt_options) {
  var layers = this.layers_;

  var pbf = new _pbf2.default( /** @type {ArrayBuffer} */source);
  var pbfLayers = pbf.readFields(_ol_format_MVT_.pbfReaders_.layers, {});
  /** @type {Array.<ol.Feature|ol.render.Feature>} */
  var features = [];
  var pbfLayer;
  for (var name in pbfLayers) {
    if (layers && layers.indexOf(name) == -1) {
      continue;
    }
    pbfLayer = pbfLayers[name];

    var rawFeature;
    for (var i = 0, ii = pbfLayer.length; i < ii; ++i) {
      rawFeature = _ol_format_MVT_.readRawFeature_(pbf, pbfLayer, i);
      features.push(this.createFeature_(pbf, rawFeature));
    }
    this.extent_ = pbfLayer ? [0, 0, pbfLayer.extent, pbfLayer.extent] : null;
  }

  return features;
};

/**
 * @inheritDoc
 * @api
 */
_ol_format_MVT_.prototype.readProjection = function (source) {
  return this.defaultDataProjection;
};

/**
 * Sets the layers that features will be read from.
 * @param {Array.<string>} layers Layers.
 * @api
 */
_ol_format_MVT_.prototype.setLayers = function (layers) {
  this.layers_ = layers;
};

/**
 * Not implemented.
 * @override
 */
_ol_format_MVT_.prototype.readFeature = function () {};

/**
 * Not implemented.
 * @override
 */
_ol_format_MVT_.prototype.readGeometry = function () {};

/**
 * Not implemented.
 * @override
 */
_ol_format_MVT_.prototype.writeFeature = function () {};

/**
 * Not implemented.
 * @override
 */
_ol_format_MVT_.prototype.writeGeometry = function () {};

/**
 * Not implemented.
 * @override
 */
_ol_format_MVT_.prototype.writeFeatures = function () {};
exports.default = _ol_format_MVT_;
module.exports = exports['default'];