import { INativeActionListener } from "./INativeActionListener";
import { EventDispatcher } from "../Utils";
import { BatchUpdatableObject } from "@devexpress/utils/lib/class/batch-updatable";
import { DiagramModel } from "../Model/Model";
import { Selection, ISelectionChangesListener } from "../Selection/Selection";
import { DiagramMouseEvent, MouseEventElementType } from "../Events/Event";
import { INativeConnector, INativeItem, INativeShape } from "./INativeItem";
import { IToolboxDragListener } from "../Render/Toolbox/Toolbox";
import { DiagramItem } from "../Model/DiagramItem";
import { DataSource } from "../Data/DataSource";
import { ModelUtils } from "../Model/ModelUtils";
import { Point, Size } from "..";
import { Connector } from "../Model/Connectors/Connector";
import { Shape } from "../Model/Shapes/Shape";

export interface IApiController {
    createNativeItem(item: DiagramItem): INativeItem;
    createNativeShape(shape: Shape): INativeShape;
    createNativeConnector(connector: Connector): INativeConnector;
    convertPoint(point: Point): Point;
    convertSize(size: Size): Size;
    convertUnit(value: number): number;
}

export class ApiController extends BatchUpdatableObject implements ISelectionChangesListener, IToolboxDragListener, IApiController {
    private events: EventDispatcher<INativeActionListener>;
    model: DiagramModel;
    private selection: Selection;
    private dataSource: DataSource;

    constructor(events: EventDispatcher<INativeActionListener>, selection: Selection, model: DiagramModel) {
        super();
        this.events = events;
        this.model = model;
        this.selection = selection;
    }

    notifySelectionChanged(selection: Selection): void {
        if(this.isUpdateLocked())
            this.registerOccurredEvent(ApiControllerAction.SelectionChanged);
        else
            this.raiseSelectionChanged();
    }

    notifyToolboxDragStart(): void {
        this.events.raise("notifyToolboxItemDragStart");
    }

    notifyToolboxDragEnd(): void {
        this.events.raise("notifyToolboxItemDragEnd");
    }
    notifyToolboxDraggingMouseMove(): void {
    }

    notifyClick(evt: DiagramMouseEvent): void {
        this.tryRaiseUserAction(evt, (i) => this.events.raise("notifyItemClick", i));
    }

    notifyDblClick(evt: DiagramMouseEvent): void {
        this.tryRaiseUserAction(evt, (i) => this.events.raise("notifyItemDblClick", i));
    }

    createNativeItem(item: DiagramItem): INativeItem {
        return item && this.cleanupNativeItem(item.toNative(this.model.units));
    }
    createNativeShape(shape: Shape): INativeShape {
        return <INativeShape> this.createNativeItem(shape);
    }
    createNativeConnector(connector: Connector): INativeConnector {
        return <INativeConnector> this.createNativeItem(connector);
    }
    convertUnit(value: number): number {
        return ModelUtils.getlUnitValue(this.model.units, value);
    }
    convertPoint(point: Point): Point {
        return new Point(
            this.convertUnit(point.x),
            this.convertUnit(point.y)
        );
    }
    convertSize(size: Size): Size {
        return new Size(
            this.convertUnit(size.width),
            this.convertUnit(size.height)
        );
    }
    private cleanupNativeItem(item: INativeItem) {
        const ds = this.dataSource;
        if(ds) {
            if(ds.isAutoGeneratedKey((<INativeConnector>item).fromKey))
                (<INativeConnector>item).fromKey = undefined;
            if(ds.isAutoGeneratedKey(item.key))
                item.key = undefined;
            if(ds.isAutoGeneratedKey((<INativeConnector>item).toKey))
                (<INativeConnector>item).toKey = undefined;
        }
        return item;
    }
    setDataSource(dataSource?: DataSource) {
        this.dataSource = dataSource;
    }

    private tryRaiseUserAction(evt: DiagramMouseEvent, callEvent: (item: INativeItem) => void) {
        if(this.isUserAction(evt)) {
            const item = this.model.findItem(evt.source.key);
            item && this.events.raise1(l => callEvent(this.createNativeItem(item)));
        }
    }
    private isUserAction(evt: DiagramMouseEvent): boolean {
        return evt.source && (
            evt.source.type === MouseEventElementType.Shape ||
            evt.source.type === MouseEventElementType.ShapeExpandButton ||
            evt.source.type === MouseEventElementType.ShapeParameterBox ||
            evt.source.type === MouseEventElementType.ShapeResizeBox ||
            evt.source.type === MouseEventElementType.ShapeConnectionPoint ||
            evt.source.type === MouseEventElementType.Connector ||
            evt.source.type === MouseEventElementType.ConnectorPoint ||
            evt.source.type === MouseEventElementType.ConnectorSide ||
            evt.source.type === MouseEventElementType.ConnectorOrthogonalSide ||
            evt.source.type === MouseEventElementType.ConnectorText
        );
    }

    onUpdateUnlocked(occurredEvents: number) {
        if(occurredEvents & ApiControllerAction.SelectionChanged)
            this.raiseSelectionChanged();
    }

    private raiseSelectionChanged() {
        const items = this.selection.getKeys().map(key => this.createNativeItem(this.model.findItem(key)));
        this.events.raise1(l => l.notifySelectionChanged(items));
    }
}

enum ApiControllerAction {
    SelectionChanged = 1 << 0,
}
