import {
	nextTick
} from '../../shared/utils.js';

export default function Controller({
	swiper,
	extendParams,
	on
}) {
	extendParams({
		controller: {
			control: undefined,
			inverse: false,
			by: 'slide', // or 'container'
		},
	});

	swiper.controller = {
		control: undefined,
	};

	function LinearSpline(x, y) {
		const binarySearch = (function search() {
			let maxIndex;
			let minIndex;
			let guess;
			return (array, val) => {
				minIndex = -1;
				maxIndex = array.length;
				while (maxIndex - minIndex > 1) {
					guess = (maxIndex + minIndex) >> 1;
					if (array[guess] <= val) {
						minIndex = guess;
					} else {
						maxIndex = guess;
					}
				}
				return maxIndex;
			};
		})();
		this.x = x;
		this.y = y;
		this.lastIndex = x.length - 1;
		let i1;
		let i3;

		this.interpolate = function interpolate(x2) {
			if (!x2) return 0;

			i3 = binarySearch(this.x, x2);
			i1 = i3 - 1;

			return (
				((x2 - this.x[i1]) * (this.y[i3] - this.y[i1])) / (this.x[i3] - this.x[i1]) + this.y[i1]
			);
		};
		return this;
	}

	function getInterpolateFunction(c) {
		swiper.controller.spline = swiper.params.loop ?
			new LinearSpline(swiper.slidesGrid, c.slidesGrid) :
			new LinearSpline(swiper.snapGrid, c.snapGrid);
	}

	function setTranslate(_t, byController) {
		const controlled = swiper.controller.control;
		let multiplier;
		let controlledTranslate;
		const Swiper = swiper.constructor;

		function setControlledTranslate(c) {
			if (c.destroyed) return;

			const translate = swiper.rtlTranslate ? -swiper.translate : swiper.translate;
			if (swiper.params.controller.by === 'slide') {
				getInterpolateFunction(c);

				controlledTranslate = -swiper.controller.spline.interpolate(-translate);
			}

			if (!controlledTranslate || swiper.params.controller.by === 'container') {
				multiplier =
					(c.maxTranslate() - c.minTranslate()) / (swiper.maxTranslate() - swiper.minTranslate());
				if (Number.isNaN(multiplier) || !Number.isFinite(multiplier)) {
					multiplier = 1;
				}
				controlledTranslate = (translate - swiper.minTranslate()) * multiplier + c.minTranslate();
			}

			if (swiper.params.controller.inverse) {
				controlledTranslate = c.maxTranslate() - controlledTranslate;
			}
			c.updateProgress(controlledTranslate);
			c.setTranslate(controlledTranslate, swiper);
			c.updateActiveIndex();
			c.updateSlidesClasses();
		}
		if (Array.isArray(controlled)) {
			for (let i = 0; i < controlled.length; i += 1) {
				if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
					setControlledTranslate(controlled[i]);
				}
			}
		} else if (controlled instanceof Swiper && byController !== controlled) {
			setControlledTranslate(controlled);
		}
	}

	function setTransition(duration, byController) {
		const Swiper = swiper.constructor;
		const controlled = swiper.controller.control;
		let i;

		function setControlledTransition(c) {
			if (c.destroyed) return;

			c.setTransition(duration, swiper);
			if (duration !== 0) {
				c.transitionStart();
				if (c.params.autoHeight) {
					nextTick(() => {
						c.updateAutoHeight();
					});
				}
			}
		}
		if (Array.isArray(controlled)) {
			for (i = 0; i < controlled.length; i += 1) {
				if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
					setControlledTransition(controlled[i]);
				}
			}
		} else if (controlled instanceof Swiper && byController !== controlled) {
			setControlledTransition(controlled);
		}
	}

	function removeSpline() {
		if (!swiper.controller.control) return;
		if (swiper.controller.spline) {
			swiper.controller.spline = undefined;
			delete swiper.controller.spline;
		}
	}
	on('beforeInit', () => {
		if (
			typeof window !== 'undefined' && // eslint-disable-line
			(typeof swiper.params.controller.control === 'string' ||
				swiper.params.controller.control instanceof HTMLElement)
		) {
			const controlElement = document.querySelector(swiper.params.controller.control);
			if (controlElement && controlElement.swiper) {
				swiper.controller.control = controlElement.swiper;
			} else if (controlElement) {
				const onControllerSwiper = (e) => {
					swiper.controller.control = e.detail[0];
					swiper.update();
					controlElement.removeEventListener('init', onControllerSwiper);
				};
				controlElement.addEventListener('init', onControllerSwiper);
			}
			return;
		}
		swiper.controller.control = swiper.params.controller.control;
	});
	on('update', () => {
		removeSpline();
	});
	on('resize', () => {
		removeSpline();
	});
	on('observerUpdate', () => {
		removeSpline();
	});
	on('setTranslate', (_s, translate, byController) => {
		if (!swiper.controller.control || swiper.controller.control.destroyed) return;
		swiper.controller.setTranslate(translate, byController);
	});
	on('setTransition', (_s, duration, byController) => {
		if (!swiper.controller.control || swiper.controller.control.destroyed) return;
		swiper.controller.setTransition(duration, byController);
	});

	Object.assign(swiper.controller, {
		setTranslate,
		setTransition,
	});
}