/*
 * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
 *
 * http://rtsys.informatik.uni-kiel.de/kieler
 *
 * Copyright 2019-2024 by
 * + Kiel University
 *   + Department of Computer Science
 *     + Real-Time and Embedded Systems Group
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 */

import { KEdge, KGraphData, KNode, SKGraphElement } from '@kieler/klighd-interactive/lib/constraint-classes'
import {
    boundsFeature,
    layoutContainerFeature,
    moveFeature,
    popupFeature,
    RectangularPort,
    RGBColor,
    selectFeature,
    SLabelImpl,
    SModelElementImpl,
} from 'sprotty'
import { Bounds, Point } from 'sprotty-protocol'

export const NODE_TYPE = 'node'
export const EDGE_TYPE = 'edge'
export const PORT_TYPE = 'port'
export const LABEL_TYPE = 'label'

/**
 * Represents the Sprotty version of its java counterpart in KLighD.
 */
export class SKNode extends KNode {
    hasFeature(feature: symbol): boolean {
        return (
            feature === selectFeature ||
            feature === boundsFeature ||
            feature === layoutContainerFeature ||
            (feature === moveFeature &&
                (this.parent as SKNode).properties &&
                ((this.parent as SKNode).properties['org.eclipse.elk.interactiveLayout'] as boolean)) ||
            feature === popupFeature
        )
    }
}

/**
 * Represents the Sprotty version of its java counterpart in KLighD.
 */
export class SKPort extends RectangularPort implements SKGraphElement {
    trace?: string

    data: KGraphData[]

    areChildAreaChildrenRendered = false

    areNonChildAreaChildrenRendered = false

    hasFeature(feature: symbol): boolean {
        return feature === selectFeature || feature === boundsFeature || feature === popupFeature
    }

    properties: Record<string, unknown>
}

/**
 * Represents the Sprotty version of its java counterpart in KLighD.
 */
export class SKLabel extends SLabelImpl implements SKGraphElement {
    trace?: string

    data: KGraphData[]

    areChildAreaChildrenRendered = false

    areNonChildAreaChildrenRendered = false

    hasFeature(feature: symbol): boolean {
        // The boundsFeature here is additionally needed because bounds of labels need to be
        // estimated during the estimateTextBounds action.
        return feature === selectFeature || feature === boundsFeature || feature === popupFeature
    }

    properties: Record<string, unknown>
}

/**
 * Represents the Sprotty version of its java counterpart in KLighD.
 */
export class SKEdge extends KEdge {
    hasFeature(feature: symbol): boolean {
        return feature === selectFeature || feature === popupFeature
    }

    properties: Record<string, unknown>
}

/**
 * Element to define styles without attaching them to a specific rendering.
 * Represents its java counterpart in KLighD.
 */
export interface KStyleHolder {
    id: string
    styles: KStyle[]
}

/**
 * Abstract class to define members of a shapeType.
 * Represents its java counterpart in KLighD.
 */
export interface KRendering extends KGraphData, KStyleHolder {
    actions: KAction[]

    properties: Record<string, unknown>

    /** If this rendering is used as a clip rendering. Will not be set in the model and is to be used during rendering only. */
    isClipRendering?: boolean
}

/**
 * Define a child area inside of a rendering to force children being placed inside the defined area.
 * Represents its java counterpart in KLighD.
 */
export type KChildArea = KRendering

/**
 * KRendering that can have Children.
 * Represents its java counterpart in KLighD.
 */
export interface KContainerRendering extends KRendering {
    children: KRendering[]
}

/**
 * Draws an arc. Needs the startingAngle of the arc (0° = rightmost vertical line) on an ellipse and the angle the arc should cover (counterclockwise on the same ellipse).
 * Represents its java counterpart in KLighD.
 */
export interface KArc extends KContainerRendering {
    startAngle: number
    arcAngle: number
    arcType: Arc
    test: KPosition
}

/**
 * Represents its java counterpart in KLighD.
 */
export interface KCustomRendering extends KContainerRendering {
    className: string
    bundleName: string
    figureObject: Record<string, unknown>
}

/**
 * Define an ellipse shape that fits inside the space defined (a) by the node it is attached to or (b) by the placementData that is attached to the rendering.
 * Represents its java counterpart in KLighD.
 */
export type KEllipse = KContainerRendering

/**
 * Use an image instead of defining the renderings completely by yourself.
 * Represents its java counterpart in KLighD.
 */
export interface KImage extends KContainerRendering {
    bundleName: string
    imagePath: string
    imageObject: Record<string, unknown>
    clipShape: KRendering
}

/**
 * Creates a polyline between two or more points.
 * Represents its java counterpart in KLighD.
 */
export interface KPolyline extends KContainerRendering {
    points: KPosition[]
    junctionPointRendering: KRendering
}

/**
 * Creates a polygon based on a list of points. The polygon is a closed figure (last point = first point) even when not defined explicitly.
 * Represents its java counterpart in KLighD.
 */
export type KPolygon = KPolyline

/**
 * A polyline with rounded corners at its bendpoints.
 * Represents its java counterpart in KLighD.
 */
export interface KRoundedBendsPolyline extends KPolyline {
    bendRadius: number
}

/**
 * Creates a rounded edge.
 * Represents its java counterpart in KLighD.
 */
export type KSpline = KPolyline

/**
 * Define a rectangle by adding the topLeft and bottomRight coordinates.
 * Represents its java counterpart in KLighD.
 */
export type KRectangle = KContainerRendering

/**
 * The rounded rectangle is used to create a rectangle with rounded corners. Corner width and height need to be passed in order to define the style of the corners.
 * Represents its java counterpart in KLighD.
 */
export interface KRoundedRectangle extends KContainerRendering {
    cornerWidth: number
    cornerHeight: number
}

/**
 * References an already defined rendering to make redefining unnecessary.
 * Represents its java counterpart in KLighD.
 */
export interface KRenderingRef extends KRendering {
    rendering: KRendering
}

/**
 * Display text. Text can be positioned by adding Horizontal or VerticalAlignment and can be clipped if there is not enough space to display all of it without overlapping other
 * elements.
 * Represents its java counterpart in KLighD.
 */
export interface KText extends KRendering {
    text: string
    cursorSelectable: boolean
    editable: boolean
}

/**
 * Instances of this class may be employed in @see KGraphElement for accommodating
 * @see KRendering that are shared by multiple other @see KGraphElement
 * and referenced by means of @see KRenderingRef .<br>
 * A @see KRenderingRef can only represent a KRendering that is listed in the library here.
 * Represents its java counterpart in KLighD.
 */
export interface KRenderingLibrary extends KGraphData {
    renderings: KStyleHolder[]
}

/**
 * Performs action (ID) on event (@see Trigger ).
 * Represents its java counterpart in KLighD.
 */
export interface KAction {
    actionId: string
    trigger: Trigger
    altPressed: ModifierState
    ctrlCmdPressed: ModifierState
    shiftPressed: ModifierState
}

/**
 * The state of a modifier that it has to be in in order for some action to be performed.
 */
export enum ModifierState {
    DONT_CARE = 0,
    PRESSED = 1,
    NOT_PRESSED = 2,
}

/**
 * The literals mirror the constants java.awt.geom.Arc2D#OPEN, java.awt.geom.Arc2D#CHORD, and java.awt.geom.Arc2D#PIE.
 * This is to be leveraged in implementation, so be careful while modifying this enumeration.
 * Represents its java counterpart in KLighD.
 */
export enum Arc {
    /**
     * Plain arc without any closing line connection from end to beginning.
     */
    OPEN = 0,
    /**
     * Arc with a straight closing line connection from end to beginning via the arc's center.
     */
    CHORD = 1,
    /**
     * Arc with a straight closing line connection from end to beginning.
     */
    PIE = 2,
}

/*
 * Used to set an absolute Position of a single point by defining x and y coordinates of this point relative to the parent.
 * The position can be set with absolute values or relative to the parent dimensions.
 * Represents its java counterpart in KLighD.
 */
export interface KPosition {
    x: KXPosition // TODO: has <?> in java
    y: KYPosition
}

/**
 * Define an x-position by setting absolute and relative position respective to a parent rendering.
 * Both parameters are always included in the calculation of the resulting position. See Subtypes for formula.
 * Can overlap the parent by setting negative values.
 * Represents its java counterpart in KLighD.
 */
export interface KXPosition {
    type: string
    absolute: number
    relative: number
}

/**
 * Defines a position starting at the leftmost point of the parent rendering.
 * pos = (L+absolute) + (R-L)*relative = (R-absolute) - widthOfParent*relative
 * Represents its java counterpart in KLighD.
 */
export type KLeftPosition = KXPosition

/**
 * Defines a position starting at the rightmost point R of the parent rendering.
 * pos = (R-absolute) - (R-L)*relative = (R-absolute) - widthOfParent*relative
 * Represents its java counterpart in KLighD.
 */
export type KRightPosition = KXPosition

/**
 * Define an y-position by setting absolute and relative position respective to a parent rendering.
 * Both parameters are always included in the calculation of the resulting position. See Subtypes for formula.
 * Can overlap the parent by setting negative values.
 * Represents its java counterpart in KLighD.
 */
export interface KYPosition {
    type: string
    absolute: number
    relative: number
}

/**
 * Defines a position starting at the highest point H of the parent rendering.
 * pos = (H+absolute) + (B-H)*relative
 * pos = (H+absolute) + heightOfParent*relative
 * Represents its java counterpart in KLighD.
 */
export type KTopPosition = KYPosition

/**
 * Defines a position starting at the bottom point B of the parent rendering.
 * pos = (B-absolute) - (B-H-absolute)*relative
 * pos = (B-absolute) - heightOfParent*relative
 * Represents its java counterpart in KLighD.
 */
export type KBottomPosition = KYPosition

/**
 * Represents its java counterpart in KLighD.
 */
export enum HorizontalAlignment {
    LEFT = 0,
    CENTER = 1,
    RIGHT = 2,
}

/**
 * Represents its java counterpart in KLighD.
 */
export enum VerticalAlignment {
    TOP = 0,
    CENTER = 1,
    BOTTOM = 2,
}

/**
 * Trigger presets to determine when to execute actions.
 * Represents its java counterpart in KLighD.
 */
export enum Trigger {
    /**
     * Fires on a left button's single click.
     * Note: Corresponding actions are not fired on the first click of a double, tripple, ... click.
     * Thus, triggering the actions is delayed by the system wide double click period for assuring the absence of subsequent clicks.
     * TODO: check if this is also true on the client!
     */
    SINGLECLICK = 0,
    /**
     * Fires on left button's double (and more) click(s).
     */
    DOUBLECLICK = 1,
    /**
     * Fires on left button's first click regardless if more clicks follow within the system wide double click period.
     */
    SINGLE_OR_MULTICLICK = 2,
    /**
     * Fires on middle button's single click.
     * Note: Corresponding actions are not fired on the first click of a double, tripple, ... click.
     * Thus, triggering the actions is delayed by the system wide double click period for assuring the absence of subsequent clicks.
     */
    MIDDLE_SINGLECLICK = 3,
    /**
     * Fires on middle button's double (and more) click(s).
     */
    MIDDLE_DOUBLECLICK = 4,
    /**
     * Fires on middle button's first click regardless if more clicks follow within the system wide double click period.
     */
    MIDDLE_SINGLE_OR_MULTICLICK = 5,
}

/**
 * Adds additional StyleInformation to a rendering.
 * Can be set to propagate to children to make redefining styles unnecessary.
 * Represents its java counterpart in KLighD.
 */
export interface KStyle {
    type: string
    propagateToChildren: boolean
    modifierId?: string
    selection: boolean
}

/**
 * Defines the alphaChannel and Color of an Object.
 * Represents its java counterpart in KLighD.
 */
export interface KColoring extends KStyle {
    color: RGBColor
    alpha: number
    targetColor?: RGBColor
    targetAlpha?: number
    gradientAngle: number
}

/**
 * Defines the BackgroundColor and its alphaChannel of a rendering.
 * Represents its java counterpart in KLighD.
 */
export type KBackground = KColoring

/**
 * Defines the ForegroundColor and its alphaChannel of a rendering.
 * Represents its java counterpart in KLighD.
 */
export type KForeground = KColoring

/**
 * FontStyle to determine whether to draw it bold or not.
 * Represents its java counterpart in KLighD.
 */
export interface KFontBold extends KStyle {
    bold: boolean
}

/**
 * FontStyle to determine whether to draw it italic or not.
 * Represents its java counterpart in KLighD.
 */
export interface KFontItalic extends KStyle {
    italic: boolean
}

/**
 * FontStyle to determine a desired font.
 * Represents its java counterpart in KLighD.
 */
export interface KFontName extends KStyle {
    name: string
}

/**
 * FontStyle to determine the size of the font.
 * Represents its java counterpart in KLighD.
 */
export interface KFontSize extends KStyle {
    size: number
    scaleWithZoom: boolean
}

/**
 * Represents its java counterpart in KLighD.
 */
export interface KHorizontalAlignment extends KStyle {
    horizontalAlignment: HorizontalAlignment
}

/**
 * Defines whether an object is visible or not.
 * Represents its java counterpart in KLighD.
 */
export interface KInvisibility extends KStyle {
    invisible: boolean
}

/**
 * Implements different line ending styles.
 * Represents its java counterpart in KLighD.
 */
export interface KLineCap extends KStyle {
    lineCap: LineCap
}

/**
 * Represents its java counterpart in KLighD.
 */
export interface KLineJoin extends KStyle {
    lineJoin: LineJoin
    miterLimit: number
}

/**
 * Defines the line style of a rendering by setting one of the available values of the LineStyle enumeration.
 * 'dashPattern' and 'dashOffset' are evaluated if and only if the literal 'CUSTOM' is chosen.
 * Represents its java counterpart in KLighD.
 */
export interface KLineStyle extends KStyle {
    lineStyle: LineStyle
    dashPattern?: number[]
    dashOffset: number
}

/**
 * Specifies a lineWidth for a rendering.
 * Represents its java counterpart in KLighD.
 */
export interface KLineWidth extends KStyle {
    lineWidth: number
}

/**
 * Specifies the (clockwise) rotation of the corresponding KRendering.
 * Represents its java counterpart in KLighD.
 */
export interface KRotation extends KStyle {
    rotation: number
    rotationAnchor: KPosition
}

/**
 * Represents its java counterpart in KLighD.
 */
export interface KShadow extends KStyle {
    xOffset: number
    yOffset: number
    blur: number
    color: RGBColor
}

/**
 * Special KStyle allowing to reference the styles of another KRendering or KStyleHolder in general.
 * Represents its java counterpart in KLighD.
 */
export interface KStyleRef extends KStyle {
    styleHolder: KStyleHolder
    // referencedTypes: Class<KStyle>
}

/**
 * FontStyle to add a strikeout to an text element.
 * Represents its java counterpart in KLighD.
 */
export interface KTextStrikeout extends KStyle {
    struckOut: boolean
    color: RGBColor
}

/**
 * FontStyle to add an underline to an text element.
 * Represents its java counterpart in KLighD.
 */
export interface KTextUnderline extends KStyle {
    underline: Underline
    color: RGBColor
}

/**
 * Represents its java counterpart in KLighD.
 */
export interface KVerticalAlignment extends KStyle {
    verticalAlignment: VerticalAlignment
}

/**
 * LineCapStyles analog to SWT LineCapStyles.
 * Represents its java counterpart in KLighD.
 */
export enum LineCap {
    CAP_FLAT = 0,
    CAP_ROUND = 1,
    CAP_SQUARE = 2,
}

/**
 * Represents its java counterpart in KLighD.
 */
export enum LineJoin {
    JOIN_MITER = 0,
    JOIN_ROUND = 1,
    JOIN_BEVEL = 2,
}

/**
 * LineStyles analog to SWT LineStyles.
 * Represents its java counterpart in KLighD.
 */
export enum LineStyle {
    SOLID = 0,
    DASH = 1,
    DOT = 2,
    DASHDOT = 3,
    DASHDOTDOT = 4,
    CUSTOM = 5,
}

/**
 * The style of an underline. Analog to SWT Underline.
 * Represents its java counterpart in KLighD.
 */
export enum Underline {
    NONE = 0,
    SINGLE = 1,
    DOUBLE = 2,
    ERROR = 3,
    SQUIGGLE = 4,
    LINK = 5,
}

/**
 * A data holder class for the result of evaluating a decorator.
 * Represents its java counterpart in KLighD.
 */
export interface Decoration {
    origin: Point
    bounds: Bounds
    rotation: number
}

// ----------- Rendering Class names ----------- //
export const K_RENDERING_REF = 'KRenderingRefImpl'
export const K_RENDERING_LIBRARY = 'KRenderingLibraryImpl'
export const K_CHILD_AREA = 'KChildAreaImpl'
export const K_CONTAINER_RENDERING = 'KContainerRenderingImpl'
export const K_ARC = 'KArcImpl'
export const K_CUSTOM_RENDERING = 'KCustomRenderingImpl'
export const K_ELLIPSE = 'KEllipseImpl'
export const K_IMAGE = 'KImageImpl'
export const K_POLYLINE = 'KPolylineImpl'
export const K_POLYGON = 'KPolygonImpl'
export const K_ROUNDED_BENDS_POLYLINE = 'KRoundedBendsPolylineImpl'
export const K_SPLINE = 'KSplineImpl'
export const K_RECTANGLE = 'KRectangleImpl'
export const K_ROUNDED_RECTANGLE = 'KRoundedRectangleImpl'
export const K_TEXT = 'KTextImpl'

/**
 * Returns if the given parameter is a KRendering.
 * instanceof cannot be used, because every rendering received by the server is typed as KGraphData and the real type can only be inferred using the type attribute.
 * @param test The potential KRendering.
 */
export function isRendering(test: KGraphData): test is KRendering {
    const { type } = test
    return (
        type === K_RENDERING_REF ||
        type === K_CHILD_AREA ||
        type === K_CONTAINER_RENDERING ||
        type === K_ARC ||
        type === K_CUSTOM_RENDERING ||
        type === K_ELLIPSE ||
        type === K_IMAGE ||
        type === K_POLYLINE ||
        type === K_POLYGON ||
        type === K_ROUNDED_BENDS_POLYLINE ||
        type === K_SPLINE ||
        type === K_RECTANGLE ||
        type === K_ROUNDED_RECTANGLE ||
        type === K_TEXT
    )
}

/**
 * Returns if the given parameter is a KContainerRendering.
 * @param test The potential KContainerRendering.
 */
export function isContainerRendering(test: KGraphData): test is KContainerRendering {
    const { type } = test
    return (
        type === K_CONTAINER_RENDERING ||
        type === K_ARC ||
        type === K_CUSTOM_RENDERING ||
        type === K_ELLIPSE ||
        type === K_IMAGE ||
        type === K_POLYLINE ||
        type === K_POLYGON ||
        type === K_ROUNDED_BENDS_POLYLINE ||
        type === K_SPLINE ||
        type === K_RECTANGLE ||
        type === K_ROUNDED_RECTANGLE
    )
}

/**
 * Returns if the given parameter is a KPolyline.
 * @param test The potential KPolyline.
 */
export function isPolyline(test: KGraphData): test is KPolyline {
    const { type } = test
    return type === K_POLYLINE || type === K_POLYGON || type === K_ROUNDED_BENDS_POLYLINE || type === K_SPLINE
}

/**
 * Returns if the given parameter is a KText.
 * @param test The potential KText
 */
export function isKText(test: KGraphData): test is KText {
    const { type } = test
    return type === K_TEXT
}

/**
 * Returns if the given parameter is an SKGraphElement.
 * @param test The potential SKGraphElement.
 */
export function isSKGraphElement(test: unknown): test is SKGraphElement {
    return (
        test instanceof SModelElementImpl &&
        (test as any).areChildAreaChildrenRendered !== undefined &&
        (test as any).areNonChildAreaChildrenRendered !== undefined &&
        (test as any).opacity !== undefined &&
        (test as any).data !== undefined
    )
}
