const NODE_LABEL = "name";
const NODE_ID = "id";
const NODE_GROUP = "group";
const NODE_DESCRIPTION = "description";
const NODE_IMAGE = "image";

const LINK_SOURCE = "source";
const LINK_TARGET = "target";
const LINK_TYPE = "type";
const LINK_PARTICLE_COUNT = 4;

const GRAPH_NODES = "nodes";
const GRAPH_LINKS = "links";

const IMAGE_SIZE = 12;
const IMAGE_SRC = PLUGIN_PATH + "datasets/images/";

const LINK_PARAMS = [LINK_TYPE];
const NODE_PARAMS = [NODE_ID, NODE_LABEL, NODE_IMAGE, NODE_DESCRIPTION];

const JSON_CONFIG = PLUGIN_PATH + "datasets/aud1.json";

const STOP_PHYSICS_DELAY = 5000; // ms

const graph = {
    data: undefined,
    externUpdate: [],   // Register callbacks in this list

    update() {
        graph.externUpdate.forEach((fn) => fn());
    },

    deleteNode(nodeId) {
        // Delete node from nodes
        graph.data[GRAPH_NODES] = graph.data[GRAPH_NODES].filter(
            (n) => n[NODE_ID] !== nodeId
        );

        // Delete links with node
        graph.data[GRAPH_LINKS] = graph.data[GRAPH_LINKS].filter(
            (l) =>
                l[LINK_SOURCE][NODE_ID] !== nodeId &&
                l[LINK_TARGET][NODE_ID] !== nodeId
        );
    },

    stopPhysics() {
        graph.data[GRAPH_NODES].forEach((n) => {
            n.fx = n.x;
            n.fy = n.y;
        });
    },

    addIdentifiers() {
        graph.data[GRAPH_NODES].forEach((n) => {
            n.node = true;
            n.link = false;
        });
        graph.data[GRAPH_LINKS].forEach((l) => {
            l.node = false;
            l.link = true;
        });
    },

    deleteLink(sourceId, targetId) {
        // Only keep links, of one of the nodes is different
        graph.data[GRAPH_LINKS] = graph.data[GRAPH_LINKS].filter(
            (l) =>
                l[LINK_SOURCE][NODE_ID] !== sourceId ||
                l[LINK_TARGET][NODE_ID] !== targetId
        );
    },

    isLinkOnNode(link, node) {
        if (link === undefined || node === undefined) {
            return false;
        }

        if (link.link !== true || node.node !== true) {
            return false;
        }

        return (
            link[LINK_SOURCE][NODE_ID] === node[NODE_ID] ||
            link[LINK_TARGET][NODE_ID] === node[NODE_ID]
        );
    },

    existsLink(sourceId, targetId) {
        const links = graph.data[GRAPH_LINKS];

        for (var i = 0; i < links.length; i++) {
            var link = links[i];
            if (
                link[LINK_SOURCE][NODE_ID] === sourceId &&
                link[LINK_TARGET][NODE_ID] === targetId
            ) {
                return true;
            }
        }

        return false;
    },

    connectNodes(sourceId, targetIds) {
        targetIds.forEach((targetId) => {
            if (
                graph.existsLink(sourceId, targetId) ||
                graph.existsLink(targetId, sourceId)
            ) {
                return;
            }

            var link = {};

            link[LINK_SOURCE] = sourceId;
            link[LINK_TARGET] = targetId;

            graph.data[GRAPH_LINKS].push(link);
        });
    },

    getCleanData() {
        var cleanData = {};
        cleanData[GRAPH_LINKS] = [];
        cleanData[GRAPH_NODES] = [];

        graph.data[GRAPH_LINKS].forEach((link) =>
            cleanData[GRAPH_LINKS].push(graph.getCleanLink(link))
        );

        graph.data[GRAPH_NODES].forEach((node) =>
            cleanData[GRAPH_NODES].push(graph.getCleanNode(node))
        );

        console.log(cleanData);
        return cleanData;
    },

    getCleanNode(node) {
        var cleanNode = {};

        NODE_PARAMS.forEach((param) => {
            cleanNode[param] = node[param];
        });

        return cleanNode;
    },

    getCleanLink(link) {
        var cleanLink = {};

        // Source and target nodes
        // Node ids will be converted to complete node objects on running graphs, gotta convert back
        cleanLink[LINK_SOURCE] = link[LINK_SOURCE][NODE_ID];
        cleanLink[LINK_TARGET] = link[LINK_TARGET][NODE_ID];

        // Other parameters
        LINK_PARAMS.forEach((param) => {
            cleanLink[param] = link[param];
        });

        return cleanLink;
    },

    existsNodeId(nodeId) {
        var nodes = graph.data[GRAPH_NODES];
        for (var i = 0; i < nodes.length; i++) {
            if (nodes[i][NODE_ID] === nodeId) {
                return true;
            }
        }
        return false;
    },

    getUnusedNodeId() {
        var id;
        do {
            id = graph.getRandomString();
        } while (graph.existsNodeId(id));
        return id;
    },

    getRandomString(length = 8) {
        // Based on: https://stackoverflow.com/a/1349426/7376120
        var characters =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        var charactersLength = characters.length;

        var result = "";
        for (var i = 0; i < length; i++) {
            result += characters.charAt(
                Math.floor(Math.random() * charactersLength)
            );
        }
        return result;
    },

    addLink(sourceId, targetId, linkDetails = {}) {
        // Copy params
        var newLink = linkDetails;

        // Make sure the IDs exist
        if (
            sourceId === undefined ||
            targetId === undefined ||
            graph.existsNodeId(sourceId) === false ||
            graph.existsNodeId(targetId) === false
        ) {
            return;
        }


        // Make sure the link is unique
        if (graph.existsLink(sourceId, targetId)) {
            return;
        }

        newLink[LINK_SOURCE] = sourceId;
        newLink[LINK_TARGET] = targetId;

        // Basic node properties
        newLink.link = true;
        newLink.node = false;

        // Add node
        graph.data[GRAPH_LINKS].push(newLink);
        graph.update();

        return newLink;
    },

    addNode(nodeDetails) {
        // Copy params
        var newNode = nodeDetails;

        // Make sure the ID is set and unique
        if (newNode[NODE_ID] === undefined) {
            newNode[NODE_ID] = graph.getUnusedNodeId();
        } else if (graph.existsNodeId(newNode[NODE_ID])) {
            return;
        }

        // Basic node properties
        newNode.node = true;
        newNode.link = false;

        // Add node
        graph.data[GRAPH_NODES].push(newNode);
        graph.update();

        return newNode;
    },
};