import Tool from "./tools/tool";
import UndoTool from "./tools/undotool";
import RedoTool from "./tools/redotool";
import SelectTool from "./tools/selecttool";
import DeleteTool from "./tools/deletetool";
import AddNodeTool from "./tools/addnodetool";
import ConnectTool from "./tools/connecttool";
import SettingsTool from "./tools/settingstool";
import SaveTool from "./tools/savetool";
import Toolbar from "./toolbar";
import { DRAG_THRESHOLD_2D } from "../../config";
import { GraphElement } from "./structures/graph/graphelement";
import { Node } from "./structures/graph/node";

export const TOOLS = {
    undo: new UndoTool("undo"),
    redo: new RedoTool("redo"),
    select: new SelectTool("select"),
    delete: new DeleteTool("delete"),
    addnode: new AddNodeTool("addnode"),
    connect: new ConnectTool("connect"),
    settings: new SettingsTool("settings"),
    save: new SaveTool("save"),
};

export const CONTEXT = {
    node: "node",
    link: "link",
    mixed: "mixed",
    nothing: "nothing",
};

export class State extends Tool {
    display: Toolbar;
    tool: Tool;
    previousTool: Tool;
    selectedItem: GraphElement;
    selectedItems: Set<GraphElement>;
    itemsContext: any;
    labelVisible: boolean;
    keyStates: any;

    constructor() {
        super("State");

        this.display = new Toolbar(TOOLS);

        this.tool = undefined;
        this.setTool(TOOLS.select);

        // Shared variables
        this.selectedItem = undefined;
        this.selectedItems = new Set();
        this.itemsContext = CONTEXT.nothing;
        this.labelVisible = true;

        this.keyStates = {};
    }

    setTool(tool: Tool) {
        if (this.tool === tool) {
            return;
        }

        if (this.tool !== undefined) {
            this.tool.deactivateTool(tool);
        }

        this.previousTool = this.tool;
        this.tool = tool;
        this.display.setSelectedTool(tool);

        if (this.tool !== undefined) {
            this.tool.activateTool();
        }
    }

    setSelectedItem(item: GraphElement) {
        this.selectedItem = item;
    }

    addSelectedItem(item: GraphElement) {
        this.selectedItems.add(item);
    }

    addSelectedItems(items: GraphElement) {
        Object.values(items).forEach((item) => {
            this.selectedItems.add(item);
        });
    }

    removeSelectedItem(item: GraphElement) {
        this.selectedItems.delete(item);
    }

    clearSelectedItems() {
        this.selectedItems.clear();
        this.itemsContext = CONTEXT.nothing;
    }

    onNodeClick(node: GraphElement) {
        this.tool.onNodeClick(node);
    }

    onNodeDragEnd(node: any, translate: any) {
        // Handle as click event, if drag distance under a certain threshold
        const distanceDragged = Math.sqrt(
            Math.pow(translate.x, 2) + Math.pow(translate.y, 2)
        );
        if (distanceDragged < DRAG_THRESHOLD_2D) {
            this.onNodeClick(node);
            return;
        } else {
            this.tool.onNodeDragEnd(node, translate);
        }
    }

    onLinkClick(link: any) {
        this.tool.onLinkClick(link);
    }

    linkColor(link: any) {
        return "red";
    }

    nodeColor(node: any) {
        return "black";
    }

    onKeyDown(key: any) {
        const id = this.getKeyId(key);
        const previous = this.keyStates[id];

        this.keyStates[id] = true;

        if (previous !== true) {
            this.tool.onKeyDown(key);
        }
    }

    onKeyUp(key: any) {
        const id = this.getKeyId(key);
        const previous = this.keyStates[id];

        this.keyStates[id] = false;

        if (previous !== false) {
            this.tool.onKeyUp(key);
        }
    }

    getKeyId(key: any) {
        return key.keyCode;
    }

    nodeCanvasObject(node: any, ctx: any, globalScale: any) {
        const toolValue = this.tool.nodeCanvasObject(node, ctx);

        if (toolValue !== undefined) {
            return toolValue;
        }

        // TODO: Clean up function

        // add ring just for highlighted nodes
        if (this.selectedItem === node || this.selectedItems.has(node)) {
            ctx.beginPath();
            ctx.arc(node.x, node.y, 4 * 0.6, 0, 2 * Math.PI, false);
            ctx.fillStyle = this.selectedItem === node ? "red" : "white";
            ctx.fill();
        }

        // Draw image
        const imageSize = 12;
        if (node.icon !== undefined) {
            const img = new Image();
            img.src = node.icon.link;

            ctx.drawImage(
                img,
                node.x - imageSize / 2,
                node.y - imageSize / 2,
                imageSize,
                imageSize
            );
        }

        // Draw label
        if (this.labelVisible) {
            const label = node.label;
            const fontSize = 11 / globalScale;
            ctx.font = `${fontSize}px Sans-Serif`;
            const textWidth = ctx.measureText(label).width;
            const bckgDimensions = [textWidth, fontSize].map(
                (n) => n + fontSize * 0.2
            ); // some padding

            const nodeHeightOffset = imageSize / 3 + bckgDimensions[1];
            ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
            ctx.fillRect(
                node.x - bckgDimensions[0] / 2,
                node.y - bckgDimensions[1] / 2 + nodeHeightOffset,
                ...bckgDimensions
            );

            ctx.textAlign = "center";
            ctx.textBaseline = "middle";
            ctx.fillStyle = "white";
            ctx.fillText(label, node.x, node.y + nodeHeightOffset);
        }

        // TODO: Render label as always visible
    }

    nodePointerAreaPaint(node: any, color: any, ctx: any) {
        const toolValue = this.tool.nodePointerAreaPaint(node, color, ctx);

        if (toolValue !== undefined) {
            return toolValue;
        }

        const imageSize = 12;
        ctx.fillStyle = color;
        ctx.fillRect(
            node.x - imageSize / 2,
            node.y - imageSize / 2,
            imageSize,
            imageSize
        ); // draw square as pointer trap
    }

    nodeCanvasObjectMode(node: any) {
        const toolValue = this.tool.nodeCanvasObjectMode(node);

        if (toolValue !== undefined) {
            return toolValue;
        }

        return "after";
    }

    linkCanvasObjectMode(link: any) {
        const toolValue = this.tool.linkCanvasObjectMode(link);

        if (toolValue !== undefined) {
            return toolValue;
        }

        return "replace";
    }

    linkCanvasObject(link: any, ctx: any, globalScale: any) {
        const toolValue = this.tool.linkCanvasObject(link, ctx);

        if (toolValue !== undefined) {
            return toolValue;
        }

        // Links already initialized?
        if (link.source.x === undefined) {
            return undefined;
        }

        // Draw gradient link
        const gradient = ctx.createLinearGradient(
            link.source.x,
            link.source.y,
            link.target.x,
            link.target.y
        );
        // Have reversed colors
        // Color at source node referencing the target node and vice versa
        gradient.addColorStop("0", link.target.type.color);
        gradient.addColorStop("1", link.source.type.color);

        ctx.beginPath();
        ctx.moveTo(link.source.x, link.source.y);
        ctx.lineTo(link.target.x, link.target.y);
        ctx.strokeStyle = gradient;
        ctx.stroke();

        // Only render strokes on last link
        // var lastLink = graph.data[Graph.GRAPH_LINKS][graph.data[Graph.GRAPH_LINKS].length - 1];
        // if (link === lastLink) {
        //     ctx.stroke();
        // }

        return undefined;
    }

    linkWidth(link: any) {
        const toolValue = this.tool.linkWidth(link);

        if (toolValue !== undefined) {
            return toolValue;
        }

        return this.isLinkHighlighted(link) ? 2 : 1;
    }

    linkDirectionalParticles() {
        const toolValue = this.tool.linkDirectionalParticles();

        if (toolValue !== undefined) {
            return toolValue;
        }

        return 4;
    }

    linkDirectionalParticleWidth(link: any) {
        const toolValue = this.tool.linkDirectionalParticleWidth(link);

        if (toolValue !== undefined) {
            return toolValue;
        }

        return this.isLinkHighlighted(link) ? 4 : 0;
    }

    onBackgroundClick(event: any, positions: any) {
        this.tool.onBackgroundClick(event, positions);
    }

    redraw() {
        this.display.setSelectedTool(this.tool);
    }

    isLinkHighlighted(link: any) {
        return false;   // Code after that is causing issues

        if (this.selectedItem === link) {
            return true;
        }

        if (this.selectedItem.node) {
            return (this.selectedItem as Node).links.includes(link);
        }

        return false;
    }

    setLabelVisibility(visibility: boolean) {
        this.labelVisible = visibility;
    }
}