import { util as zrUtil, graphic, matrix } from 'echarts/core';
import { DomUtil, LatLng, Layer, Map as LMap, Projection } from 'leaflet';
import 'proj4';
import projleaflet from 'proj4leaflet';

function dataToCoordSize(dataSize, dataItem) {
	dataItem = dataItem || [0, 0];
	return zrUtil.map(
		[0, 1],
		function (dimIdx) {
			const val = dataItem[dimIdx];
			const halfSize = dataSize[dimIdx] / 2;
			const p1 = [];
			const p2 = [];
			p1[dimIdx] = val - halfSize;
			p2[dimIdx] = val + halfSize;
			p1[1 - dimIdx] = p2[1 - dimIdx] = dataItem[1 - dimIdx];
			return Math.abs(this.dataToPoint(p1)[dimIdx] - this.dataToPoint(p2)[dimIdx]);
		},
		this
	);
}

// exclude private and unsupported options
const excludedOptions = ['echartsLayerInteractive', 'renderOnMoving', 'largeMode', 'layers', 'getCrs'];

const CustomOverlay = Layer.extend({
	initialize: function (container) {
		this._container = container;
	},

	onAdd: function (map) {
		let 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 (map) {
		DomUtil.remove(this._container);
	},

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

function LeafletCoordSys(lmap, api) {
	this._lmap = lmap;
	this._api = api;
	this._mapOffset = [0, 0];
	this._projection = Projection.Mercator;
	// this.dimensions = ['lat', 'lng'] // Is Leaflet default, but incompatible with echarts heatmap since isGeoCoordSys in HeatMapView.ts, checks for [0] === 'lng', [1] === 'lat'
}

const LeafletCoordSysProto = LeafletCoordSys.prototype;

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

LeafletCoordSysProto.setCenter = function (center) {
	const latlng = this._projection.project(new LatLng(center[1], center[0])); // lng, lat
	this._center = [latlng.lng, latlng.lat];
};

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

LeafletCoordSysProto.setLeaflet = function (lmap) {
	this._lmap = lmap;
};

LeafletCoordSysProto.getLeaflet = function () {
	return this._lmap;
};

LeafletCoordSysProto.dataToPoint = function (data) {
	const latlng = new LatLng(data[1], data[0]); // lng, lat
	const px = this._lmap.latLngToLayerPoint(latlng);
	const mapOffset = this._mapOffset;
	return [px.x - mapOffset[0], px.y - mapOffset[1]];
};

LeafletCoordSysProto.pointToData = function (pt) {
	const mapOffset = this._mapOffset;
	const coord = this._lmap.layerPointToLatLng({
		x: pt[0] + mapOffset[0],
		y: pt[1] + mapOffset[1],
	});
	return [coord.lng, coord.lat]; // lng, lat
};

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

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

LeafletCoordSysProto.prepareCustoms = function () {
	const rect = this.getViewRect();
	return {
		coordSys: {
			// The name exposed to user is always 'cartesian2d' but not 'grid'.
			type: 'lmap',
			x: rect.x,
			y: rect.y,
			width: rect.width,
			height: rect.height,
		},
		api: {
			coord: zrUtil.bind(this.dataToPoint, this),
			size: zrUtil.bind(dataToCoordSize, this),
		},
	};
};

LeafletCoordSysProto.convertToPixel = function (ecModel, finder, value) {
	// here we don't use finder as only one amap component is allowed
	return this.dataToPoint(value);
};

LeafletCoordSysProto.convertFromPixel = function (ecModel, finder, value) {
	// here we don't use finder as only one amap component is allowed
	return this.pointToData(value);
};

LeafletCoordSys.create = function (ecModel, api) {
	let lmapCoordSys;

	ecModel.eachComponent('lmap', function (lmapModel) {
		if (typeof L === 'undefined') {
			throw new Error('Leaflet api is not loaded');
		}
		if (lmapCoordSys) {
			throw new Error('Only one lmap echarts component is allowed');
		}

		let lmap = lmapModel.getLeaflet();
		const echartsLayerInteractive = lmapModel.get('echartsLayerInteractive');
		if (!lmap) {
			const root = api.getDom();
			const painter = api.getZr().painter;
			let viewportRoot = painter.getViewportRoot();
			viewportRoot.className = 'lmap-ec-layer';
			// Not support IE8
			let lmapRoot = root.querySelector('.ec-extension-leaflet');
			if (lmapRoot) {
				// Reset viewport left and top, which will be changed
				// in moving handler in Leaflet View
				viewportRoot.style.left = '0px';
				viewportRoot.style.top = '0px';

				root.removeChild(lmapRoot);
			}
			lmapRoot = document.createElement('div');
			lmapRoot.className = 'ec-extension-leaflet';
			lmapRoot.style.cssText = 'position:absolute;top:0;left:0;bottom:0;right:0;';
			root.appendChild(lmapRoot);

			const options = zrUtil.clone(lmapModel.get());

			const crs = options.getCrs && options.getCrs();

			// delete excluded options
			zrUtil.each(excludedOptions, function (key) {
				delete options[key];
			});
			if (crs) {
				lmap = new LMap(lmapRoot, { ...options, crs });
			} else {
				lmap = new LMap(lmapRoot, { ...options });
			}

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

			new CustomOverlay(moveContainer).addTo(lmap);

			lmapModel.setLeaflet(lmap);
			lmapModel.setEChartsLayer(viewportRoot);
		}

		const oldEChartsLayerInteractive = lmapModel.__echartsLayerInteractive;
		if (oldEChartsLayerInteractive !== echartsLayerInteractive) {
			lmapModel.setEChartsLayerInteractive(echartsLayerInteractive);
			lmapModel.__echartsLayerInteractive = echartsLayerInteractive;
		}

		const center = lmapModel.get('center');
		const zoom = lmapModel.get('zoom');
		if (center && zoom) {
			const lmapCenter = lmap.getCenter(); // leaflet lat lng
			const lmapZoom = lmap.getZoom();
			const centerOrZoomChanged = lmapModel.centerOrZoomChanged(
				[lmapCenter.lng, lmapCenter.lat], // lng, lat
				lmapZoom
			);
			if (centerOrZoomChanged) {
				lmap.setView(new LatLng(center[1], center[0]), zoom); // lng, lat
			}
		}

		lmapCoordSys = new LeafletCoordSys(lmap, api);
		lmapCoordSys.setMapOffset(lmapModel.__mapOffset || [0, 0]);
		lmapCoordSys.setZoom(zoom);
		lmapCoordSys.setCenter(center);

		lmapModel.coordinateSystem = lmapCoordSys;
	});

	ecModel.eachSeries(function (seriesModel) {
		if (seriesModel.get('coordinateSystem') === 'lmap') {
			seriesModel.coordinateSystem = lmapCoordSys;
		}
	});

	return lmapCoordSys && [lmapCoordSys];
};

LeafletCoordSysProto.dimensions = LeafletCoordSys.dimensions = ['lng', 'lat']; // lng, lat

LeafletCoordSysProto.type = 'lmap';

export default LeafletCoordSys;
