// The MIT License (MIT)

// Copyright (c) 2013 Tim Dwyer
// Copyright (c) 2017 Istio Authors

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

var graphDiv = d3.select("#graph")
    .on("click", function(d){select(null);});

var width = graphDiv.node().getBoundingClientRect().width - 20;
var height = 1000;
var infoHeight = 300;

var graphDivHeight = d3.select("#total").node().getBoundingClientRect().height - 20;

hideInfo();

function showInfo() {
    var shortHeight = graphDivHeight - 300
    graphDiv.style("height", shortHeight + "px");
    d3.select("#info").style("display", "block");
}
function hideInfo() {
    graphDiv.style("height", graphDivHeight + "px");
    d3.select("#info").style("display", "none");
}

var d3cola = cola.d3adaptor(d3)
    .avoidOverlaps(true)
    .convergenceThreshold(1e-3)
    .flowLayout('y', 150)
    .jaccardLinkLengths(150)
    .size([width, height]);

var outer = graphDiv.append("svg")
    .attr('width',width)
    .attr('height',height);

var vis = outer
    .append('g');

var lineFunction = d3.line()
    .x(function (d) { return d.x; })
    .y(function (d) { return d.y; });

var link, node, label;
var margin = 8, pad = 6;

outer.append('svg:defs').append('svg:marker')
    .attr('id','end-arrow')
    .attr('viewBox','0 -5 10 10')
    .attr('refX',8)
    .attr('markerWidth',6)
    .attr('markerHeight',6)
    .attr('orient','auto')
    .append('svg:path')
    .attr('d','M0,-5L10,0L0,5L2,0')
    .attr('stroke-width','0px')
    .attr('fill','#000');

d3cola.on("tick", function () {
    node.each(function (d) { d.innerBounds = d.bounds.inflate(-margin); })
        .attr("x", function (d) { return d.innerBounds.x; })
        .attr("y", function (d) { return d.innerBounds.y; })
        .attr("width", function (d) {
            return d.innerBounds.width();
        })
        .attr("height", function (d) {
            return d.innerBounds.height();
        });

    link.attr("d", function (d) {
        var route = cola.makeEdgeBetween(d.source.innerBounds, d.target.innerBounds, 5);
        return lineFunction([route.sourceIntersection, route.arrowStart]);
    });

    label
        .attr("x", function (d) { return d.x })
        .attr("y", function (d) { return d.y + (margin + pad) / 2 });

});

var endpoint = "/d3graph";

(function(){
    var query={};
    var i=0;
    var tele;
    var search_values=location.search.replace('\?','').split('&');
    for(i=0;i<search_values.length;i++){
        telem=search_values[i].split('=');
        query[telem[0]]=telem[1];
    }

    if (query.time_horizon != undefined && query.filter_empty != undefined) {
        endpoint = endpoint + "?time_horizon=" + query.time_horizon +
            "&filter_empty=" + query.filter_empty;
    } else if (query.time_horizon != undefined) {
        endpoint = endpoint + "?time_horizon=" + query.time_horizon;
    } else if (query.filter_empty != undefined) {
        endpoint = endpoint + "?filter_empty=" + query.filter_empty;
    }
}());

refresh()
d3.interval(refresh, 5000)

function refresh() {
    d3.json(endpoint, updateData);
    return true;
}

var graphData;

function linkName(d) {
    var source, dest;
    if (Number.isInteger(d.source)) {
        source = graphData.nodes[d.source].name;
    } else {
        source = d.source.name;
    }
    if (Number.isInteger(d.source)) {
        dest = graphData.nodes[d.target].name;
    } else {
        dest = d.target.name;
    }

    return source + "_" + dest;
}

function updateData(data) {
    if (graphData != undefined) {
        data.nodes.forEach(function(newNode) {
            var found = graphData.nodes.find(function(n){return n.name == newNode.name});
            if (found != undefined) {
                newNode.x = found.x;
                newNode.y = found.y;
            }
        });
    }
    graphData = data;

    d3cola.stop();
    delete d3cola._lastStress;
    delete d3cola._alpha;
    delete d3cola._descent;
    delete d3cola._rootGroup;

    d3cola
        .nodes(data.nodes)
        .links(data.links);

    link = vis.selectAll(".link")
        .data(data.links, linkName);
    link
        .enter().append("path")
        .attr("class", "link");
    link
        .exit().remove();
    link = vis.selectAll(".link")
        .data(data.links, linkName);

    node = vis.selectAll(".node")
        .data(data.nodes, function(d){return d.name;});
    node
        .enter().append("rect")
        .classed("node", true)
        .attr('rx',5)
        .attr('ry',5)
        .call(d3cola.drag)
        .on("click", function(d){select(d);}, true)
        .on("mouseenter", function(d){highlight(d);})
        .on("mouseleave", function(d){highlight(null);});
    node
        .exit().remove();
    node = vis.selectAll(".node")
        .data(data.nodes, function(d){return d.name;});

    label = vis.selectAll(".label")
        .data(data.nodes, function(d){return d.name;});
    label
        .enter().append("text")
        .attr("class", "label")
        .text(function (d) { return d.name; })
        .call(d3cola.drag)
        .on("click", function(d){select(d);}, true)
        .on("mouseenter", function(d){highlight(d);})
        .on("mouseleave", function(d){highlight(null);});
    label
        .exit().remove();
    label = vis.selectAll(".label")
        .data(data.nodes, function(d){return d.name;});
    label
        .each(function (d) {
            var b = this.getBBox();
            var extra = 2 * margin + 2 * pad;
            d.width = b.width + extra;
            d.height = b.height + extra;
        });

    d3cola
        .start();

    updateInfo();
}

var source   = document.getElementById("info-template").innerHTML;
var template = Handlebars.compile(source);

function updateInfo() {
    if (selected != null) {
        var nodeData = {
            name:selected.name,
            incoming:[],
            outgoing:[]
        };
        graphData.links.forEach(function(l) {
            if (l.source.name == selected.name) {
                nodeData.outgoing.push({destination:l.target.name, ops:l.labels["reqs/sec"]});
            }
            if (l.target.name == selected.name) {
                nodeData.incoming.push({source:l.source.name, ops:l.labels["reqs/sec"]});
            }
        });
        nodeData.incoming.sort(function(a, b){return a.source > b.source;});
        nodeData.outgoing.sort(function(a, b){return a.destination > b.destination;});
        var html = template(nodeData);
        d3.select("#info").html(html);
    }
}

var selected = null;

function highlight(obj) {
    if (!selected) {
        if (obj) {
            node.classed("darken", function(d){return obj !== d;});
            label.classed("darken", function(d){return obj !== d;});
            link.classed("darken", function(d){return obj !== d.source && obj !== d.target});
        } else {
            node.classed("darken", false);
            label.classed("darken", false);
            link.classed("darken", false);
        }
    }
}

function select(obj) {
    d3.event.stopPropagation();
    if (obj && selected !== obj) {
        node.classed("darken", function(d){return obj !== d;});
        label.classed("darken", function(d){return obj !== d;});
        link.classed("darken", function(d){return obj !== d.source && obj !== d.target});
        selected = obj;
        updateInfo();
        showInfo();
    } else {
        node.classed("darken", false);
        label.classed("darken", false);
        link.classed("darken", false);
        selected = null;
        hideInfo();
    }
}
