const highlightNodes = new Set();
const highlightLinks = new Set();
let hoverNode = null;
let Graph = null;
let firstTick = true;

const colorPallette = ['rgb(104, 169, 77)', 'rgb(102, 75, 154)', 'rgb(41, 171, 226)', 'rgb(224, 133, 35)',
    'rgb(214, 207, 126)', 'rgb(239, 65, 35)', 'rgb(255, 255, 255)'];
const edgeColors = {};
var graph_element = document.getElementById('3d-graph');

loadGraph();

async function loadGraph() {
    const dataUrl = plugin_path + 'datasets/aud1.json'
    const gData = await fetch(dataUrl).then(res => res.json())
    Graph = ForceGraph3D({extraRenderers: [new THREE.CSS2DRenderer(), new THREE.CSS3DRenderer()]})
    (document.getElementById('3d-graph'))
        .graphData(gData)
        .nodeLabel('id')
        .nodeAutoColorBy('group')
        .nodeColor(node => highlightNodes.has(node) ? node === hoverNode ? 'rgb(255,0,0,1)' : 'rgba(255,160,0,0.8)' : 'rgba(0,255,255,0.6)')
        .linkWidth(link => highlightLinks.has(link) ? 2 : 0.8)
        .onNodeClick(handleNodeClick)
        .onNodeHover(nodeHover)
        .onLinkHover(linkHover)
        .linkColor(linkColor)
        .linkOpacity(0.8)
        .nodeThreeObjectExtend(false)
        .nodeThreeObject(drawNode)
        .onEngineTick(loadComponents)
        .width(getWidth())
        .height(getHeight());
    //Graph.renderer().sortObjects = true;
}


function loadComponents() {
    if (firstTick) {
        mapEdgeColors();
        updateLinks();
        add_background();
        createLinkOverlay();
        createInfoOverlay();
        add_fullscreen_mode_button();
        add_fullscreen_Listener();
        window.onresize = resize_graph();
        firstTick = false;
    }
}

function resize_graph() {
    Graph.width(getWidth());
    Graph.height(getHeight());
}

function getWidth() {
    return document.getElementById('3d-graph').offsetWidth;
}

function getHeight() {
    return window.innerHeight - 200
}

function mapEdgeColors() {
    const linkClasses = getLinkClasses()
    for (let i = 0; i < linkClasses.length; i++) {
        edgeColors[linkClasses[i]] = colorPallette[i % colorPallette.length]
    }
}


function getLinkClasses() {
    const linkClasses = [];
    Graph.graphData().links.forEach(link => linkClasses.push(link.type));
    return [... new Set(linkClasses)];
}

function getCanvasDivNode() {
    const domNode = document.getElementById('3d-graph');
    return domNode.firstChild.firstChild.firstChild;
}

function drawNode(node) {
    // Draw node as label + image
    const nodeDiv = document.createElement('div');
    group = new THREE.Group();

    const labelDiv = document.createElement('div')
    labelDiv.textContent = node.name;
    labelDiv.style.color = node.color;
    labelDiv.className = 'node-label';
    nodeDiv.appendChild(labelDiv);

    const cssobj = new THREE.CSS3DSprite(nodeDiv);
    cssobj.scale.set(0.25, 0.25, 0.25);
    cssobj.position.set(0, -6, 0);
    group.add(cssobj)

    // Draw node circle image
    const textureLoader = new THREE.TextureLoader();
    const imageAlpha = textureLoader.load(plugin_path + 'datasets/images/alpha.png');
    let imageTexture = null;

    if ('image' in node) {
        imageTexture = textureLoader.load(plugin_path + 'datasets/images/' + node.image);
    } else {
        imageTexture = textureLoader.load(plugin_path + 'datasets/images/default.jpg');
    }

    const material = new THREE.SpriteMaterial({map: imageTexture, alphaMap: imageAlpha, transparent: true,
        alphaTest: 0.2, depthWrite:false, depthTest: false});
    const sprite = new THREE.Sprite(material);
    sprite.renderOrder = 999; // This may not be optimal. But it allows us to render the sprite on top of everything else.

    if ('image' in node) {
        sprite.scale.set(20, 20);
    } else {
        sprite.scale.set(5, 5);
    }

    group.add(sprite)

    return group;
}


function createInfoOverlay() {
    const sceneNode = getCanvasDivNode();
    const overlayNode = document.createElement('div');
    overlayNode.id = 'infoOverlay'
    overlayNode.className = 'detail-view';
    //overlayNode.innerText = 'Hello there!';
    sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2]);

    const close = document.createElement('div');
    close.innerHTML = '<p>&#10006;</p>';
    close.id = 'infoOverlayCloseButton';
    close.className = 'close-button';
    overlayNode.appendChild(close);

    const topArea = document.createElement('div');
    topArea.className = 'detail-view-top-area';
    overlayNode.appendChild(topArea);

    const nodeImage = document.createElement('img');
    nodeImage.id = 'infoOverlayImage';
    nodeImage.src = plugin_path + 'datasets/images/default.jpg';
    nodeImage.className = 'detail-view-image';
    topArea.appendChild(nodeImage);

    const headline = document.createElement('h2');
    headline.id = 'infoOverlayHeadline';
    headline.innerText = 'Default Text';
    headline.className = 'detail-view-headline';
    topArea.appendChild(headline);

    const textArea = document.createElement('div');
    textArea.className = 'detail-view-text-area';
    overlayNode.appendChild(textArea);

    const description = document.createElement('p');
    description.id = 'infoOverlayDescription';
    description.innerText = 'Default Text'
    description.setAttribute("overflow-y", "scroll");
    textArea.appendChild(description);

    const link_container = document.createElement('div');
    const links = document.createElement('div');
    const linkClasses = getLinkClasses();

    link_container.id = 'link_container';
    link_container.className = 'bottom container';
    links.className = 'tab';
    links.id = 'links';

    for(i = 0; i < linkClasses.length; i++){

        const linkType = linkClasses[i];
        const tablink = document.createElement('button');
        tablink.className = 'tablinks';
        tablink.id = linkType + "_id";
        tablink.innerHTML = linkType[0] + linkType[1] + linkType[2];
        tablink.style = "background-color: " + edgeColors[linkClasses[i]];
        tablink.setAttribute("onclick", "openTab(event, " + linkType + "_id)"); //unusual function call
        links.appendChild(tablink);

    }

    link_container.appendChild(links);

    for(i = 0; i < linkClasses.length; i++){

        const linkType = linkClasses[i];
        const tabcontent = document.createElement('div');
        tabcontent.className = 'tabcontent';
        tabcontent.id = linkType + "_id_content";
        tabcontent.style = "background-color: " + edgeColors[linkClasses[i]];
        link_container.appendChild(tabcontent);

    }

    overlayNode.appendChild(link_container);

    jQuery('#infoOverlayCloseButton').click(function () {
        jQuery('#infoOverlay').slideUp('fast');
    });

    //sceneNode.appendChild(overlayNode);

}


function createLinkOverlay() {
    const sceneNode = getCanvasDivNode();
    const overlayNode = document.createElement('div');
    overlayNode.className = 'link-overlay';
    //overlayNode.innerText = 'Hello there!';
    sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2])

    const linkClasses = getLinkClasses();
    const chars = Math.max.apply(Math, linkClasses.map(function (c) { return c.length; }));

    for (let i=0; i<linkClasses.length; i++) {
        const relation = document.createElement('div');
        relation.className = 'relation';
        relation.innerHTML = "<p>" + linkClasses[i] + "</p>";
        overlayNode.appendChild(relation);

        const colorStrip = document.createElement('div');
        colorStrip.className = 'rel-container';
        colorStrip.style = "background-color: " + edgeColors[linkClasses[i]] + "; width: " + 10 * chars + "px;";
        relation.appendChild(colorStrip);
    }
}

function add_fullscreen_mode_button() {
    const sceneNode = document.getElementById('3d-graph');
    const overlayNode = document.createElement('button');
    overlayNode.className = 'fullscreen_button';
    overlayNode.innerText = 'fullscreen';
    overlayNode.addEventListener("click", fullscreen_mode);
    sceneNode.appendChild(overlayNode);
}

function fullscreen_mode(){
    if(getCanvasDivNode().requestFullscreen) {
        getCanvasDivNode().requestFullscreen().catch();
    }
}

function resize_canvas() {
    if(document.fullscreenElement == getCanvasDivNode()) {
        Graph.height(screen.height);
        Graph.width(screen.width);
    } else {
        Graph.height(window.innerHeight - 200);
        Graph.width(getWidth());
    }
}

function add_fullscreen_Listener() {
    getCanvasDivNode().addEventListener("fullscreenchange", resize_canvas);
}

function add_background() {
    const sphereGeometry = new THREE.SphereGeometry(20000, 32, 32);
    //const planeGeometry = new THREE.PlaneGeometry(1000, 1000, 1, 1);
    const loader = new THREE.TextureLoader();
    //const planeMaterial = new THREE.MeshLambertMaterial({color: 0xFF0000, side: THREE.DoubleSide}); //THREE.BackSide
    const planeMaterial = new THREE.MeshBasicMaterial({map: loader.load(plugin_path + 'backgrounds/background_3.jpg'), side: THREE.DoubleSide}); //THREE.BackSide
    const mesh = new THREE.Mesh(sphereGeometry, planeMaterial);
    mesh.position.set(0, 0, 0);
    //mesh.rotation.set(0.5 * Math.PI, 0, 0);

    Graph.scene().add(mesh);
}


function updateLinks() {
    const gData = Graph.graphData()
    // cross-link node objects
    gData.links.forEach(link => {
        const a = link.source;
        const b = link.target;
        if (!a.neighbors) a.neighbors = [];
        if (!b.neighbors) b.neighbors = [];
        a.neighbors.push(b);
        b.neighbors.push(a);

        if (!a.links) a.links = [];
        if (!b.links) b.links = [];
        a.links.push(link);
        b.links.push(link);
    });
    Graph.graphData(gData)
}



function handleNodeClick(node) {
    focusOnNode(node);
    updateInfoOverlay(node);
}


function updateInfoOverlay(node) {
    jQuery('#infoOverlayHeadline').text(node.name);
    if ('image' in node) {
        jQuery('#infoOverlayImage').attr('src', plugin_path + 'datasets/images/' + node.image);
    } else {
        jQuery('#infoOverlayImage').attr('src', plugin_path + 'datasets/images/default.jpg');
    }

    if ('description' in node) {
        jQuery('#infoOverlayDescription').text(node.description);
    } else {
        jQuery('#infoOverlayDescription').text('Default Text');
    }

    updateTabs(node);

    jQuery('#infoOverlay').slideDown('fast');
}

function updateTabs(node){
    //tabs of the links to other nodes:
    //delete old nodes from tabcontent
    tabcontent = document.getElementsByClassName("tabcontent");
    for (i = 0; i < tabcontent.length; i++) {
        while (tabcontent[i].firstChild){
            tabcontent[i].removeChild(tabcontent[i].lastChild);
        }
    }
    //add new nodes to tabcontent
    for (i = 0; i < tabcontent.length; i++) {                                   //for every type of link
        node.links.forEach(link => {                                            //and for every link
            if(link.type + '_id_content' == tabcontent[i].id){                  //is checked if the type is equal
                const linkButton = document.createElement('div');      //and if so a new element is created
                linkButton.className = "link img";
                var node2;
                if(link.source == node){
                    node2 = link.target;
                }
                else if(link.target == node){
                    node2 = link.source;
                }
                linkButton.id = "linkButton_" + node2.id;
                const nodeID = node2.id;

                const linkImage = document.createElement('img');
                const linkTitle = document.createElement('div');

                if ('image' in node2) {
                    linkImage.src = plugin_path + 'datasets/images/' + node2.image;
                } else if(!('image' in node2) && ('id' in node2)) {
                    linkImage.src = plugin_path + 'datasets/images/default.jpg';
                }

                linkTitle.className = "link title";
                linkTitle.innerHTML = node2.name;

                //linkButton.appendChild(linkTitle);
                linkButton.appendChild(linkImage);

                linkButton.addEventListener('click', function (){focusOnNodeId(nodeID)});
                tabcontent[i].appendChild(linkButton);
            }
        });
    }
}

function focusOnNodeId(id) {
    const gData = Graph.graphData();

    gData.nodes.forEach(node => {
        if (node.id === id) {
            handleNodeClick(node);
        }
    })
}

function focusOnNode(node) {
    // Aim at node from outside it
    const distance = 250;
    const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z);

    Graph.cameraPosition(
        { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }, // new position
        node, // lookAt ({ x, y, z })
        1000  // ms transition duration
    );
}


function nodeHover(node) {

    // no state change
    if ((!node && !highlightNodes.size) || (node && hoverNode === node)) return;

    highlightNodes.clear();
    highlightLinks.clear();
    if (node) {
        highlightNodes.add(node);
        node.neighbors.forEach(neighbor => highlightNodes.add(neighbor));
        node.links.forEach(link => highlightLinks.add(link));
    }

    hoverNode = node || null;
    updateHighlight();
}


function linkHover(link) {
    highlightNodes.clear();
    highlightLinks.clear();

    if (link) {
        highlightLinks.add(link);
        highlightNodes.add(link.source);
        highlightNodes.add(link.target);
    }

    updateHighlight();
}

function linkColor(link) {
    if ('type' in link) {
        return edgeColors[link.type]
    }
    return 'rgb(255, 255, 255)'
}


function updateHighlight() {
    // trigger update of highlighted objects in scene
    Graph
        .nodeColor(Graph.nodeColor())
        .linkWidth(Graph.linkWidth())
        .linkDirectionalParticles(Graph.linkDirectionalParticles());
}

//is used in createInfoOverlay, as an unusual function call to open link-tabs
function openTab (event, tab) {
    var i, tabcontent, tablinks;

    // Get all elements with class="tabcontent" and hide them
    tabcontent = document.getElementsByClassName("tabcontent");
    for (i = 0; i < tabcontent.length; i++) {
        tabcontent[i].style.display = "none";
    }

    // Get all elements with class="tablinks" and remove the class "active"
    tablinks = document.getElementsByClassName("tablinks");
    for (i = 0; i < tablinks.length; i++) {
        tablinks[i].className = tablinks[i].className.replace(" active", "");
    }

    // Show the current tab, and add an "active" class to the button that opened the tab
    document.getElementById(tab.id + "_content").style.display = "block";
    event.currentTarget.className += " active";
}