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 * as Graph from "./graph";
import { DRAG_THRESHOLD_2D } from "../../config";
import { Editor } from "./components/editor";

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 {
    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) {
        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) {
        this.selectedItem = item;
    }

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

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

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

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

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

    onNodeDragEnd(node, translate) {
        // Handle as click event, if drag distance under a certain threshold
        var 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) {
        this.tool.onLinkClick(link);
    }

    linkColor(link) {
        return "red";
    }

    nodeColor(node) {
        return "black";
    }

    onKeyDown(key) {
        var id = this.getKeyId(key);
        var previous = this.keyStates[id];

        this.keyStates[id] = true;

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

    onKeyUp(key) {
        var id = this.getKeyId(key);
        var previous = this.keyStates[id];

        this.keyStates[id] = false;

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

    getKeyId(key) {
        return key.keyCode;
    }

    nodeCanvasObject(node, ctx, globalScale) {
        var 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
        if (node[Graph.NODE_IMAGE] !== undefined) {
            var path = node[Graph.NODE_IMAGE];

            if (!path.includes("/")) {
                path = Graph.IMAGE_SRC + path;
            }
            var img = new Image();
            img.src = path;

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

        // Draw label
        if (this.labelVisible) {
            const label = node[Graph.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 = Graph.IMAGE_SIZE / 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, color, ctx) {
        var toolValue = this.tool.nodePointerAreaPaint(node, color, ctx);

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

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

    nodeCanvasObjectMode(node) {
        var toolValue = this.tool.nodeCanvasObjectMode(node);

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

        return "after";
    }

    linkCanvasObjectMode(link) {
        var toolValue = this.tool.linkCanvasObjectMode(link);

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

        return "replace";
    }

    linkCanvasObject(link, ctx, globalScale) {
        var toolValue = this.tool.linkCanvasObject(link, ctx);

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

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

        // Draw gradient link
        var 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", Editor.globalGraph.getNodeColor(link.target));
        gradient.addColorStop("1", Editor.globalGraph.getNodeColor(link.source));

        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) {
        var toolValue = this.tool.linkWidth(link);

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

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

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

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

        return 4;
    }

    linkDirectionalParticleWidth(link) {
        var toolValue = this.tool.linkDirectionalParticleWidth(link);

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

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

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

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

    isLinkHighlighted(link) {
        return (
            this.selectedItem === link ||
            Editor.globalGraph.isLinkOnNode(link, this.selectedItem)
        );
    }

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