import { Graph } from "./graph";
import { GraphElement } from "./graphelement";
import { NodeType } from "./nodetype";
import { Link } from "./link";

interface NodeProperties {
    name: string;
    description?: string;
    icon?: string;
    banner?: string;
    video?: string;
    references?: string[];
}

export interface NodeData extends NodeProperties {
    /**
     * This interfaces provides a data representation for a simple "flat" node without object pointers.
     * Can be used to store nodes in JSON format.
     */
    id: number;
    type: string;
}

// Based on https://github.com/d3/d3-force#simulation_nodes
export interface SimNodeData extends NodeData {
    /**
     * This interface serves as a data representation for the history of the editor.
     * Same as the JSON representation + additional parameters related to the simulation.
     * This ensures that nodes from the history can are restored with the same visual state.
     */
    index: number;
    x: number;
    y: number;
    vx: number;
    vy: number;
    fx: number;
    fy: number;
}

export interface GraphNode extends NodeProperties {
    /**
     * Node representation in a Graph. Contains values for easy traversal and force graph simulation properties.
     */
    id: number;
    type: NodeType;

    // Properties used for graph traversal
    neighbors: Node[];
    links: Link[];

    // Properties used by the force graph simulation
    index?: number;
    x?: number;
    y?: number;
    z?: number;
    vx?: number;
    vy?: number;
    vz?: number;
    fx?: number;
    fy?: number;
    fz?: number;
}

export class Node
    extends GraphElement<NodeData, SimNodeData>
    implements GraphNode
{
    public name: string;
    public description: string;
    public type: NodeType;
    public icon: string;
    public banner: string;
    public video: string;
    public references: string[];

    public neighbors: Node[];
    public links: Link[];

    // These parameters will be added by the force graph implementation
    public index?: number;
    public x?: number;
    public y?: number;
    public z?: number;
    public vx?: number;
    public vy?: number;
    public vz?: number;
    public fx?: number;
    public fy?: number;
    public fz?: number;

    constructor(graph?: Graph) {
        super(0, graph);
        this.neighbors = [];
        this.links = [];
    }

    public setType(typeId: number) {
        const newType = this.graph.nameToObjectGroup.get(String(typeId)); // TODO

        // Exists?
        if (newType === undefined) {
            return;
        }

        this.type = newType;
    }

    public delete() {
        return this.graph.deleteNode(this.id);
    }

    /**
     * Connects this node to a given node. Only works if they are in the same graph.
     * @param node Other node to connect.
     * @returns The created link, if successful, otherwise undefined.
     */
    public connect(node: Node): Link {
        if (this.graph !== node.graph) {
            throw new Error("The connected nodes are not on the same graph!");
        }

        return this.graph.createLink(this.id, node.id);
    }

    public toJSONSerializableObject(): NodeData {
        return {
            id: this.id,
            name: this.name,
            description: this.description,
            icon: this.icon,
            banner: this.banner,
            video: this.video,
            references: this.references,
            type: this.type.name,
        };
    }

    public toHistorySerializableObject(): SimNodeData {
        return {
            ...this.toJSONSerializableObject(),
            index: this.index,
            x: this.x,
            y: this.y,
            vx: this.vx,
            vy: this.vy,
            fx: this.fx,
            fy: this.fy,
        };
    }

    public fromSerializedObject(data: NodeData | SimNodeData): Node {
        Object.assign(this, data);
        this.type = undefined; // Remove string type again if undefined as it may be a string.
        return this;
    }

    public toString(): string {
        return this.name;
    }
}