import {
	Component,
	ElementRef,
	EventEmitter,
	HostBinding,
	HostListener,
	Input,
	OnChanges,
	OnDestroy,
	OnInit,
	Output,
	Renderer2,
	ViewChild
} from '@angular/core';

// Сенсор
import { ResizeSensor } from 'css-element-queries';

// Сервисы
import { CrmHelperService } from '../../services';

// Перечисления
import {
	SliderDirection,
	SliderType
} from '../../enums';

// Слайдер
@Component({
	selector: 'crm-slider',
	templateUrl: './crm-slider.component.html',
	styleUrls: ['./crm-slider.component.scss']
})
export class CrmSliderComponent
	implements OnInit, OnDestroy, OnChanges {

	// Конструктор
	constructor(private readonly renderer: Renderer2) {}

	// Тип слайдера
	@HostBinding('attr.slider-type')
	@Input() public type: SliderType = SliderType.Default;

	// Направление слайдера
	@Input() public direction: SliderDirection;

	// Размер области отображения
	@Input() public viewport: number = 0;

	// Размер области контента в области отображения
	@Input() public canvas: number = 0;

	// Смещение области контента в области отображения
	@Input() public offset: number = 0;

	// Событие перемещения каретки в слайдере
	@Output() public sliderMoved = new EventEmitter<number>();

	// Событие прокрутки в слайдере
	@Output() public sliderWheel = new EventEmitter<number>();

	// Событие прокрутки в слайдере на одну линию вверх
	@Output() public sliderLineUp = new EventEmitter();

	// Событие прокрутки в слайдере на одну линию вниз
	@Output() public sliderLineDown = new EventEmitter();

	// Событие прокрутки в слайдере на одну страницу вверх
	@Output() public sliderPageUp = new EventEmitter();

	// Событие прокрутки в слайдере на одну страницу вниз
	@Output() public sliderPageDown = new EventEmitter();

	// Ссылка на элемент слайдера в DOM
	@ViewChild('slider') public slider: ElementRef;

	// Ссылка на элемент каретки в DOM
	@ViewChild('thumb') public thumb: ElementRef;

	// Ссылка на обьект для touch событий
	private touchTarget: Element;

	// Сенсор изменения размера
	private resizeSensor: any;

	// Размер слайдера
	public sliderSize: number = 0;

	// Минимальный размер каретки
	public readonly MIN_THUMB_SIZE: number = 62;

	// Размер каретки
	public thumbSize: number = 0;

	// Позиция каретки
	public thumbPos: number = 0;

	// Коэффициент смещения от начала каретки в момент захвата
	public thumbPosRatio: number = 0;

	// Начальная позиция каретки в момент захвата
	public thumbPosStart: number = 0;

	// Нажата ли каретка
	public isThumbPressed: boolean = false;

	// Функции для отключения прослушивания событий
	private mouseMoveListen: () => void;
	private mouseUpListen: () => void;
	private touchMoveListen: () => void;
	private touchEndListen: () => void;

	// Является ли слайдер вертикальным?
	public get isVertical(): boolean {
		return this.direction === SliderDirection.Vertical;
	}

	// Стиль каретки
	public get thumbStyle(): any {
		return this.isVertical
			? {
				height: `${this.thumbSize}px`,
				transform: `translateY(${this.thumbPos}px)`
			}
			: {
				width: `${this.thumbSize}px`,
				transform: `translateX(${this.thumbPos}px)`
			};
	}

	// --------------------------------------------------------------------------
	// MOUSE
	// Обработчик прокрутки
	@HostListener('mousewheel', ['$event'])
	@HostListener('DOMMouseScroll', ['$event'])
	@HostListener('onmousewheel', ['$event'])
	public onMouseWheel(event: MouseEvent): void {
		if (!this.isThumbPressed) {
			const delta = CrmHelperService.getDeltaFromWheelEvent(event);
			this.sliderWheel.emit(
				this.isVertical
					? delta.y
					: delta.x
			);
		}
	}

	// Нажатие на каретку
	public onMouseDown(event: MouseEvent): void {
		this.isThumbPressed = true;
		this.mouseMoveListen = this.renderer.listen(
			document,
			'mousemove',
			this.onMouseMove.bind(this)
		);
		this.mouseUpListen = this.renderer.listen(
			document,
			'mouseup',
			this.onMouseUp.bind(this)
		);
		this.sliderPressed(event.clientX, event.clientY);
	}

	// Перемещение каретки
	public onMouseMove(event: MouseEvent): void {
		this.sliderMove(event.clientX, event.clientY);
	}

	// Отжатие каретки
	public onMouseUp(event: MouseEvent): void {
		this.isThumbPressed = false;
		this.removeEvents();
	}

	// --------------------------------------------------------------------------
	// TOUCH
	// Нажатие на каретку
	public onTouchStart(event: TouchEvent): void {
		const touch = event.touches[event.touches.length - 1];
		const touchTarget = (
			event.target ||
			event.srcElement ||
			event.currentTarget
		) as Element;

		if (touch) {
			this.touchTarget = touchTarget;
			this.isThumbPressed = true;
			this.touchMoveListen = this.renderer.listen(
				this.touchTarget,
				'touchmove',
				this.onTouchMove.bind(this)
			);
			this.touchEndListen = this.renderer.listen(
				this.touchTarget,
				'touchend',
				this.onTouchEnd.bind(this)
			);
			this.sliderPressed(touch.clientX, touch.clientY);
		}
	}

	// Перемещение каретки
	public onTouchMove(event: TouchEvent): void {
		const touch = event.touches[event.touches.length - 1];
		this.sliderMove(touch.clientX, touch.clientY);
	}

	// Отжатие каретки
	public onTouchEnd(event: TouchEvent): void {
		this.isThumbPressed = false;
		this.removeEvents();
		this.removeLinks();
	}

	// --------------------------------------------------------------------------
	// BUTTON
	// Событие прокрутки в слайдере на одну линию вверх
	public onLineUp(): void {
		this.sliderLineUp.emit();
	}

	// Событие прокрутки в слайдере на одну линию вниз
	public onLineDown(): void {
		this.sliderLineDown.emit();
	}

	// Событие прокрутки в слайдере на одну страницу вверх/вниз
	public onSliderClick(event: MouseEvent): void {
		if (this.thumb && this.thumb.nativeElement) {
			const { left, top } = CrmHelperService.getOffsetElement(
				document.body,
				this.thumb.nativeElement
			);
			const len = this.isVertical ? top : left;
			const point = this.isVertical ? event.clientY : event.clientX;
			const offset = point - len;
			if (offset < 0) {
				this.sliderPageUp.emit();
			} else if (offset > this.thumbSize) {
				this.sliderPageDown.emit();
			}
		}
	}

	// --------------------------------------------------------------------------
	// Обновить слайдер
	public update(): void {
		this.sliderSize = this.calcSliderSize();
		this.thumbSize = this.calcThumbSize();
		this.thumbPos = this.calcThumbPos();
	}

	// Расчет размера слайдера
	public calcSliderSize(): number {
		let size = 0;
		if (this.slider && this.slider.nativeElement) {
			size = this.isVertical
				? this.slider.nativeElement.offsetHeight
				: this.slider.nativeElement.offsetWidth;
		}

		return size;
	}

	// Расчет размера каретки
	public calcThumbSize(): number {
		let size = this.viewport * this.sliderSize;
		size = this.canvas ? size / this.canvas : 0;
		size = CrmHelperService.clamp(size, this.MIN_THUMB_SIZE, this.sliderSize);

		return size;
	}

	// Расчет позиции каретки
	public calcThumbPos(): number {
		const maxOffset = CrmHelperService.clamp(this.canvas - this.viewport);
		const maxPos = this.sliderSize - this.thumbSize;
		const percent = maxOffset ? this.offset / maxOffset : 0;
		const pos = CrmHelperService.clamp(percent * maxPos, 0, maxPos);

		return pos;
	}

	// Захват каретки
	public sliderPressed(x: number, y: number): void {
		if (this.thumb && this.thumb.nativeElement) {
			const { left, top } = CrmHelperService.getOffsetElement(
				document.body,
				this.thumb.nativeElement
			);
			const len = this.isVertical ? top : left;
			const point = this.isVertical ? y : x;
			this.thumbPosRatio = this.thumbSize ? (point - len) / this.thumbSize : 0;
			this.thumbPosStart = len - this.thumbPos;
		}
	}

	// Перемещение каретки
	public sliderMove(x: number, y: number): void {
		const point = this.isVertical ? y : x;
		const thumbOffset = this.thumbPosRatio * this.thumbSize;
		const pos = point - this.thumbPosStart - thumbOffset;
		const maxPos = CrmHelperService.clamp(this.sliderSize - this.thumbSize);
		this.thumbPos = CrmHelperService.clamp(pos, 0, maxPos);
		const percent = maxPos ? this.thumbPos / maxPos : 0;
		this.sliderMoved.emit(percent);
	}

	// Убрать обработчики
	public removeEvents(): void {
		if (this.mouseMoveListen) { this.mouseMoveListen(); }
		if (this.mouseUpListen) { this.mouseUpListen(); }
		if (this.touchMoveListen) { this.touchMoveListen(); }
		if (this.touchEndListen) { this.touchEndListen(); }
	}

	// Очистить ссылки на DOM
	public removeLinks(): void {
		this.touchTarget = null;
	}

	// Добавить сенсор
	private addSensor(): void {
		if (this.slider && this.slider.nativeElement) {
			this.resizeSensor = new ResizeSensor(
				this.slider.nativeElement,
				this.update.bind(this)
			);
		}
	}

	// Удалить сенсор
	private removeSensor(): void {
		if (this.resizeSensor) { this.resizeSensor.detach(); }
	}

	// --------------------------------------------------------------------------
	// HOOKS
	// Инициализация
	public ngOnInit(): void {
		this.addSensor();
		this.update();
	}

	// Уничтожение
	public ngOnDestroy(): void {
		this.removeEvents();
		this.removeLinks();
		this.removeSensor();
	}

	// Изменение входных параметров
	public ngOnChanges(): void {
		this.update();
	}
}
