/*
   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
   Copyright 2018 EveryUP srl

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an  BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

if (typeof TCD3 === 'undefined') {
    var TCD3 = {};
}

TCD3.GraphEditor = (function () {
    'use strict';

    var DEBUG = true;
    var SHIFT_BUTTON = 16;
    var CANC_BUTTON = 46;
    var nominal_text_size = 14;
    var nominal_stroke = 1.5;
    var EventHandler = TCD3.Event;


    /**
     * Constructor
     */
    function GraphEditor(args) {
        log("Constructor");
        this.eventHandler = new EventHandler();
        this.lastKeyDown = -1;
        this._selected_node = undefined;
        this._selected_link = undefined;
        this._edit_mode = true;
        this.filter_parameters = {
            node: {
                type: [],
                group: [],
            },
            link: {
                group: [],
                view: [],
            }
        };
        this.current_view_id = '';
        // graph data initailization
        this.d3_graph = {
            nodes: [],
            links: [],
            graph_parameters: {}

        };


    }


    GraphEditor.prototype.init = function (args) {
        args = args || {};
        var self = this;
        this.width = args.width || 1500;
        this.height = args.height || 1500;
        this.forceSimulationActive = false;

        var min_zoom = 0.1;
        var max_zoom = 7;
        this._setupBehaviorsOnEvents();
        this._setupFiltersBehaviors(args);

        this.type_property = {
            "unrecognized": {
                "shape": d3.symbolCircle,
                "color": "#fff",
                "node_label_color": "#000",
                "size": 15
            },
        };

        this.type_property_link = {
            "unrecognized": {
                "color": "lightgray"
            },
        };

        this.force = d3.forceSimulation()
            .force("charge", d3.forceManyBody())
            .force("collide", d3.forceCollide().radius(40))
           // .force("link", d3.forceLink().distance(80).iterations(1).id(function (d) {
            .force("link", d3.forceLink().distance(function(d){
                return d.short ? 1 : 100;
            }).id(function (d) {
                return d.id;
            }))
            .force("center", d3.forceCenter(this.width / 2, this.height / 2));

        var zoom = d3.zoom().scaleExtent([min_zoom, max_zoom]);

        var size = d3.scalePow().exponent(2)
            .domain([1, 100])
            .range([8, 24]);

        this.svg = d3.select("#graph_editor_container").append("svg")
            .attr("id", "graph_svg")
            .attr("preserveAspectRatio", "xMinYMid")
            .attr("width", '100%')
            .attr("height", '100%');

        //End Arrow style
        this.defs = this.svg.append("svg:defs");

        this.defs.selectAll("marker")
            .data(["unrecognized"]) // Different link/path types can be defined here
            .enter().append("svg:marker") // This section adds in the arrows
            .attr("id", String)
            .attr("viewBox", "-5 -5 10 10")
            .attr("refX", 13) //must be smarter way to calculate shift
            .attr("refY", 0)
            .attr("markerUnits", "userSpaceOnUse")
            .attr("markerWidth", 12)
            .attr("markerHeight", 12)
            .attr("orient", "auto")
            .append("path")
            .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
            .attr('fill', this.type_property_link['unrecognized']['color']);

        d3.select(window)
            .on('keydown', function () {
                log('keydown ' + d3.event.keyCode);
                //d3.event.preventDefault();
                if (self.lastKeyDown !== -1) return;
                self.lastKeyDown = d3.event.keyCode;
                // if (self.lastKeyDown === CANC_BUTTON && self._selected_node !== undefined) {
                //     self.removeNode(self._selected_node, null, showAlert);
                // } else if (self.lastKeyDown === CANC_BUTTON && self._selected_link !== undefined) {
                //     self.removeLink(self._selected_link, null, showAlert);
                // }

            })
            .on('keyup', function () {
                log('keyup' + self.lastKeyDown);
                self.lastKeyDown = -1;
            });
        var popup = this.svg.append("g")
            .attr("id", "popup")
            .attr("class", "popup")
            .attr("opacity", "0")
            .attr("transform", "translate(1 1)")
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));

        function dragstarted(d) {
            //d3.select(this).raise().classed("active", true);
        }

        function dragged(d) {
            //console.log(JSON.stringify(d))
            d3.select(this).attr("transform", function () {
                return "translate(" + d3.event.x + "," + d3.event.y + ")";

            })
        }

        function dragended(d) {
            //d3.select(this).classed("active", false);
        }

        var chart = $("#graph_svg");
        this.aspect = chart.width() / chart.height();
        this.container = $("#graph_editor_container");
        $(window).on("resize", function () {

            self.width = self.container.width();
            self.height = self.container.height();
            chart.attr("width", self.container.width());
            chart.attr("height", self.container.height());
        });

    }


    GraphEditor.prototype.get_d3_symbol =
        function (myString) {

            switch (myString) {
                case "circle":
                    return d3.symbolCircle;
                case "square":
                    return d3.symbolSquare;
                case "diamond":
                    return d3.symbolDiamond;
                case "triangle":
                    return d3.symbolTriangle;
                case "star":
                    return d3.symbolStar;
                case "cross":
                    return d3.symbolCross;
                default:
                    // if the string is not recognized
                    return d3.symbolCross;
            }

        };

    GraphEditor.prototype.get_name_from_d3_symbol =
        function (mySymbol) {
            switch (mySymbol) {
                case d3.symbolCircle:
                    return "circle";
                case d3.symbolSquare:
                    return "square";
                case d3.symbolDiamond:
                    return "diamond";
                case d3.symbolTriangle:
                    return "triangle";
                case d3.symbolStar:
                    return "star";
                case d3.symbolCross:
                    return "cross";
                default:
                    // if the string is not recognized
                    return "unknown";
                //return d3.symbolCircleUnknown;
            }

        };

    /**
     * Start or Stop force layout
     * @param {boolean} Required. Value true: start, false: stop
     * @returns {boolean}
     */
    GraphEditor.prototype.handleForce = function (start) {
        if (start)
            this.force.stop();
        this.forceSimulationActive = start;
        this.node.each(function (d) {
            d.fx = (start) ? null : d.x;
            d.fy = (start) ? null : d.y;
        });

        if (start)
            this.force.restart();

        this.eventHandler.fire("force_status_changed_on", start);
    };

    /**
     * Handle the parameters of basic filters: node type, view, group
     * @param {Object} Required.
     *
     */
    GraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
        this.filter_parameters = (filtersParams !== undefined) ? filtersParams : this.filter_parameters;
        this.current_view_id = (this.filter_parameters !== undefined && this.filter_parameters.link.view[0] !== undefined) ? this.filter_parameters.link.view[0] : this.current_view_id
        this.cleanAll();
        this.refresh();
        this.startForce();
        this.force.restart();
        this._deselectAllNodes();
        this.handleForce(this.forceSimulationActive);
        if (!notFireEvent)
            this.eventHandler.fire("filters_changed", filtersParams);

    };

    /**
     * Add a new node to the graph.
     * @param {Object} Required. An object that specifies tha data of the new node.
     * @returns {boolean}
     */
    GraphEditor.prototype.addNode = function (args) {
        if (args.id && args.info && args.info.type) {
            args.fixed = true;
            this.force.stop();
            this.cleanAll();
            this.d3_graph.nodes.push(args);
            this.refresh();
            this.startForce();
            this.force.restart();
            this.handleForce(this.forceSimulationActive);
            return true;
        }

        return false;

    };

    /**
     * Update the data properties of the node
     * @param {Object} Required. An object that specifies tha data of the node.
     * @returns {boolean}
     */
    GraphEditor.prototype.updateDataNode = function (args) {

    };

    /**
     * Remove a node from graph and related links.
     * @param {String} Required. Id of node to remove.
     * @returns {boolean}
     */
    GraphEditor.prototype.removeNode = function (node) {
        if (node != undefined) {
            var node_id = node.id;
            this.d3_graph['nodes'].forEach(function (n, index, object) {
                if (n.id === node_id) {
                    object.splice(index, 1);
                }
            });

            var self = this;
            var links_to_remove = [];
            this.d3_graph['links'].forEach(function (l, index, object) {
                if (node_id === l.source.id || node_id === l.target.id) {
                    links_to_remove.push(index);
                }
            });
            var links_removed = 0;
            links_to_remove.forEach(function (l_index) {
                self.d3_graph['links'].splice(l_index - links_removed, 1);
                links_removed++;
            });
            this.cleanAll();
            this.refresh();
            this.startForce();
            this.force.restart();

            return true;
        }
        return false;
    };


    /**
     * Add a new link to graph.
     * @param {Object} Required. An object that specifies tha data of the new Link.
     * @returns {boolean}
     */
    GraphEditor.prototype.addLink = function (link) {

        if (link.source && link.target) {
            this.force.stop();
            this.cleanAll();
            this.d3_graph.links.push(link);
            this.refresh();
            this.startForce();
            this.force.restart();
            return true;
        }

        return false;
    };

    /**
     * Remove a link from graph.
     * @param {String} Required. The identifier of link to remove.
     * @returns {boolean}
     */
    GraphEditor.prototype.removeLink = function (link_id) {
        var self = this;
        if (link_id !== 'undefined') {
            this.d3_graph['links'].forEach(function (l, index, object) {
                if (link_id === l.index) {
                    object.splice(index, 1);

                    self.cleanAll();
                    self.refresh();
                    self.startForce();
                    self.force.restart();
                    return true;
                }

            });
        }

        return false;
    };


    /**
     * Force a refresh of GraphView
     * @returns {}
     */
    GraphEditor.prototype.refresh = function () {

        //log(data)
        var self = this;

        var link = this.svg
            .selectAll()
            .data(self.d3_graph.links
                .filter(this.link_filter_cb)
            );
        link.exit().remove();
         this.link = link.enter().append("g")
            .attr("class", "link cleanable")
            .append("path")
            .attr("class", "link")
            .attr("class", "cleanable")
            .style("stroke-width", nominal_stroke)
            .style("stroke", function (d) {
                return self._link_property_by_type((d.type_link) ? d.type_link : "unrecognized", "color");
            })
            .attr("marker-end", function (d) {
                if (!d.directed_edge)
                    return '';

                var marker_url = (d.type_link) ? d.type_link : "unrecognized"
                return (d.directed_edge ? "url(#" + marker_url + ")" : '');
            });

        var nodeContainer = this.svg
            .selectAll()
            .data(self.d3_graph.nodes
                .filter(this.node_filter_cb));
        nodeContainer.exit().remove();
        nodeContainer.enter()
            .append("g")
            // .attr("class", "nodosdads")
            .attr("class", "node cleanable");

        var nodes_symbols = this.svg.selectAll('.node')
            .data(self.d3_graph.nodes
                .filter(this.node_filter_cb))

            .filter(function (d) {
                return (d.info.type === undefined) || (self._node_property_by_type(d.info.type, 'image', d) === undefined)
            });
            nodes_symbols.exit().remove();

            nodes_symbols.append("svg:path")
            .attr("d", d3.symbol()
                .size(function (d) {
                    return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d), 2) / 4;
                })
                .type(function (d) {
                    // console.log(d.info.type, 'shape', self.current_view_id)
                    return (self._node_property_by_type(d.info.type, 'shape', d));
                })
            )
            .style("fill", function (d) {
                return self._node_property_by_type(d.info.type, 'color', d);
            })
            .attr("transform", function () {
                return "rotate(-45)";

            })
            .attr("stroke-width", 2.4)

            .attr("class", "node_path")
            .attr("id", function (d) {
                return "path_" + d.id;
            })

            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));

        var figure_node = this.svg.selectAll('.node')
            .data(self.d3_graph.nodes
                .filter(this.node_filter_cb))

            .filter(function (d) {
                return self._node_property_by_type(d.info.type, 'image', d) != undefined
            });
            figure_node.exit().remove();
        figure_node.append("svg:image")
            .attr("xlink:href", function (d) {
                return self._node_property_by_type(d.info.type, 'image', d)
            })
            .attr("x", function (d) {
                return -self._node_property_by_type(d.info.type, 'size', d) / 2
            })
            .attr("y", function (d) {
                return -self._node_property_by_type(d.info.type, 'size', d) / 2
            })
            .attr("width", function (d) {
                return self._node_property_by_type(d.info.type, 'size', d)
            })
            .attr("height", function (d) {
                return self._node_property_by_type(d.info.type, 'size', d)
            })
            .style("stroke", "black")
            .style("stroke-width", "1px")

            .attr("class", "node_path")
            .attr("id", function (d) {
                return "path_" + d.id;
            })
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));

        figure_node.append("svg:path")
            .attr("d", d3.symbol()
                .size(function (d) {
                    return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d) + 7, 2) / 4;
                })
                .type(function (d) {
                    return (self.get_d3_symbol('circle'));
                })
            )
            .style("fill", 'transparent')
            .attr("transform", function () {
                return "rotate(-45)";

            })
            .attr("stroke-width", 2.4)

            .attr("class", "hidden_circle")
            .attr("id", function (d) {
                return "path_" + d.id;
            })

            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));


        this.node = this.svg.selectAll('.node')
            .data(self.d3_graph.nodes
                .filter(this.node_filter_cb)).selectAll("image, path, circle");


        this.node.on("contextmenu", self.behavioursOnEvents.nodes["contextmenu"])
            .on("mouseover", self.behavioursOnEvents.nodes["mouseover"])
            .on("mouseout", self.behavioursOnEvents.nodes["mouseout"])
            .on('click', self.behavioursOnEvents.nodes["click"])
            .on('dblclick', self.behavioursOnEvents.nodes["dblclick"]);

        this.link
            .on("contextmenu", self.behavioursOnEvents.links["contextmenu"])
            .on("mouseover", self.behavioursOnEvents.links["mouseover"])
            .on('click', self.behavioursOnEvents.links["click"])
            .on("mouseout", self.behavioursOnEvents.links["mouseout"]);


        this.text = this.svg.selectAll(".node")
            .data(self.d3_graph.nodes
                .filter(this.node_filter_cb))
            .append("svg:text")
            .attr("class", "node_text cleanable")
            .attr("dy", function (d) {
                    return "-5";
            })
            .attr("pointer-events", "none")
            .style("font-size", nominal_text_size + "px")
            .style("font-family", "'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif")
            .style("fill", function (d) {
                return self._node_property_by_type(d.info.type, 'node_label_color', d);
            })
            //.style("text-anchor", "middle")
            .text(function (d) {
                if(d.info && d.info.property.custom_label && d.info.property.custom_label !==''){
                    return d.info.property.custom_label
                } else
                    return d.id;
            });


        function dragstarted(d) {
            d.draggednode = true;
            if (!d3.event.active) self.force.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;

        }

        function dragged(d) {
            d.fx = d3.event.x;
            d.fy = d3.event.y;
        }

        function dragended(d) {
            d.draggednode = false;
            if (!d3.event.active) self.force.alphaTarget(0);
            if (self.forceSimulationActive) {
                d.fx = null;
                d.fy = null;
            } else {
                d.fx = d.x;
                d.fy = d.y;
                self.force.stop();
                self.forceSimulationActive = false;
            }
        }


    };

    /**
     *  Start force layout on Graph.
     *
     */
    GraphEditor.prototype.startForce = function () {
        this.force.stop();
        var self = this;
        this.force
            .nodes(this.d3_graph.nodes)
            .on("tick", ticked);


        this.force
            .force("link")
            .links(this.d3_graph.links);

        function ticked() {
            self.node.attr("cx", function (d) {
                return d.x = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.width - self._node_property_by_type(d.info.type, 'size', d), d.x));
            })
                .attr("cy", function (d) {
                    return d.y = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.height - self._node_property_by_type(d.info.type, 'size', d), d.y));
                });

            self.link.attr("d", function (d) {
                var dx = d.target.x - d.source.x,
                    dy = d.target.y - d.source.y,
                    dr = Math.sqrt(dx * dx + dy * dy);
                return "M" + d.source.x + "," + d.source.y + "," + d.target.x + "," + d.target.y;
            });

            self.node.attr("transform", function (d) {
                return "translate(" + d.x + "," + d.y + ")";
            });
            self.text.attr("transform", function (d) {
                var label_pos_y = d.y + self._node_property_by_type(d.info.type, 'size', d)/2 +nominal_text_size;
                return "translate(" + d.x + "," + label_pos_y + ")";
            });
        }


    };

    /**
     * This method attaches an event handler.
     * @param {String} Required. A String that specifies the name of the event.
     * @param {Function} Required. Specifies the function to run when the event occurs.
     * @returns {}
     */
    GraphEditor.prototype.addListener = function (event_name, cb) {
        this.eventHandler.addL(event_name, cb);
    };

    /**
     * This method removes an event handler that has been attached with the addListener() method.
     * @param {String} Required. A String that specifies the name of the event to remove.
     * @param {Function} Required. Specifies the function to remove.
     * @returns {}
     */
    GraphEditor.prototype.removeListener = function (event_name, cb) {

    };


    GraphEditor.prototype.setNodeClass = function (class_name, filter_cb) {
        log("setNodeClass");
        var self = this;
        this.svg.selectAll('.node').classed(class_name, false);
        this.svg.selectAll('.node')
            .classed(class_name, filter_cb);
    };

    GraphEditor.prototype.setLinkClass = function (class_name, filter_cb) {
        log("setLinkClass");
        var self = this;
        this.svg.selectAll('.link').classed(class_name, false);
        this.svg.selectAll('.link')
            .classed(class_name, filter_cb);
    };

    GraphEditor.prototype.showNodeInfo = function (args) {
        this.addLinesToPopup(args['node_info'], "Info about node selected")
        this.handlePopupVisibility(true, 'right')
    };

    GraphEditor.prototype.addLinesToPopup = function (data, title) {
        var self = this;
        var index = 1;
        var translate_y = 0;
        var width_popup = 400;
        var height_popup = 0;

        d3.selectAll(".popupcleanable").remove(); // clean

        var popupbg = d3.select(".popup").append("rect")
            .attr("id", "popupbg")
            .attr("class", "popup bg popupcleanable cleanable")
            .attr("width", "400")
            .attr("height", "0")
            .attr("rx", 10) // set the x corner curve radius
            .attr("ry", 10); // set the y corner curve radius


        d3.select(".popup").append("svg:path")
            .attr("d", d3.symbol()
                .size(function (d) {
                    return 80
                })
                .type(function (d) {
                    console.log("popup")
                    return (self.get_d3_symbol());
                })
            )
            .style("fill", 'red')
            .attr("transform", function () {
                return "translate(380,15) rotate(-45)";

            })
            .attr("stroke-width", 2.4)
            .attr("id", "close_popup")
            .attr("class", "popupcleanable cleanable")
            .on("click", function (d) {
                self.handlePopupVisibility(false);
            });

        d3.select(".popup").append("text")
            .attr("class", "popup title popupcleanable cleanable")
            .attr("x", "10")
            .attr("y", "20")
            .text(title);

        for (var i in data) {
            //console.log(i, data, data[i])
            //var typeofvalue = typeof data[i];
            var record = data[i];
            index = this._addRecordToPopup(i, record, index)

        }

    };

    GraphEditor.prototype._addRecordToPopup = function (key, record, index, tab) {
        //console.log("_addRecordToPopup", key, record, index)
        var translate_y = 23 * index;
        var summary = d3.select(".popup").append("g")
            .attr("class", "popup summary d popupcleanable cleanable")
            .attr("transform", "translate(10 " + translate_y + ")");
        if (Object.prototype.toString.call(record) !== '[object Array]') { //is a record simple key:value
            //console.log(key, record)
            var summary_g = summary.append("g");
            summary_g.append("rect")
                .attr("class", "popup summary bg popupcleanable cleanable")
                .attr("width", "380")
                .attr("height", "20");

            summary_g.append("text")
                .attr("class", "popup summary  popupcleanable cleanable")
                .attr("x", (tab) ? tab : 10)
                .attr("y", "17")
                .attr("width", "100")
                .text(function (d) {
                    return key.toUpperCase() + ":";
                });

            summary_g.append("text")
                .attr("class", "popup summary  popupcleanable cleanable")
                .attr("x", "370")
                .attr("y", "17")
                .attr("text-anchor", "end")
                .text(function (d) {
                    return record
                });
        }
        else {//is a record simple complex: have a list of sub record key:value
            //index ++;
            this._addRecordToPopup(key, "", index)
            for (var r in record) {
                //console.log(i, r, record, record[r])
                for (var k in record[r]) {
                    //console.log(i, r, k, record[r][k])
                    var curr_key = k;
                    var recordValue = record[r][k]

                    index++;
                    this._addRecordToPopup(curr_key, recordValue, index, 20)
                }
            }

        }

        translate_y = 30 * index++;
        d3.select('#popupbg').attr("height", translate_y);
        return index;
    };


    /**
     *  Remove all the graph objects from the view
     */
    GraphEditor.prototype.cleanAll = function () {
        this.svg.selectAll('.cleanable').remove();
    };

    /**
     *  Internal functions
     */

    GraphEditor.prototype._node_property_by_type = function (type, property, node) {
        //console.log(type, property, layer, group)
        var unrecognized = function (ui_prop, property) {
            return ui_prop['unrecognized'][property]
        };

        //type recognized
        if (this.type_property[type]) {

            if (this.type_property[type]['property']) {
                var filt_property = this.type_property[type]['property']
                return this.type_property[type][node.info[filt_property]][property]
            } else { // type without property spec

                return this.type_property[type][property]

            }

        } else { //type unrecognized
            return unrecognized(this.type_property, property)
        }

    };

    GraphEditor.prototype._link_property_by_type = function (type, property) {
        //log(type + "-" + property)
        if (this.type_property_link[type] != undefined && this.type_property_link[type][property] != undefined) {
            //if(property == "shape")
            //    log("dentro" + this.type_property[type][property])
            return this.type_property_link[type][property];
        } else {
            return this.type_property_link['unrecognized'][property];
        }

    }


    /**
     *
     *
     *
     */
    GraphEditor.prototype._setupFiltersBehaviors = function (args) {

        var self = this;

        this.node_filter_cb = args.node_filter_cb || function (d) {

            var cond_view = true,
                cond_group = true;
            //log(d.info.type + " " + self.filter_parameters.node.type + " group: " + self.filter_parameters.node.group + "- " + d.info.group)
            // check filter by node type
            if (self.filter_parameters.node.type.length > 0) {

                if (self.filter_parameters.node.type.indexOf(d.info.type) < 0)
                    cond_view = false;
            }

            // check filter by group
            if (self.filter_parameters.node.group.length > 0) {
                self.filter_parameters.node.group.forEach(function (group) {
                    if (d.info.group.indexOf(group) < 0)
                        cond_group = false;
                });


            }


            return cond_view && cond_group;
        };

        this.link_filter_cb = args.link_filter_cb || function (d) {
            var cond_view = true,
                cond_group = true;

            // check filter by view
            if (self.filter_parameters.link.view.length > 0) {
                self.filter_parameters.link.view.forEach(function (view) {
                    if (d.view.indexOf(view) < 0)
                        cond_view = false;
                });
            }

            // check filter by group
            if (self.filter_parameters.link.group.length > 0) {
                self.filter_parameters.link.group.forEach(function (group) {
                    if (d.group.indexOf(group) < 0)
                        cond_group = false;
                });
            }
            return cond_view && cond_group;
        };

    };

    /**
     *
     *
     */
    GraphEditor.prototype._setupBehaviorsOnEvents = function () {
        log("_setupBehaviorsOnEvents");
        var self = this;
        this.behavioursOnEvents = {
            'nodes': {
                'click': function (d) {
                    d3.event.preventDefault();
                    log('click', d);
                    if (self.lastKeyDown === SHIFT_BUTTON && self._selected_node !== undefined) {
                        var source_id = self._selected_node.id;
                        var target_id = d.id;
                        log("--" + JSON.stringify(self.filter_parameters.link.view));
                        var new_link = {
                            source: source_id,
                            target: target_id,
                            view: self.filter_parameters.link.view[0],
                            group: self.filter_parameters.link.group[0],
                        };
                        self.addLink(new_link);
                        self._deselectAllNodes();
                    } else {
                        self._selectNodeExclusive(this, d);
                    }

                },
                'mouseover': function (d) {

                },
                'mouseout': function (d) {
                },
                'dblclick': function (d) {
                    d3.event.preventDefault();
                    log('dblclick');
                },
                'contextmenu': function (d, i) {
                    d3.event.preventDefault();
                    log("contextmenu node");
                    self.eventHandler.fire("right_click_node", d);
                }
            },
            'links': {
                'click': function (event) {

                },
                'dblclick': function (event) {

                }
            }
        };
    };

    /**
     *  Deselect previously selected nodes
     *
     */
    GraphEditor.prototype._deselectAllNodes = function () {
        log("_deselectAllNodes");
        this.node.classed("node_selected", false);
        this._selected_node = undefined;
    };

    GraphEditor.prototype._deselectAllLinks = function () {
        log("_deselectAllLinks");
        this.link.classed("link_selected", false).style('stroke-width', 2);
        this._selected_link = undefined;
    };
    /**
     *  Select node in exclusive mode
     *  @param {Object} Required. Element selected on click event
     */
    GraphEditor.prototype._selectNodeExclusive = function (node_instance, node_id) {
        log("_selectNodeExclusive ");
        var activeClass = "node_selected";
        var alreadyIsActive = d3.select(node_instance).classed(activeClass);
        this._deselectAllNodes();
        this._deselectAllLinks();
        d3.select(node_instance).classed(activeClass, !alreadyIsActive);
        this._selected_node = (alreadyIsActive) ? undefined : node_instance.__data__;
        if(this._selected_node){
            this.eventHandler.fire("node:selected", this._selected_node)
        } else {
             this.eventHandler.fire("node:deselected", this._selected_node)
        }
    };

    /**
     *  Select node in exclusive mode
     *  @param {Object} Required. Element selected on click event
     */
    GraphEditor.prototype._selectLinkExclusive = function (link_instance, link_id) {
        log("_selectLinkExclusive ");
        var activeClass = "link_selected";
        var alreadyIsActive = d3.select(link_instance).classed(activeClass);
        this._deselectAllNodes();
        this._deselectAllLinks();
        d3.select(link_instance).classed(activeClass, !alreadyIsActive);
        d3.select(link_instance).style('stroke-width', 4)
        this._selected_link = link_instance.__data__;
    };

    /**
     *  Callback to resize SVG element on window resize
     */
    GraphEditor.prototype.resizeSvg = function (width, height) {
        log("resizeSvg");
        //log(event);
        this.width = width || this.width;
        this.height = height || this.height;
        this.svg.attr('width', width);
        this.svg.attr('height', height);

    }

    GraphEditor.prototype.handlePopupVisibility = function (visible, side) {
        var opacity = (visible) ? 1 : 0;

        var translate_op = (side === "left") ? "translate(50 50)" : "translate(" + (this.width - 450).toString() + " 50)";

        if (!visible) {
            d3.selectAll(".popupcleanable").remove();
            d3.select(".popup")
                .attr("transform", "translate(-1 -1)");
        } else {
            d3.select(".popup")
                .attr("transform", translate_op);
        }
        d3.select(".popup").attr("opacity", opacity);
    };

    GraphEditor.prototype.refreshGraphParameters = function (graphParameters) {
        this.eventHandler.fire("refresh_graph_parameters", graphParameters);
    };

    /**
     * Log utility
     */
    function log(text) {
        if (DEBUG)
            console.log("::GraphEditor::", text);
    }


    return GraphEditor;


}(this));

if (typeof module === 'object') {
    module.exports = TCD3.GraphEditor;
}
