import {
	Component,
	ElementRef,
	Input,
	OnDestroy,
	OnInit,
	Renderer2,
	ViewChild
} from '@angular/core';

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

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

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

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

// Скролл
@Component({
	selector: 'crm-scroll',
	templateUrl: './crm-scroll.component.html',
	styleUrls: ['./crm-scroll.component.scss']
})
export class CrmScrollComponent
	implements OnInit, OnDestroy {

	constructor(private readonly renderer: Renderer2) {}

	// Режим скролла
	@Input() public mode: ScrollMode = ScrollMode.Vertical;

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

	// Ссылка на область отображения
	@ViewChild('viewportRef') public viewportRef: ElementRef;

	// Ссылка на область контента
	@ViewChild('canvasRef') public canvasRef: ElementRef;

	// Размер прокрутки колесом мыши (px)
	private readonly WHEEL_SIZE: number = 30;

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

	// Последняя позиция касания
	private readonly lastTouch: IOffset = {
		left: 0,
		top: 0
	};

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

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

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

	// Размер области отображения
	public readonly viewport: ISize = {
		width: 0,
		height: 0
	};

	// Размер области контента
	public readonly canvas: ISize = {
		width: 0,
		height: 0
	};

	// Смещение области контента в области отображения
	public readonly offset: IOffset = {
		left: 0,
		top: 0
	};

	// Смещение от левого края
	private get offsetLeft(): number {
		return this.offset.left;
	}
	private set offsetLeft(value: number) {
		this.offset.left = CrmHelperService.clamp(value, 0, this.maxOffsetLeft);
		this.viewportRef.nativeElement.scrollLeft = this.offset.left;
	}

	// Смещение от верхнего края
	private get offsetTop(): number {
		return this.offset.top;
	}
	private set offsetTop(value: number) {
		this.offset.top = CrmHelperService.clamp(value, 0, this.maxOffsetTop);
		this.viewportRef.nativeElement.scrollTop = this.offset.top;
	}

	// Максимальное смещение от левого края
	private get maxOffsetLeft(): number {
		return CrmHelperService.clamp(this.canvas.width - this.viewport.width);
	}

	// Максимальное смещение от верхнего края
	private get maxOffsetTop(): number {
		return CrmHelperService.clamp(this.canvas.height - this.viewport.height);
	}

	// Флаг видимости вертикального слайдера
	private _isVerticalSliderVisible: boolean = false;
	public get isVerticalSliderVisible(): boolean {
		return this._isVerticalSliderVisible;
	}

	// Флаг видимости горизонтального слайдера
	private _isHorizontalSliderVisible: boolean = false;
	public get isHorizontalSliderVisible(): boolean {
		return this._isHorizontalSliderVisible;
	}

	// --------------------------------------------------------------------------
	// Добавить сенсоры
	private addSensors(): void {
		this.resizeSensorView = new ResizeSensor(
			this.viewportRef.nativeElement,
			this.updateViewport.bind(this)
		);
		this.resizeSensorCanvas = new ResizeSensor(
			this.canvasRef.nativeElement,
			this.updateCanvas.bind(this)
		);
	}

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

	// Обновить область отображения
	private updateViewport(): void {
		this.viewport.width = this.viewportRef.nativeElement.offsetWidth;
		this.viewport.height = this.viewportRef.nativeElement.offsetHeight;
		this.updateSliders();
	}

	// Обновить область контента
	private updateCanvas(): void {
		this.canvas.width = this.canvasRef.nativeElement.offsetWidth;
		this.canvas.height = this.canvasRef.nativeElement.offsetHeight;
		this.updateSliders();
	}

	// Обновить видимость слайдеров
	private updateSliders(): void {
		// Защита от ложного переключения флагов
		setTimeout((): void => {
			this._isVerticalSliderVisible = this.canvas.height > this.viewport.height;
			this._isHorizontalSliderVisible = this.canvas.width > this.viewport.width;
		});
	}

	// --------------------------------------------------------------------------
	// Обработчик прокрутки
	public onMouseWheel(event: MouseEvent): void {
		const delta = CrmHelperService.getDeltaFromWheelEvent(event);
		this.offsetLeft += delta.x * this.WHEEL_SIZE;
		this.offsetTop += delta.y * this.WHEEL_SIZE;
	}

	// Обработчик перемещения вертикального слайдера
	public onVerticalSliderMoved(percent: number): void {
		this.offsetTop = this.maxOffsetTop * percent;
	}

	// Обработчик вертикальной прокрутки
	public onVerticalScroll(delta: number): void {
		this.offsetTop += delta;
	}

	// Обработчик перемещения горизонтального слайдера
	public onHorizontalSliderMoved(percent: number): void {
		this.offsetLeft = this.maxOffsetLeft * percent;
	}

	// Обработчик горизонтальной прокрутки
	public onHorizontalScroll(delta: number): void {
		this.offsetLeft += delta;
	}

	// --------------------------------------------------------------------------
	// Нажатие на область
	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.touchMoveListen = this.renderer.listen(
				this.touchTarget,
				'touchmove',
				this.onTouchMove.bind(this)
			);
			this.touchEndListen = this.renderer.listen(
				this.touchTarget,
				'touchend',
				this.onTouchEnd.bind(this)
			);
			this.lastTouch.left = touch.clientX;
			this.lastTouch.top = touch.clientY;
		}
	}

	// Перемещение по области
	public onTouchMove(event: TouchEvent): void {
		const touch = event.touches[event.touches.length - 1];
		this.offsetLeft += this.lastTouch.left - touch.clientX;
		this.offsetTop += this.lastTouch.top - touch.clientY;
		this.lastTouch.left = touch.clientX;
		this.lastTouch.top = touch.clientY;
	}

	// Отжатие по области
	public onTouchEnd(event: TouchEvent): void {
		this.removeEvents();
		this.removeLinks();
	}

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

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

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

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