import { ACTION_MODE, App, vertexsMap } from '..';
import undoRedoService from '../../service/undoredo';
import Vertex from '../common/Vertex';
import { LogicItem } from '../logic/LogicItem';

export interface Operation {
    operationAction: string;
    operationDesc: string;
    operationBeforeImage: any;
    operationAfterImage: any;
    activeImage?: any
}

export interface OperationHandler {
    context: Vertex | { new(): LogicItem },
    undoAction: string,
    undoActionParam: string,
    redoAction: string,
    redoActionParam: string,
}

/**
 * 后续考虑优化可逆操作
 * @param operation
 * @returns
 */
function getOperationHandler(operation: Operation) {
    const map: { [action: string]: () => OperationHandler } = {
        /* Element */
        'Element.create': () => ({
            context: vertexsMap.get(operation.operationAfterImage.parentId),
            undoAction: 'Element.removeChild',
            undoActionParam: 'After',
            redoAction: 'Element.addChild',
            redoActionParam: 'After',
        }),
        'Element.addChild': () => ({
            context: vertexsMap.get(operation.operationAfterImage.parentId),
            undoAction: 'Element.removeChild',
            undoActionParam: 'After',
            redoAction: 'Element.addChild',
            redoActionParam: 'After',
        }),
        'Element.delete': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.parentId),
            undoAction: 'Element.addChild',
            undoActionParam: 'Before',
            redoAction: 'Element.removeChild',
            redoActionParam: 'Before',
        }),
        'Element.removeChild': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.parentId),
            undoAction: 'Element.addChild',
            undoActionParam: 'Before',
            redoAction: 'Element.removeChild',
            redoActionParam: 'Before',
        }),
        'Element.update': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.id),
            undoAction: 'Element.update',
            undoActionParam: 'Before',
            redoAction: 'Element.update',
            redoActionParam: 'After',
        }),
        'Element.move': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.id),
            undoAction: 'Element.move',
            undoActionParam: 'Before',
            redoAction: 'Element.move',
            redoActionParam: 'After',
        }),
        /* Attr */
        'Attr.create': () => ({
            context: vertexsMap.get(operation.operationAfterImage.elementId),
            undoAction: 'Element.deleteAttr',
            undoActionParam: 'After',
            redoAction: 'Element.addAttr',
            redoActionParam: 'After',
        }),
        'Element.addAttr': () => ({
            context: vertexsMap.get(operation.operationAfterImage.elementId),
            undoAction: 'Element.deleteAttr',
            undoActionParam: 'After',
            redoAction: 'Element.addAttr',
            redoActionParam: 'After',
        }),
        'Attr.delete': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.elementId),
            undoAction: 'Element.addAttr',
            undoActionParam: 'Before',
            redoAction: 'Element.deleteAttr',
            redoActionParam: 'Before',
        }),
        'Element.deleteAttr': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.elementId),
            undoAction: 'Element.addAttr',
            undoActionParam: 'Before',
            redoAction: 'Element.deleteAttr',
            redoActionParam: 'Before',
        }),
        'Attr.update': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.id),
            undoAction: 'Attr.update',
            undoActionParam: 'Before',
            redoAction: 'Attr.update',
            redoActionParam: 'After',
        }),
        /* Event */
        'Event.create': () => ({
            context: vertexsMap.get(operation.operationAfterImage.elementId),
            undoAction: 'Element.deleteEvent',
            undoActionParam: 'After',
            redoAction: 'Element.addEvent',
            redoActionParam: 'After',
        }),
        'Element.addEvent': () => ({
            context: vertexsMap.get(operation.operationAfterImage.elementId),
            undoAction: 'Element.deleteEvent',
            undoActionParam: 'After',
            redoAction: 'Element.addEvent',
            redoActionParam: 'After',
        }),
        'Event.delete': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.elementId),
            undoAction: 'Element.addEvent',
            undoActionParam: 'Before',
            redoAction: 'Element.deleteEvent',
            redoActionParam: 'Before',
        }),
        'Element.deleteEvent': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.elementId),
            undoAction: 'Element.addEvent',
            undoActionParam: 'Before',
            redoAction: 'Element.deleteEvent',
            redoActionParam: 'Before',
        }),
        'Event.update': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.id),
            undoAction: 'Event.update',
            undoActionParam: 'Before',
            redoAction: 'Event.update',
            redoActionParam: 'After',
        }),
        /* Directive */
        'Directive.create': () => ({
            context: vertexsMap.get(operation.operationAfterImage.elementId),
            undoAction: 'Element.deleteDirective',
            undoActionParam: 'After',
            redoAction: 'Element.addDirective',
            redoActionParam: 'After',
        }),
        'Element.addDirective': () => ({
            context: vertexsMap.get(operation.operationAfterImage.elementId),
            undoAction: 'Element.deleteDirective',
            undoActionParam: 'After',
            redoAction: 'Element.addDirective',
            redoActionParam: 'After',
        }),
        'Directive.delete': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.elementId),
            undoAction: 'Element.addDirective',
            undoActionParam: 'Before',
            redoAction: 'Element.deleteDirective',
            redoActionParam: 'Before',
        }),
        'Element.deleteDirective': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.elementId),
            undoAction: 'Element.addDirective',
            undoActionParam: 'Before',
            redoAction: 'Element.deleteDirective',
            redoActionParam: 'Before',
        }),
        'Directive.update': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.id),
            undoAction: 'Directive.update',
            undoActionParam: 'Before',
            redoAction: 'Directive.update',
            redoActionParam: 'After',
        }),
        /* Lifecycle */
        'Lifecycle.create': () => ({
            context: vertexsMap.get(operation.operationAfterImage.viewId),
            undoAction: 'Element.removeLifecycle',
            undoActionParam: 'After',
            redoAction: 'Element.addLifecycle',
            redoActionParam: 'After',
        }),
        'View.addLifecycle': () => ({
            context: vertexsMap.get(operation.operationAfterImage.viewId),
            undoAction: 'View.removeLifecycle',
            undoActionParam: 'After',
            redoAction: 'View.addLifecycle',
            redoActionParam: 'After',
        }),
        'Lifecycle.delete': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.viewId),
            undoAction: 'View.addLifecycle',
            undoActionParam: 'Before',
            redoAction: 'View.removeLifecycle',
            redoActionParam: 'Before',
        }),
        'View.removeLifecycle': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.viewId),
            undoAction: 'View.addLifecycle',
            undoActionParam: 'Before',
            redoAction: 'View.removeLifecycle',
            redoActionParam: 'Before',
        }),
        'Lifecycle.update': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.id),
            undoAction: 'Lifecycle.update',
            undoActionParam: 'Before',
            redoAction: 'Lifecycle.update',
            redoActionParam: 'After',
        }),
        /* LogicItem */
        'LogicItem.create': () => ({
            context: vertexsMap.get(operation.operationAfterImage.logicId),
            undoAction: 'Logic.removeItem',
            undoActionParam: 'After',
            redoAction: 'Logic.addItem',
            redoActionParam: 'After',
        }),
        'Logic.addItem': () => ({
            context: vertexsMap.get(operation.operationAfterImage.logicId),
            undoAction: 'Logic.removeItem',
            undoActionParam: 'After',
            redoAction: 'Logic.addItem',
            redoActionParam: 'After',
        }),
        'LogicItem.delete': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.logicId),
            undoAction: 'Logic.addItem',
            undoActionParam: 'Before',
            redoAction: 'Logic.removeItem',
            redoActionParam: 'Before',
        }),
        'LogicItem.removeItem': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.logicId),
            undoAction: 'Logic.addItem',
            undoActionParam: 'Before',
            redoAction: 'Logic.removeItem',
            redoActionParam: 'Before',
        }),
        'LogicItem.update': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.id),
            undoAction: 'LogicItem.update',
            undoActionParam: 'Before',
            redoAction: 'LogicItem.update',
            redoActionParam: 'After',
        }),
        'LogicItem.move': () => ({
            context: vertexsMap.get(operation.operationBeforeImage.id),
            undoAction: 'LogicItem.move',
            undoActionParam: 'Before',
            redoAction: 'LogicItem.move',
            redoActionParam: 'After',
        }),
        'LogicItem.paste': () => ({
            context: LogicItem,
            undoAction: 'View.undoPaste',
            undoActionParam: 'After',
            redoAction: 'View.redoPaste',
            redoActionParam: 'After',
        }),
        'View.mergeBlock': () => ({
            context: vertexsMap.get(operation.operationAfterImage.id),
            undoAction: 'View.undoMergeBlock',
            undoActionParam: 'After',
            redoAction: 'View.redoMergeBlock',
            redoActionParam: 'After',
        }),
    };

    return map[operation.operationAction]();
}
export class History {
    app: App = undefined;
    undoCount: number = 0;
    redoCount: number = 0;
    undoStack: Array<Operation> = [];
    redoStack: Array<Operation> = [];
    constructor(source: any) {
        Object.assign(this, source);
    }
    /**
     * 加载 undoRedo 栈
     * @param operation 失败时用于前端模拟
     */
    async load(operation?: Operation) {
        try {
            const result = await undoRedoService.undoRedoInfo({
                headers: {
                    appId: this.app.id,
                },
            });
            this.undoCount = result.undoNumber;
            this.redoCount = result.redoNumber;
        } catch (e) { }
    }
    async undo() {
        this.app.emit('saving');

        let operation: Operation;
        try {
            const result = await undoRedoService.undo({
                headers: {
                    appId: this.app.id,
                },
            });
            operation = result.operation;
        } catch (e) {
            return;
        }

        const oh = getOperationHandler(operation);
        let image = (operation as any)[`operation${oh.undoActionParam}Image`];
        if (operation.operationAction === 'LogicItem.move') {
            // 移动 QueryJoinExpression 会同时修改 onExpressionList
            if (image.type === 'QueryJoinExpression') {
                const context = oh.context as LogicItem;
                context.assign(LogicItem.from(image, context.logic, context.parent));
            }

            image = image.moveToLogicItem;
        }

        operation.activeImage = image;
        if (operation.operationAction === 'LogicItem.paste')
            operation.activeImage = operation.operationAfterImage.logicItems[0];

        await (oh.context as any)[oh.undoAction.split('.').pop()](image, {
            actionMode: ACTION_MODE.undoRedo,
        });

        return operation;
    }

    async redo() {
        this.app.emit('saving');

        let operation: Operation;
        try {
            const result = await undoRedoService.redo({
                headers: {
                    appId: this.app.id,
                },
            });
            operation = result.operation;
        } catch (e) {
            return;
        }

        const oh = getOperationHandler(operation);
        let image = (operation as any)[`operation${oh.redoActionParam}Image`];
        if (operation.operationAction === 'LogicItem.move') {
            // 移动 QueryJoinExpression 会同时修改 onExpressionList
            const { operationBeforeImage } = operation;
            if (operationBeforeImage.type === 'QueryJoinExpression') {
                const context = <any>oh.context;
                context.onExpressionList = [LogicItem.from(operationBeforeImage.onExpressionList[0], context.logic, context)];
                context.onExpressionList[0].left.propertyRef = '';
                context.onExpressionList[0].right.propertyRef = '';
            }

            image = image.moveToLogicItem;
        }

        operation.activeImage = image;
        if (operation.operationAction === 'LogicItem.paste')
            operation.activeImage = operation.operationAfterImage.logicItems[0];

        await (oh.context as any)[oh.redoAction.split('.').pop()](image, {
            actionMode: ACTION_MODE.undoRedo,
        });

        return operation;
    }

    clear() {
        this.undoStack = [];
        this.redoStack = [];
        this.undoCount = 0;
        this.redoCount = 0;
    }
}

export default History;
