import { Point } from "@devexpress/utils/lib/geometry/point";
import { GridElementInfo } from "../Helpers/GridElementInfo";
import { Task } from "../../Model/Entities/Task";
import { ViewType } from "../Helpers/Enums";
import { DateRange } from "../../Model/WorkingTime/DateRange";
import { DomUtils } from "@devexpress/utils/lib/utils/dom";
import { Browser } from "@devexpress/utils/lib/browser";
import { ViewVisualModelItem } from "../../Model/VisualModel/ViewVisualModelItem";
import { TaskEditTooltip } from "./TaskEditTooltip";
import { DependencyType } from "../../Model/Entities/Enums";
import { RenderHelper } from "../Render/RenderHelper";
import { TooltipSettings } from "../Settings/InitSettings/TooltipSettings";
import { Settings } from "../Settings/Settings";
import { ViewVisualModel } from "../../Model/VisualModel/VisualModel";
import { CommandManager } from "../../Commands/CommandManager";
import { TaskEditSettings } from "../Settings/InitSettings/TaskEditSettings";
import { ValidationController } from "../../Model/Validation/ValidationController";
import { DateUtils } from "../Utils/DateUtils";
import { UpdateTaskCommand } from "../../Commands/Task/UpdateTaskCommand";

export class TaskEditController {
    private baseElement: HTMLElement;
    private baseFrame: HTMLElement;
    private progressEdit: HTMLElement;
    private startEdit: HTMLElement;
    private endEdit: HTMLElement;
    private dependencyStart: HTMLElement;
    private dependencyFinish: HTMLElement;
    private timerId: number;
    private showInfoDelay: number = 1000;
    private dependencyLine: HTMLElement;
    private dependencySuccessorBaseElement: HTMLElement;
    private dependencySuccessorFrame: HTMLElement;
    public dependencySuccessorStart: HTMLElement;
    public dependencySuccessorFinish: HTMLElement;

    private settings: TaskEditSettings;
    private _tooltip: TaskEditTooltip;
    private taskIndex: number = -1;
    private successorIndex: number = -1;
    private isEditingInProgress: boolean = false;
    private disableTaskEditBox: boolean = false;
    private isTaskEditBoxShown: boolean = false;
    private startPosition: Point;
    private taskDateRange: DateRange;
    private onMouseLeaveHandler: any;
    public dependencyId: string;
    public wrapInfo: GridElementInfo;
    private get taskId(): string {
        return this.viewModel.items[this.taskIndex].task.internalId;
    }
    private get successorId(): string {
        return this.viewModel.items[this.successorIndex].task.internalId;
    }
    private get task(): Task {
        return this.viewItem.task;
    }
    private get viewItem(): ViewVisualModelItem {
        return this.viewModel.items[this.taskIndex];
    }
    private get renderHelper(): RenderHelper {
        return this.settings.getRenderHelper();
    }
    private get ganttSettings(): Settings {
        return this.settings.getGanttSettings();
    }
    private get viewModel(): ViewVisualModel {
        return this.settings.getViewModel();
    }
    private get commandManager(): CommandManager {
        return this.settings.getCommandManager();
    }
    private get updateTaskCommand(): UpdateTaskCommand {
        return this.commandManager.updateTaskCommand;
    }
    private get validationController(): ValidationController {
        return this.settings.getValidationController();
    }

    private raiseTaskMoving(task: Task, newStart: Date, newEnd: Date, callback:(newStart, newEnd: Date) => void): boolean {
        return this.settings.getModelManipulator().dispatcher.raiseTaskMoving(task, newStart, newEnd, callback);
    }


    public get tooltip(): TaskEditTooltip {
        this._tooltip ??= new TaskEditTooltip(this.baseElement, this.tooltipSettings, this.renderHelper.elementTextHelperCultureInfo);
        this._tooltip.tooltipSettings = this.tooltipSettings;
        return this._tooltip;
    }

    public get tooltipSettings(): TooltipSettings {
        const tooltipSettings = TooltipSettings.parse({
            getHeaderHeight: this.settings.getRenderHelper().header.clientHeight,
            getTaskProgressTooltipContentTemplate: this.ganttSettings.taskProgressTooltipContentTemplate,
            getTaskTimeTooltipContentTemplate: this.ganttSettings.taskTimeTooltipContentTemplate,
            getTaskTooltipContentTemplate: this.ganttSettings.taskTooltipContentTemplate,
            destroyTemplate: (container: HTMLElement) => { this.settings.destroyTemplate(container); },
            formatDate: (date: Date) => { return this.settings.formatDate(date); }
        });

        return tooltipSettings;
    }

    constructor(settings: TaskEditSettings) {
        this.settings = settings;
        this.createElements();
    }

    public show(taskIndex: number): void {
        if(this.isEditingInProgress || this.disableTaskEditBox)
            return;
        this.taskIndex = taskIndex;
        this.hide();
        this.changeWrapInfo();
        this.baseElement.className = TaskEditController.CLASSNAMES.TASK_EDIT_BOX;
        this.displayDependency();
        if(this.task.isMilestone() && !this.viewItem.isCustom)
            this.baseElement.className = this.baseElement.className + " milestone";
        else {
            if(!this.isTaskUpdateAllowed())
                this.baseElement.className = this.baseElement.className + " " + TaskEditController.CLASSNAMES.TASK_EDIT_HIDE_UPDATING;
            if(this.viewItem.isCustom)
                this.baseElement.classList.add(TaskEditController.CLASSNAMES.TASK_EDIT_BOX_CUSTOM);
        }
        const delay = this.settings.getGanttSettings().editing.taskHoverDelay || 0;
        this.taskDateRange = new DateRange(this.task.start, this.task.end);
        this.displayTaskEditBox(delay);
        this.displayProgressEdit();
        this.displayStartEndEditElements();

    }
    private displayStartEndEditElements() {
        const showElements = !this.task.isMilestone() && this.isTaskUpdateAllowed() && this.canUpdateTask();
        if(!showElements) {
            this.startEdit.style.display = "none";
            this.endEdit.style.display = "none";
        }
        else {
            this.startEdit.style.display = "block";
            this.endEdit.style.display = "block";
        }
    }
    private displayProgressEdit() {
        if(!this.viewItem.isCustom && this.canUpdateTask() && this.isTaskUpdateAllowed() && this.wrapInfo.size.width > this.wrapInfo.size.height) {
            this.progressEdit.style.display = "block";
            this.progressEdit.style.left = ((this.task.normalizedProgress / 100) * this.wrapInfo.size.width - (this.progressEdit.offsetWidth / 2)) + "px";
        }
        else
            this.progressEdit.style.display = "none";
    }
    private displayDependency() {
        if(!this.ganttSettings.editing.enabled || !this.ganttSettings.editing.allowDependencyInsert || !this.ganttSettings.showDependencies)
            this.baseElement.className = this.baseElement.className + " hide-dependency";
    }
    private changeWrapInfo() {
        this.updateWrapInfo();
        this.wrapInfo.assignPosition(this.baseElement);
        this.wrapInfo.assignSize(this.baseElement);
    }
    private displayTaskEditBox(delay: number = 0) {
        const showFunc = () => {
            this.renderHelper.taskArea.appendChild(this.baseElement);
            this.isTaskEditBoxShown = true;
        };
        if(delay)
            this.timerId = setTimeout(showFunc, delay);
        else
            showFunc();
    }
    public endEditing(): void {
        this.isEditingInProgress = false;
        this.hide();
    }
    public hide(): void {
        this.isTaskEditBoxShown = false;
        const parentNode: Node = this.baseElement.parentNode;
        if(parentNode)
            parentNode.removeChild(this.baseElement);
        this.tooltip.hide();
        clearTimeout(this.timerId);
    }
    public cancel(): void {
        clearTimeout(this.timerId);
    }
    public showTaskInfo(posX: number, delay: number = 500): void {
        if(this.timerId)
            delay = this.showInfoDelay;
        this.tooltip.showInfo(this.task, posX, delay);
    }
    private updateWrapInfo(): void {
        this.wrapInfo = this.getTaskWrapperElementInfo(this.taskIndex);
        this.wrapInfo.position.x--;
    }
    private isAllowedToConnectTasks(taskIndex: number) {
        const successorViewItem = this.viewModel.items[taskIndex];
        return this.validationController.canCreateDependency(this.taskId, successorViewItem.task?.internalId);
    }
    public showDependencySuccessor(taskIndex: number): void {
        if(this.isAllowedToConnectTasks(taskIndex)) {
            this.successorIndex = taskIndex;
            const wrapInfo = this.getTaskWrapperElementInfo(taskIndex);
            wrapInfo.assignPosition(this.dependencySuccessorBaseElement);
            wrapInfo.assignSize(this.dependencySuccessorBaseElement);
            wrapInfo.assignSize(this.dependencySuccessorFrame);
            this.renderHelper.taskArea.appendChild(this.dependencySuccessorBaseElement);
        }
    }
    public hideDependencySuccessor(): void {
        const parentNode: Node = this.dependencySuccessorBaseElement.parentNode;
        if(parentNode)
            parentNode.removeChild(this.dependencySuccessorBaseElement);
        this.successorIndex = -1;
    }
    public processProgress(position: Point): void {
        if(!this.isTaskUpdateAllowed())
            return;
        this.isEditingInProgress = true;
        const progressOffset = position.x - this.wrapInfo.position.x;
        let progress = 0;
        if(position.x > this.wrapInfo.position.x)
            if(position.x < this.wrapInfo.position.x + this.wrapInfo.size.width)
                progress = Math.round((progressOffset) / this.baseElement.clientWidth * 100);
            else
                progress = 100;
        this.progressEdit.style.left = ((progress / 100) *
            this.wrapInfo.size.width - (this.progressEdit.offsetWidth / 2)) + "px";
        this.tooltip.showProgress(progress,
            DomUtils.getAbsolutePositionX(this.progressEdit) + this.progressEdit.offsetWidth / 2);
    }
    public confirmProgress(): void {
        if(this.isTaskUpdateAllowed()) {
            this.isEditingInProgress = false;
            const progress = Math.round((this.progressEdit.offsetLeft + (this.progressEdit.offsetWidth / 2)) / this.wrapInfo.size.width * 100);
            this.updateTaskCommand.execute(this.taskId, { progress: progress });
        }
    }
    public processEnd(position: Point): void {
        if(!this.isTaskUpdateAllowed())
            return;
        this.baseElement.className = this.baseElement.className + " move";
        this.isEditingInProgress = true;
        const positionX = position.x > this.wrapInfo.position.x ? position.x : this.wrapInfo.position.x;
        const width = positionX - this.wrapInfo.position.x;
        this.baseElement.style.width = (width < 1 ? 0 : width) + "px";
        const startDate = this.task.start;
        const date = this.renderHelper.gridLayoutCalculator.getDateByPos(positionX);
        date.setSeconds(0);
        if(date < startDate || width < 1)
            this.taskDateRange.end.setTime(startDate.getTime());
        else
            this.taskDateRange.end = this.getCorrectedDate(this.task.end, date);
        this.tooltip.showTime(startDate, this.taskDateRange.end, DomUtils.getAbsolutePositionX(this.baseElement) + this.baseElement.clientWidth);
    }
    public confirmEnd(): void {
        if(this.isTaskUpdateAllowed()) {
            this.baseElement.className = TaskEditController.CLASSNAMES.TASK_EDIT_BOX;
            this.isEditingInProgress = false;
            this.updateTaskCommand.execute(this.taskId, { end: this.taskDateRange.end });
            this.hide();
            this.updateWrapInfo();
        }
    }
    public processStart(position: Point): void {
        if(!this.isTaskUpdateAllowed())
            return;
        this.baseElement.className = this.baseElement.className + " move";
        this.isEditingInProgress = true;
        const positionX = position.x < this.wrapInfo.position.x + this.wrapInfo.size.width ? position.x : this.wrapInfo.position.x + this.wrapInfo.size.width;
        const width = this.wrapInfo.size.width - (positionX - this.wrapInfo.position.x);
        this.baseElement.style.left = positionX + "px";
        this.baseElement.style.width = (width < 1 ? 0 : width) + "px";
        const endDate = this.task.end;
        const date = this.renderHelper.gridLayoutCalculator.getDateByPos(positionX);
        date.setSeconds(0);
        if(date > endDate || width < 1)
            this.taskDateRange.start.setTime(endDate.getTime());
        else
            this.taskDateRange.start = this.getCorrectedDate(this.task.start, date);
        this.tooltip.showTime(this.taskDateRange.start, endDate, DomUtils.getAbsolutePositionX(this.baseElement));
    }
    public confirmStart(): void {
        if(this.isTaskUpdateAllowed()) {
            this.baseElement.className = TaskEditController.CLASSNAMES.TASK_EDIT_BOX;
            this.isEditingInProgress = false;
            this.updateTaskCommand.execute(this.taskId, { start: this.taskDateRange.start });
            this.hide();
            this.updateWrapInfo();
        }
    }
    public processMove(delta: number): boolean {
        if(this.isTaskUpdateAllowed() && this.isTaskEditBoxShown) {
            this.baseElement.className = this.baseElement.className + " move";
            const left = this.baseElement.offsetLeft + delta;
            this.baseElement.style.left = left + "px";
            const startDate = this.renderHelper.gridLayoutCalculator.getDateByPos(left);
            this.taskDateRange.start = this.getCorrectedDate(this.task.start, startDate);
            const taskPeriod = DateUtils.getRangeMSPeriod(this.task.start, this.task.end);
            this.taskDateRange.end = DateUtils.getDSTCorrectedTaskEnd(this.taskDateRange.start, taskPeriod);
            this.isEditingInProgress = this.raiseTaskMoving(this.task, this.taskDateRange.start, this.taskDateRange.end, this.onTaskMovingCallback.bind(this));
            if(this.isEditingInProgress)
                this.tooltip.showTime(this.taskDateRange.start, this.taskDateRange.end, DomUtils.getAbsolutePositionX(this.baseElement));
            return this.isEditingInProgress;
        }
        return true;
    }
    onTaskMovingCallback(newStart: Date, newEnd: Date): void {
        if(this.taskDateRange.start === newStart && this.taskDateRange.end === newEnd)
            return;

        const calculator = this.renderHelper.gridLayoutCalculator;
        const newLeft = calculator.getPosByDate(newStart);
        const newWidth = calculator.getPosByDate(newEnd) - newLeft;
        this.baseElement.style.left = newLeft + "px";
        this.baseElement.style.width = (newWidth < 1 ? 0 : newWidth) + "px";
        this.taskDateRange.start = newStart;
        this.taskDateRange.end = newEnd;
    }
    public confirmMove(): void {
        if(this.isTaskUpdateAllowed()) {
            if(!this.ganttSettings.editing.allowDependencyInsert)
                this.baseElement.className = this.baseElement.className + " hide-dependency";
            if(this.isEditingInProgress) {
                this.baseElement.className = TaskEditController.CLASSNAMES.TASK_EDIT_BOX;
                this.updateTaskCommand.execute(this.taskId, { start: this.taskDateRange.start, end: this.taskDateRange.end });
                this.updateWrapInfo();
                this.hide();
                this.isEditingInProgress = false;
            }
        }
    }
    private getCorrectedDate(referenceDate: Date, newDate: Date): Date {
        if(this.ganttSettings.viewType > ViewType.SixHours) {
            const year = newDate.getFullYear();
            const month = newDate.getMonth();
            const day = newDate.getDate();
            const hours = this.ganttSettings.viewType === ViewType.Days ? newDate.getHours() : referenceDate.getHours();
            const minutes = referenceDate.getMinutes();
            const sec = referenceDate.getSeconds();
            const msec = referenceDate.getMilliseconds();
            return new Date(year, month, day, hours, minutes, sec, msec);
        }

        return newDate;
    }
    public startDependency(pos: Point): void {
        this.dependencyLine = document.createElement("DIV");
        this.dependencyLine.className = TaskEditController.CLASSNAMES.TASK_EDIT_DEPENDENCY_LINE;
        this.renderHelper.taskArea.appendChild(this.dependencyLine);
        this.startPosition = pos;
    }
    public processDependency(pos: Point): void {
        this.isEditingInProgress = true;
        this.drawline(this.startPosition, pos);
    }
    public endDependency(type: DependencyType): void {
        this.isEditingInProgress = false;
        if(type != null)
            this.commandManager.createDependencyCommand.execute(this.task.internalId, this.successorId, type);
        const parentNode: Node = this.dependencyLine.parentNode;
        if(parentNode)
            parentNode.removeChild(this.dependencyLine);
        this.dependencyLine = null;
        this.hideDependencySuccessor();
        this.hide();
    }
    public selectDependency(id: string): void {
        if(this.ganttSettings.editing.allowDependencyDelete)
            this.dependencyId = id;
    }
    public isDependencySelected(id: string): boolean {
        return this.dependencyId && this.dependencyId === id;
    }
    public deleteSelectedDependency(): void {
        if(this.dependencyId)
            this.commandManager.removeDependencyCommand.execute(this.dependencyId);
    }
    private getTaskWrapperElementInfo(taskIndex: number): GridElementInfo {
        const calculator = this.renderHelper.gridLayoutCalculator;
        const info = calculator.getTaskWrapperElementInfo(taskIndex);
        info.size.width = calculator.getTaskWidth(taskIndex);
        info.size.height = calculator.getTaskHeight(taskIndex);
        return info;
    }

    public createElements(): void {
        this.baseElement = document.createElement("DIV");

        this.baseFrame = document.createElement("DIV");
        this.baseFrame.className = TaskEditController.CLASSNAMES.TASK_EDIT_FRAME;
        this.baseElement.appendChild(this.baseFrame);

        this.progressEdit = document.createElement("DIV");
        this.progressEdit.className = TaskEditController.CLASSNAMES.TASK_EDIT_PROGRESS;
        this.baseFrame.appendChild(this.progressEdit);
        this.progressEdit.appendChild(document.createElement("DIV"));

        this.dependencyFinish = document.createElement("DIV");
        this.dependencyFinish.classList.add(TaskEditController.CLASSNAMES.TASK_EDIT_DEPENDENCY_RIGTH);
        if(Browser.TouchUI)
            this.dependencyFinish.classList.add(TaskEditController.CLASSNAMES.TASK_EDIT_TOUCH);
        this.baseFrame.appendChild(this.dependencyFinish);

        this.dependencyStart = document.createElement("DIV");
        this.dependencyStart.classList.add(TaskEditController.CLASSNAMES.TASK_EDIT_DEPENDENCY_LEFT);
        if(Browser.TouchUI)
            this.dependencyStart.classList.add(TaskEditController.CLASSNAMES.TASK_EDIT_TOUCH);
        this.baseFrame.appendChild(this.dependencyStart);

        this.startEdit = document.createElement("DIV");
        this.startEdit.className = TaskEditController.CLASSNAMES.TASK_EDIT_START;
        this.baseFrame.appendChild(this.startEdit);

        this.endEdit = document.createElement("DIV");
        this.endEdit.className = TaskEditController.CLASSNAMES.TASK_EDIT_END;
        this.baseFrame.appendChild(this.endEdit);

        this.dependencySuccessorBaseElement = document.createElement("DIV");
        this.dependencySuccessorBaseElement.className = TaskEditController.CLASSNAMES.TASK_EDIT_BOX_SUCCESSOR;

        this.dependencySuccessorFrame = document.createElement("DIV");
        this.dependencySuccessorFrame.className = TaskEditController.CLASSNAMES.TASK_EDIT_FRAME_SUCCESSOR;
        this.dependencySuccessorBaseElement.appendChild(this.dependencySuccessorFrame);

        this.dependencySuccessorStart = document.createElement("DIV");
        this.dependencySuccessorStart.classList.add(TaskEditController.CLASSNAMES.TASK_EDIT_SUCCESSOR_DEPENDENCY_RIGTH);
        if(Browser.TouchUI)
            this.dependencySuccessorStart.classList.add(TaskEditController.CLASSNAMES.TASK_EDIT_TOUCH);
        this.dependencySuccessorFrame.appendChild(this.dependencySuccessorStart);

        this.dependencySuccessorFinish = document.createElement("DIV");
        this.dependencySuccessorFinish.classList.add(TaskEditController.CLASSNAMES.TASK_EDIT_SUCCESSOR_DEPENDENCY_LEFT);
        if(Browser.TouchUI)
            this.dependencySuccessorFinish.classList.add(TaskEditController.CLASSNAMES.TASK_EDIT_TOUCH);
        this.dependencySuccessorFrame.appendChild(this.dependencySuccessorFinish);
        this._tooltip = new TaskEditTooltip(this.baseElement, this.tooltipSettings, this.renderHelper.elementTextHelperCultureInfo);
        this.attachEvents();
    }
    private attachEvents(): void {
        this.onMouseLeaveHandler = function() {
            if(!this.isEditingInProgress)
                this.hide();
        }.bind(this);
        this.baseElement.addEventListener("mouseleave", this.onMouseLeaveHandler);
    }
    private drawline(start: Point, end: Point): void {
        if(start.x > end.x) {
            const temp = end;
            end = start;
            start = temp;
        }
        let angle = Math.atan((start.y - end.y) / (end.x - start.x));
        angle = (angle * 180 / Math.PI);
        angle = -angle;
        const length = Math.sqrt((start.x - end.x) * (start.x - end.x) + (start.y - end.y) * (start.y - end.y));
        this.dependencyLine.style.left = start.x + "px";
        this.dependencyLine.style.top = start.y + "px";
        this.dependencyLine.style.width = length + "px";
        this.dependencyLine.style.transform = "rotate(" + angle + "deg)";
    }
    private canUpdateTask(): boolean {
        return !this.viewModel.isTaskToCalculateByChildren(this.task.internalId);
    }
    private isTaskUpdateAllowed(): boolean {
        const settings = this.ganttSettings.editing;
        return settings.enabled && settings.allowTaskUpdate;
    }

    public detachEvents(): void {
        this.baseElement?.removeEventListener("mouseleave", this.onMouseLeaveHandler);
    }

    public static CLASSNAMES = {
        TASK_EDIT_BOX: "dx-gantt-task-edit-wrapper",
        TASK_EDIT_BOX_CUSTOM: "dx-gantt-task-edit-wrapper-custom",
        TASK_EDIT_FRAME: "dx-gantt-task-edit-frame",
        TASK_EDIT_PROGRESS: "dx-gantt-task-edit-progress",
        TASK_EDIT_DEPENDENCY_RIGTH: "dx-gantt-task-edit-dependency-r",
        TASK_EDIT_DEPENDENCY_LEFT: "dx-gantt-task-edit-dependency-l",
        TASK_EDIT_START: "dx-gantt-task-edit-start",
        TASK_EDIT_END: "dx-gantt-task-edit-end",
        TASK_EDIT_DEPENDENCY_LINE: "dx-gantt-task-edit-dependency-line",

        TASK_EDIT_BOX_SUCCESSOR: "dx-gantt-task-edit-wrapper-successor",
        TASK_EDIT_FRAME_SUCCESSOR: "dx-gantt-task-edit-frame-successor",
        TASK_EDIT_SUCCESSOR_DEPENDENCY_RIGTH: "dx-gantt-task-edit-successor-dependency-r",
        TASK_EDIT_SUCCESSOR_DEPENDENCY_LEFT: "dx-gantt-task-edit-successor-dependency-l",
        TASK_EDIT_TOUCH: "dx-gantt-edit-touch",
        TASK_EDIT_HIDE_UPDATING: "hide-updating"
    }
}
