import { Injectable } from '@angular/core';

// Интерфейсы
import {
	IDelta,
	IOffset
} from '../interfaces';

// Сервис вспомогательных функций
@Injectable()
export class CrmHelperService {

	// Фиксирование числа в диапазоне
	public static clamp(
		value: number,
		min: number = 0,
		max: number = Number.POSITIVE_INFINITY): number {
		return Math.min(Math.max(value, min), max);
	}

	// Расстояние между двумя точками
	public static distance(a: IOffset, b: IOffset): number {
		const DOUBLE = 2;

		return Math.sqrt(
			Math.pow(a.left - b.left, DOUBLE) +
			Math.pow(a.top - b.top, DOUBLE)
		);
	}

	/**
	 * Получить путь из Node по событию
	 * @param event - событие
	 */
	public static eventPath(event: MouseEvent | any): Node[] {
		const target = event.target || event.srcElement || event.currentTarget;
		const composedPath = event.composedPath && event.composedPath();
		const path = composedPath || event.path;

		if (path) {
			// Safari fix
			return (path.indexOf(window) === -1)
				? path.concat(window)
				: path;
		}

		const getParents = (node: Node, memo: Node[] = []): Node[] => (
			node.parentNode
				? getParents(node.parentNode, memo.concat(node.parentNode))
				: memo
			);

		return [target].concat(getParents(target), window);
	}

	/**
	 * Проверить принадлежность element к parent в иерархии
	 * @param parent - элемент родителя
	 * @param element - проверяемый элемент
	 */
	public static hasParent(parent: any, element: any): boolean {
		let el = element;
		while (el) {
			if (el === parent) {
				return true;
			}
			el = el.parentElement;
		}

		return false;
	}

	/**
	 * Расчет смещения элемента element от родительского элемента parent
	 * @param parent - элемент родителя
	 * @param element - проверяемый элемент
	 * Возвращает объект содержащий значения смещения left, top
	 * Учитывает смещения при помощи transform: translate
	 */
	public static getOffsetElement(parent: any, element: any): IOffset {
		let el: any = null;
		const offset: IOffset = {
			left: 0,
			top: 0
		};

		const hasParent = CrmHelperService.hasParent(parent, element);
		if (element !== parent && hasParent) {
			// Первый проход по иерархии (сбор смещений offsetLeft, offsetTop)
			el = element;
			while (el && el !== parent) {
				offset.left += el.offsetLeft;
				offset.top += el.offsetTop;
				el = el.offsetParent;
			}
			// Второй проход по иерархии (сбор смещений transform)
			el = element;
			const INDEX_MATRIX = 1;
			const INDEX_TX = 4;
			const INDEX_TY = 5;
			while (el && el !== parent) {
				const style = window.getComputedStyle(el);
				const mat = style.transform.match(/^matrix\((.+)\)$/);
				if (mat) {
					const matrix = mat[INDEX_MATRIX].split(', ');
					const tx = parseFloat(matrix[INDEX_TX]);
					const ty = parseFloat(matrix[INDEX_TY]);
					if (!isNaN(tx)) { offset.left += tx; }
					if (!isNaN(ty)) { offset.top += ty; }
				}
				el = el.parentElement;
			}
		}

		return offset;
	}

	// Получить дельту из события прокрутки
	public static getDeltaFromWheelEvent(event: any): IDelta {
		const delta: IDelta = {
			x: 0,
			y: 0
		};
		const e = event || window.event;
		const wheelDelta = -e.wheelDelta || e.detail;
		if (e.shiftKey === true) {
			delta.x = CrmHelperService.clamp(wheelDelta, -1, 1);
		} else {
			delta.y = CrmHelperService.clamp(wheelDelta, -1, 1);
		}

		return delta;
	}
}
