import {
	AfterViewInit,
	Component,
	EventEmitter,
	HostListener,
	Input,
	Output
} from '@angular/core';

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

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

// Модели
import {
	Column,
	PlaceholderColumn
} from '../../models';

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

// Таблица
@Component({
	selector: 'crm-table',
	templateUrl: './crm-table.component.html',
	styleUrls: ['./crm-table.component.scss']
})
export class CrmTableComponent implements AfterViewInit {

	// Параметры столбцов
	@Input() public columnDefs: IColumnDefinition[];

	// Строки
	@Input() public rowData: any[];

	// Конфигурация таблицы
	@Input() public options: ITableOptions;

	// Событие изменения сортировки
	@Output() public changeSort = new EventEmitter<any>();

	// Событие инициации догрузки
	@Output() public loadMore = new EventEmitter<any>();

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

	// Размер одной "линии" контента (px)
	public readonly LINE_SIZE: number = 30;

	// Столбцы
	public columns: IColumn[];

	// Столбец заглушка
	public placeholderColumn: IColumn;

	// Закрепленные столбцы
	public pinnedColumns: IColumn[];

	// Ширина области контента закрепленных столбцов
	public pinnedCanvasWidth: number = 0;

	// Обычные столбцы
	public defaultColumns: IColumn[];

	// Ширина области контента обычных столбцов
	public defaultCanvasWidth: number = 0;

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

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

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

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

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

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

	// Максимальное смещение от верхнего края
	public 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 get renderColumns(): IColumn[] {
		return [...this.pinnedColumns, ...this.defaultColumns]
			.filter((value: any) => !value.isPlaceholder);
	}

	// --------------------------------------------------------------------------
	// Обработчик изменения размера
	public onResized(current: ISize, size: ISize): void {
		current.width = size.width;
		current.height = size.height;
		this.updateSliders();
	}

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

	// Обработчик прокрутки
	@HostListener('mousewheel', ['$event'])
	@HostListener('DOMMouseScroll', ['$event'])
	@HostListener('onmousewheel', ['$event'])
	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 onHorizontalSliderMoved(percent: number): void {
		this.offsetLeft = this.maxOffsetLeft * percent;
	}

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

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

	// --------------------------------------------------------------------------
	// Обработчик изменения сортировки
	public onChangeSort(column: IColumn): void {
		this.changeSort.emit({
			field: column.field,
			sortType: column.sortType
		});
	}

	// Обработчик изменения состояния столбца (закреплен/нет)
	public onChangePin(column: IColumn): void {
		this.updateColumns();
		this.updateColumnsPosition();
	}

	// Обработчик изменения ширины столбца
	public onChangeSize(column: IColumn): void {
		this.updateColumnsPosition();
	}

	// Обработчик перемещения
	public onChangeSwap(command: SwapCommand): void {
		this.swapCommand(command);
		this.updatePlaceholderColumn();
		this.updateColumns();
		this.updateColumnsPosition();
	}

	// Обработчик прокрутки контента
	public onChangeScroll(offset: number): void {
		this.offsetLeft = offset;
	}

	// --------------------------------------------------------------------------
	// Создание столбцов по их описанию
	private createColumns(): void {
		const columns = (this.columnDefs || [])
			.map((value: IColumnDefinition) => new Column(value));

		const pinnedColumns = columns
			.filter((value: IColumn) => value.isPinned);

		const defaultColumns = columns
			.filter((value: IColumn) => !value.isPinned);

		this.columns = [ ...pinnedColumns, ...defaultColumns ];
		this.placeholderColumn = new PlaceholderColumn();
	}

	// Обновить списки столбцов
	private updateColumns(): void {
		this.pinnedColumns = this.columns
			.filter((value: IColumn) =>
				value.isVisible && value.isPinned
			);

		this.defaultColumns = this.columns
			.filter((value: IColumn) =>
				value.isVisible && !value.isPinned
			);
	}

	// Выполнение команды на перемещение
	private swapCommand(command: SwapCommand): void {
		// Отображаемые столбцы
		const columns = this.renderColumns;
		const cur = columns.find((value: IColumn) => value.isSwap);
		const index = columns.indexOf(cur);
		const prev = columns[index - 1];
		const next = columns[index + 1];

		if (index !== -1) {
			switch (command) {
				case SwapCommand.SwapLeft:
					this.swapColumns(cur, prev);
					break;
				case SwapCommand.SwapRight:
					this.swapColumns(cur, next);
					break;
				default:
			}
		}
	}

	// Обменять столбцы местами
	private swapColumns(a: IColumn, b: IColumn): void {
		if (a && b) {
			const indexA = this.columns.indexOf(a);
			const indexB = this.columns.indexOf(b);
			const temp = this.columns[indexA];
			this.columns[indexA] = this.columns[indexB];
			this.columns[indexB] = temp;
		}
	}

	// Обновить заглушку
	private updatePlaceholderColumn(): void {
		this.removePlaceholderColumn();
		this.insertPlaceholderColumn();
	}

	// Вставляем заглушку в массив столбцов
	private insertPlaceholderColumn(): void {
		const column = this.columns.find((value: IColumn) => value.isSwap);
		const index = this.columns.indexOf(column);
		if (index !== -1) {
			this.placeholderColumn.isPinned = column.isPinned;
			this.placeholderColumn.width = column.width;
			this.placeholderColumn.offset = column.offset;
			this.placeholderColumn.component = column.component;
			this.placeholderColumn.events = column.events;

			// Вставка после оригинального столбца
			this.columns.splice(index + 1, 0, this.placeholderColumn);
		}
	}

	// Удаляем заглушку из массива столбцов
	private removePlaceholderColumn(): void {
		const index = this.columns.indexOf(this.placeholderColumn);
		if (index !== -1) {
			this.columns.splice(index, 1);
		}
	}

	// Обновить расположение столбцов
	private updateColumnsPosition(): void {
		this.pinnedCanvasWidth = this.calcColumnPosition(this.pinnedColumns);
		this.defaultCanvasWidth = this.calcColumnPosition(this.defaultColumns);
	}

	// Вычисление позиций столбцов
	private calcColumnPosition(columns: IColumn[] = []): number {
		let offset = 0;
		columns.forEach((column: IColumn) => {
			if (!column.isSwap) {
				column.offset = offset;
				offset += column.width;
			}
		});

		return offset;
	}

	// Проверка необходимости догрузки данных
	private checkLoadMore(): void {
		if (this.offset.top > this.maxOffsetTop - this.viewport.height) {
			this.loadMore.emit();
		}
	}

	// --------------------------------------------------------------------------
	// HOOKS
	// Инициализация
	public ngAfterViewInit(): void {
		// Вызов методов в следующем кадре
		setTimeout(() => {
			this.createColumns();
			this.updateColumns();
			this.updateColumnsPosition();
		});
	}
}
