import { Rectangle } from "@devexpress/utils/lib/geometry/rectangle";
import { Point } from "@devexpress/utils/lib/geometry/point";
import { Metrics } from "@devexpress/utils/lib/geometry/metrics";
import { Connector } from "./Connectors/Connector";
import { SvgPrimitive } from "../Render/Primitives/Primitive";
import { ObjectUtils } from "../Utils";
import { IKeyOwner } from "../Interfaces";
import { Style, TextStyle } from "./Style";
import { ConnectionPoint } from "./ConnectionPoint";
import { Shape } from "./Shapes/Shape";
import { INativeItem } from "../Api/INativeItem";
import { DiagramUnit } from "../Enums";


export enum ConnectionPointSide { Undefined = -1, North = 0, East = 1, South = 2, West = 3 }

export type ItemKey = string;
export type ItemDataKey = string | number;
export type ItemsMap = {[key: string]: boolean};

export const DEFAULT_ZINDEX = 0;

export abstract class DiagramItem implements IKeyOwner {
    key: ItemKey = undefined;
    dataKey: ItemDataKey = undefined;
    customData: any = undefined;
    attachedConnectors: Connector[] = [];
    zIndex: number = DEFAULT_ZINDEX;
    locked: boolean = false;

    container: Shape = undefined;

    style: Style = new Style();
    styleText: TextStyle = new TextStyle();

    abstract createPrimitives(instanceId: string): SvgPrimitive<SVGGraphicsElement>[];
    abstract createSelectorPrimitives(): SvgPrimitive<SVGGraphicsElement>[];

    assign(item: DiagramItem) {
        item.key = this.key;
        item.dataKey = this.dataKey;
        item.customData = ObjectUtils.cloneObject(this.customData);
        item.locked = this.locked;
        item.attachedConnectors = this.attachedConnectors.slice();
        item.style = <Style> this.style.clone();
        item.styleText = <TextStyle> this.styleText.clone();
        item.zIndex = this.zIndex;

        item.container = this.container;
    }
    getConnectionPointPosition(index: number, targetPoint?: Point): Point {
        return this.getConnectionPoint(index, targetPoint).toPoint();
    }
    protected getConnectionPoint(index: number, targetPoint?: Point): ConnectionPoint {
        if(index < 0 && targetPoint)
            index = this.getNearestConnectionPoint(targetPoint);
        const connectionPoints = this.getConnectionPoints();
        return connectionPoints[index] || connectionPoints[0];
    }
    getNearestConnectionPoint(targetPoint: Point): number {
        let distance = Number.MAX_VALUE;
        let result: number;
        this.getConnectionPoints().forEach((pt, index) => {
            const ptDistance = Metrics.euclideanDistance(pt, targetPoint);
            if(ptDistance < distance) {
                distance = ptDistance;
                result = index;
            }
        });
        return result;
    }
    getConnectionPointIndex(side: ConnectionPointSide): number {
        const points = this.getConnectionPoints();
        return points.reduce((prevIndex, pt, index) => {
            if(side === ConnectionPointSide.North && pt.y < points[prevIndex].y)
                return index;
            if(side === ConnectionPointSide.South && pt.y > points[prevIndex].y)
                return index;
            if(side === ConnectionPointSide.West && pt.x < points[prevIndex].x)
                return index;
            if(side === ConnectionPointSide.East && pt.x > points[prevIndex].x)
                return index;
            return prevIndex;
        }, 0);
    }
    getConnectionPointSideByIndex(index: number, targetPoint?: Point): ConnectionPointSide {
        const point = this.getConnectionPoint(index, targetPoint);
        return this.getConnectionPointSide(point, targetPoint);
    }
    getConnectionPointIndexForSide(side: ConnectionPointSide): number {
        return side;
    }

    abstract get rectangle(): Rectangle;

    get enableText() { return true; }
    get allowEditText() { return true; }

    get hasTemplate() { return false; }

    get enableChildren() { return false; }
    get isLocked() { return this.locked || (this.container && this.container.isLocked); }

    get allowResizeHorizontally() { return false; }
    get allowResizeVertically() { return false; }

    get strokeWidth() { return this.style.strokeWidth; }

    abstract getConnectionPoints(): ConnectionPoint[];
    abstract getConnectionPointSide(point: ConnectionPoint, targetPoint?: Point): ConnectionPointSide;
    abstract toNative(units?: DiagramUnit): INativeItem;

    intersectedByRect(rect: Rectangle): boolean {
        return Rectangle.areIntersected(this.rectangle, rect);
    }
}
