import echarts, { util, graphic, matrix } from 'echarts/lib/echarts';
import L$1 from 'leaflet/src/Leaflet';

// import  'leaflet'

// 这段代码来自于 https://github.com/Leaflet/Leaflet.TileLayer.NoGap/blob/master/L.TileLayer.NoGap.js
// 如果没有这段，则显示的地图的瓦片之间有空白线
// let leaflet = require('leaflet/src/Leaflet')
// require('leaflet/src/Leaflet')
// @class TileLayer
(function () {

	L.TileLayer.mergeOptions({
		// @option keepBuffer
		// The amount of tiles outside the visible map area to be kept in the stitched
		// `TileLayer`.

		// @option dumpToCanvas: Boolean = true
		// Whether to dump loaded tiles to a `<canvas>` to prevent some rendering
		// artifacts. (Disabled by default in IE)
		dumpToCanvas: L.Browser.canvas && !L.Browser.ie
	});

	L.TileLayer.include({
		_onUpdateLevel: function _onUpdateLevel(z, zoom) {
			if (this.options.dumpToCanvas) {
				this._levels[z].canvas.style.zIndex = this.options.maxZoom - Math.abs(zoom - z);
			}
		},

		_onRemoveLevel: function _onRemoveLevel(z) {
			if (this.options.dumpToCanvas) {
				L.DomUtil.remove(this._levels[z].canvas);
			}
		},

		_onCreateLevel: function _onCreateLevel(level) {
			if (this.options.dumpToCanvas) {
				level.canvas = L.DomUtil.create("canvas", "leaflet-tile-container leaflet-zoom-animated", this._container);
				level.ctx = level.canvas.getContext("2d");
				this._resetCanvasSize(level);
			}
		},

		_removeTile: function _removeTile(key) {
			if (this.options.dumpToCanvas) {
				var tile = this._tiles[key];
				var level = this._levels[tile.coords.z];
				var tileSize = this.getTileSize();

				if (level) {
					// Where in the canvas should this tile go?
					var offset = L.point(tile.coords.x, tile.coords.y).subtract(level.canvasRange.min).scaleBy(this.getTileSize());

					level.ctx.clearRect(offset.x, offset.y, tileSize.x, tileSize.y);
				}
			}

			L.GridLayer.prototype._removeTile.call(this, key);
		},

		_resetCanvasSize: function _resetCanvasSize(level) {
			var buff = this.options.keepBuffer,
			    pixelBounds = this._getTiledPixelBounds(this._map.getCenter()),
			    tileRange = this._pxBoundsToTileRange(pixelBounds),
			    tileSize = this.getTileSize();

			tileRange.min = tileRange.min.subtract([buff, buff]); // This adds the no-prune buffer
			tileRange.max = tileRange.max.add([buff + 1, buff + 1]);

			var pixelRange = L.bounds(tileRange.min.scaleBy(tileSize), tileRange.max.add([1, 1]).scaleBy(tileSize) // This prevents an off-by-one when checking if tiles are inside
			),
			    mustRepositionCanvas = false,
			    neededSize = pixelRange.max.subtract(pixelRange.min);

			// Resize the canvas, if needed, and only to make it bigger.
			if (neededSize.x > level.canvas.width || neededSize.y > level.canvas.height) {
				// Resizing canvases erases the currently drawn content, I'm afraid.
				// To keep it, dump the pixels to another canvas, then display it on
				// top. This could be done with getImageData/putImageData, but that
				// would break for tainted canvases (in non-CORS tilesets)
				var oldSize = { x: level.canvas.width, y: level.canvas.height };
				// console.info('Resizing canvas from ', oldSize, 'to ', neededSize);

				var tmpCanvas = L.DomUtil.create("canvas");
				tmpCanvas.style.width = (tmpCanvas.width = oldSize.x) + "px";
				tmpCanvas.style.height = (tmpCanvas.height = oldSize.y) + "px";
				tmpCanvas.getContext("2d").drawImage(level.canvas, 0, 0);
				// var data = level.ctx.getImageData(0, 0, oldSize.x, oldSize.y);

				level.canvas.style.width = (level.canvas.width = neededSize.x) + "px";
				level.canvas.style.height = (level.canvas.height = neededSize.y) + "px";
				level.ctx.drawImage(tmpCanvas, 0, 0);
				// level.ctx.putImageData(data, 0, 0, 0, 0, oldSize.x, oldSize.y);
			}

			// Translate the canvas contents if it's moved around
			if (level.canvasRange) {
				var offset = level.canvasRange.min.subtract(tileRange.min).scaleBy(this.getTileSize());

				// 			console.info('Offsetting by ', offset);

				if (!L.Browser.safari) {
					// By default, canvases copy things "on top of" existing pixels, but we want
					// this to *replace* the existing pixels when doing a drawImage() call.
					// This will also clear the sides, so no clearRect() calls are needed to make room
					// for the new tiles.
					level.ctx.globalCompositeOperation = "copy";
					level.ctx.drawImage(level.canvas, offset.x, offset.y);
					level.ctx.globalCompositeOperation = "source-over";
				} else {
					// Safari clears the canvas when copying from itself :-(
					if (!this._tmpCanvas) {
						var t = this._tmpCanvas = L.DomUtil.create("canvas");
						t.width = level.canvas.width;
						t.height = level.canvas.height;
						this._tmpContext = t.getContext("2d");
					}
					this._tmpContext.clearRect(0, 0, level.canvas.width, level.canvas.height);
					this._tmpContext.drawImage(level.canvas, 0, 0);
					level.ctx.clearRect(0, 0, level.canvas.width, level.canvas.height);
					level.ctx.drawImage(this._tmpCanvas, offset.x, offset.y);
				}

				mustRepositionCanvas = true; // Wait until new props are set
			}

			level.canvasRange = tileRange;
			level.canvasPxRange = pixelRange;
			level.canvasOrigin = pixelRange.min;

			// console.log('Canvas tile range: ', level, tileRange.min, tileRange.max );
			// console.log('Canvas pixel range: ', pixelRange.min, pixelRange.max );
			// console.log('Level origin: ', level.origin );

			if (mustRepositionCanvas) {
				this._setCanvasZoomTransform(level, this._map.getCenter(), this._map.getZoom());
			}
		},

		/// set transform/position of canvas, in addition to the transform/position of the individual tile container
		_setZoomTransform: function _setZoomTransform(level, center, zoom) {
			L.GridLayer.prototype._setZoomTransform.call(this, level, center, zoom);
			if (this.options.dumpToCanvas) {
				this._setCanvasZoomTransform(level, center, zoom);
			}
		},

		// This will get called twice:
		// * From _setZoomTransform
		// * When the canvas has shifted due to a new tile being loaded
		_setCanvasZoomTransform: function _setCanvasZoomTransform(level, center, zoom) {
			// console.log('_setCanvasZoomTransform', level, center, zoom);
			if (!level.canvasOrigin) {
				return;
			}
			var scale = this._map.getZoomScale(zoom, level.zoom),
			    translate = level.canvasOrigin.multiplyBy(scale).subtract(this._map._getNewPixelOrigin(center, zoom)).round();

			if (L.Browser.any3d) {
				L.DomUtil.setTransform(level.canvas, translate, scale);
			} else {
				L.DomUtil.setPosition(level.canvas, translate);
			}
		},

		_onOpaqueTile: function _onOpaqueTile(tile) {
			if (!this.options.dumpToCanvas) {
				return;
			}

			// Guard against an NS_ERROR_NOT_AVAILABLE (or similar) exception
			// when a non-image-tile has been loaded (e.g. a WMS error).
			// Checking for tile.el.complete is not enough, as it has been
			// already marked as loaded and ready somehow.
			try {
				this.dumpPixels(tile.coords, tile.el);
			} catch (ex) {
				return this.fire("tileerror", {
					error: "Could not copy tile pixels: " + ex,
					tile: tile,
					coods: tile.coords
				});
			}

			// If dumping the pixels was successful, then hide the tile.
			// Do not remove the tile itself, as it is needed to check if the whole
			// level (and its canvas) should be removed (via level.el.children.length)
			tile.el.style.display = "none";
		},

		// @section Extension methods
		// @uninheritable

		// @method dumpPixels(coords: Object, imageSource: CanvasImageSource): this
		// Dumps pixels from the given `CanvasImageSource` into the layer, into
		// the space for the tile represented by the `coords` tile coordinates (an object
		// like `{x: Number, y: Number, z: Number}`; the image source must have the
		// same size as the `tileSize` option for the layer. Has no effect if `dumpToCanvas`
		// is `false`.
		dumpPixels: function dumpPixels(coords, imageSource) {
			var level = this._levels[coords.z],
			    tileSize = this.getTileSize();

			if (!level.canvasRange || !this.options.dumpToCanvas) {
				return;
			}

			// Check if the tile is inside the currently visible map bounds
			// There is a possible race condition when tiles are loaded after they
			// have been panned outside of the map.
			if (!level.canvasRange.contains(coords)) {
				this._resetCanvasSize(level);
			}

			// Where in the canvas should this tile go?
			var offset = L.point(coords.x, coords.y).subtract(level.canvasRange.min).scaleBy(this.getTileSize());

			level.ctx.drawImage(imageSource, offset.x, offset.y, tileSize.x, tileSize.y);

			// TODO: Clear the pixels of other levels' canvases where they overlap
			// this newly dumped tile.
			return this;
		}
	});
})();

/**
 * constructor for Leaflet CoordSys
 * @param {L.map} map
 * @param {Object} api
 */
function LeafletCoordSys(map, api) {
  this._map = map;
  this.dimensions = ['lng', 'lat'];
  this._mapOffset = [0, 0];
  this._api = api;
  this._projection = L$1.Projection.Mercator;
}

LeafletCoordSys.prototype.dimensions = ['lng', 'lat'];

LeafletCoordSys.prototype.setZoom = function (zoom) {
  this._zoom = zoom;
};

LeafletCoordSys.prototype.setCenter = function (center) {
  this._center = this._projection.project(new L$1.LatLng(center[1], center[0]));
};

LeafletCoordSys.prototype.setMapOffset = function (mapOffset) {
  this._mapOffset = mapOffset;
};

LeafletCoordSys.prototype.getLeaflet = function () {
  return this._map;
};

LeafletCoordSys.prototype.dataToPoint = function (data) {
  var point = new L$1.LatLng(data[1], data[0]);
  var px = this._map.latLngToLayerPoint(point);
  var mapOffset = this._mapOffset;
  return [px.x - mapOffset[0], px.y - mapOffset[1]];
};

LeafletCoordSys.prototype.pointToData = function (pt) {
  var mapOffset = this._mapOffset;
  var coord = this._map.layerPointToLatLng({
    x: pt[0] + mapOffset[0],
    y: pt[1] + mapOffset[1]
  });
  return [coord.lng, coord.lat];
};

LeafletCoordSys.prototype.convertToPixel = util.curry(doConvert, 'dataToPoint');

LeafletCoordSys.prototype.convertFromPixel = util.curry(doConvert, 'pointToData');

/**
 * find appropriate coordinate system to convert
 * @param {*} methodName
 * @param {*} ecModel
 * @param {*} finder
 * @param {*} value
 * @return {*} converted value
 */
function doConvert(methodName, ecModel, finder, value) {
  var leafletModel = finder.leafletModel;
  var seriesModel = finder.seriesModel;

  var coordSys = leafletModel ? leafletModel.coordinateSystem : seriesModel ? seriesModel.coordinateSystem || // For map.
  (seriesModel.getReferringComponents('leaflet')[0] || {}).coordinateSystem : null;
  /* eslint-disable no-invalid-this */
  return coordSys === this ? coordSys[methodName](value) : null;
}

LeafletCoordSys.prototype.getViewRect = function () {
  var api = this._api;
  return new graphic.BoundingRect(0, 0, api.getWidth(), api.getHeight());
};

LeafletCoordSys.prototype.getRoamTransform = function () {
  return matrix.create();
};

LeafletCoordSys.dimensions = LeafletCoordSys.prototype.dimensions;

var CustomOverlay = L$1.Layer.extend({
  initialize: function initialize(container) {
    this._container = container;
  },

  onAdd: function onAdd(map) {
    var pane = map.getPane(this.options.pane);
    pane.appendChild(this._container);

    // Calculate initial position of container with
    // `L.Map.latLngToLayerPoint()`, `getPixelOrigin()
    // and/or `getPixelBounds()`

    // L.DomUtil.setPosition(this._container, point);

    // Add and position children elements if needed

    // map.on('zoomend viewreset', this._update, this);
  },

  onRemove: function onRemove(map) {
    L$1.DomUtil.remove(this._container);
    // map.off('zoomend viewreset', this._update, this);
  },

  _update: function _update() {
    // Recalculate position of container
    // L.DomUtil.setPosition(this._container, point);
    // Add/remove/reposition children elements if needed
  }
});

LeafletCoordSys.create = function (ecModel, api) {
  var leafletCoordSys = void 0;
  var leafletList = [];
  var root = api.getDom();

  // TODO Dispose
  ecModel.eachComponent('leaflet', function (leafletModel) {
    var viewportRoot = api.getZr().painter.getViewportRoot();
    if (typeof L$1 === 'undefined') {
      throw new Error('Leaflet api is not loaded');
    }
    if (leafletCoordSys) {
      throw new Error('Only one leaflet component can exist');
    }
    if (!leafletModel.__map) {
      // Not support IE8
      var mapRoot = root.querySelector('.ec-extension-leaflet');
      if (mapRoot) {
        // Reset viewport left and top, which will be changed
        // in moving handler in LeafletView
        viewportRoot.style.left = '0px';
        viewportRoot.style.top = '0px';
        root.removeChild(mapRoot);
      }
      mapRoot = document.createElement('div');
      mapRoot.style.cssText = 'width:100%;height:100%';
      // Not support IE8
      mapRoot.classList.add('ec-extension-leaflet');
      root.appendChild(mapRoot);
      var _map = leafletModel.__map = L$1.map(mapRoot);
      var tiles = leafletModel.get('tiles');
      var baseLayers = {};
      var baseLayerAdded = false;
      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;
      var _iteratorError = undefined;

      try {
        for (var _iterator = tiles[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
          var tile = _step.value;

          var tileLayer = L$1.tileLayer(tile.urlTemplate, tile.options);
          if (tile.label) {
            // only add one baseLayer
            if (!baseLayerAdded) {
              tileLayer.addTo(_map);
              baseLayerAdded = true;
            }
            baseLayers[tile.label] = tileLayer;
          } else {
            // add all tiles without labels into the map
            tileLayer.addTo(_map);
          }
        }
        // add layer control when there are more than two layers
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator.return) {
            _iterator.return();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }

      if (tiles.length > 1) {
        var layerControlOpts = leafletModel.get('layerControl');
        L$1.control.layers(baseLayers, {}, layerControlOpts).addTo(_map);
      }

      /*
       Encapsulate viewportRoot element into
       the parent element responsible for moving,
       avoiding direct manipulation of viewportRoot elements
       affecting related attributes such as offset.
      */
      var moveContainer = document.createElement('div');
      moveContainer.style = 'position: relative;';
      moveContainer.appendChild(viewportRoot);

      new CustomOverlay(moveContainer).addTo(_map);
    }
    var map = leafletModel.__map;

    // Set leaflet options
    // centerAndZoom before layout and render
    var center = leafletModel.get('center');
    var zoom = leafletModel.get('zoom');
    if (center && zoom) {
      map.setView([center[1], center[0]], zoom);
    }

    leafletCoordSys = new LeafletCoordSys(map, api);
    leafletList.push(leafletCoordSys);
    leafletCoordSys.setMapOffset(leafletModel.__mapOffset || [0, 0]);
    leafletCoordSys.setZoom(zoom);
    leafletCoordSys.setCenter(center);

    leafletModel.coordinateSystem = leafletCoordSys;
  });

  ecModel.eachSeries(function (seriesModel) {
    if (seriesModel.get('coordinateSystem') === 'leaflet') {
      seriesModel.coordinateSystem = leafletCoordSys;
    }
  });

  return leafletList;
};

/**
 * compare if two arrays of length 2 are equal
 * @param {Array} a array of length 2
 * @param {Array} b array of length 2
 * @return {Boolean}
 */
function v2Equal(a, b) {
  return a && b && a[0] === b[0] && a[1] === b[1];
}

echarts.extendComponentModel({
  type: 'leaflet',

  getLeaflet: function getLeaflet() {
    // __map is injected when creating LeafletCoordSys
    return this.__map;
  },

  setCenterAndZoom: function setCenterAndZoom(center, zoom) {
    this.option.center = center;
    this.option.zoom = zoom;
  },

  centerOrZoomChanged: function centerOrZoomChanged(center, zoom) {
    var option = this.option;
    return !(v2Equal(center, option.center) && zoom === option.zoom);
  },

  defaultOption: {
    center: [104.114129, 37.550339],
    zoom: 2,
    mapStyle: {},
    roam: false,
    layerControl: {},
    tiles: [{
      urlTemplate: 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
      options: {
        attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      }
    }]
  }
});

echarts.extendComponentView({
  type: 'leaflet',

  render: function render(leafletModel, ecModel, api) {
    var rendering = true;

    var leaflet = leafletModel.getLeaflet();
    var moveContainer = api.getZr().painter.getViewportRoot().parentNode;
    var coordSys = leafletModel.coordinateSystem;

    var moveHandler = function moveHandler(type, target) {
      if (rendering) {
        return;
      }
      var offsetEl = leaflet._mapPane;
      // calculate new mapOffset
      var transformStyle = offsetEl.style.transform;
      var dx = 0;
      var dy = 0;
      if (transformStyle) {
        transformStyle = transformStyle.replace('translate3d(', '');
        var parts = transformStyle.split(',');
        dx = -parseInt(parts[0], 10);
        dy = -parseInt(parts[1], 10);
      } else {
        // browsers that don't support transform: matrix
        dx = -parseInt(offsetEl.style.left, 10);
        dy = -parseInt(offsetEl.style.top, 10);
      }
      var mapOffset = [dx, dy];
      moveContainer.style.left = mapOffset[0] + 'px';
      moveContainer.style.top = mapOffset[1] + 'px';

      coordSys.setMapOffset(mapOffset);
      leafletModel.__mapOffset = mapOffset;

      api.dispatchAction({
        type: 'leafletRoam'
      });
    };

    /**
     * handler for map zoomEnd event
     */
    function zoomEndHandler() {
      if (rendering) return;
      api.dispatchAction({
        type: 'leafletRoam'
      });
    }

    /**
     * handler for map zoom event
     */
    function zoomHandler() {
      moveHandler();
    }

    if (this._oldMoveHandler) {
      leaflet.off('move', this._oldMoveHandler);
    }
    if (this._oldZoomHandler) {
      leaflet.off('zoom', this._oldZoomHandler);
    }
    if (this._oldZoomEndHandler) {
      leaflet.off('zoomend', this._oldZoomEndHandler);
    }

    leaflet.on('move', moveHandler);
    leaflet.on('zoom', zoomHandler);
    leaflet.on('zoomend', zoomEndHandler);

    this._oldMoveHandler = moveHandler;
    this._oldZoomHandler = zoomHandler;
    this._oldZoomEndHandler = zoomEndHandler;

    var roam = leafletModel.get('roam');
    // can move
    if (roam && roam !== 'scale') {
      leaflet.dragging.enable();
    } else {
      leaflet.dragging.disable();
    }
    // can zoom (may need to be more fine-grained)
    if (roam && roam !== 'move') {
      leaflet.scrollWheelZoom.enable();
      leaflet.doubleClickZoom.enable();
      leaflet.touchZoom.enable();
    } else {
      leaflet.scrollWheelZoom.disable();
      leaflet.doubleClickZoom.disable();
      leaflet.touchZoom.disable();
    }

    rendering = false;
  }
});

/**
 * Leftlet component extension
 */

echarts.registerCoordinateSystem('leaflet', LeafletCoordSys);

echarts.registerAction({
  type: 'leafletRoam',
  event: 'leafletRoam',
  update: 'updateLayout'
}, function (payload, ecModel) {
  ecModel.eachComponent('leaflet', function (leafletModel) {
    var leaflet = leafletModel.getLeaflet();
    var center = leaflet.getCenter();
    leafletModel.setCenterAndZoom([center.lng, center.lat], leaflet.getZoom());
  });
});

var version = '1.0.0';

export { version };
