1(function() {
2    'use strict';
3
4    let diameter = 1280;
5    let radius = diameter / 2;
6    let innerRadius = radius - 240;
7
8    let cluster = d3.cluster();
9    cluster.size([ 360, innerRadius ]);
10
11    let line = d3.radialLine();
12    line.curve(d3.curveBundle.beta(0.85));
13    line.radius(function(d) { return d.y; });
14    line.angle(function(d) { return d.x / 180 * Math.PI; });
15
16    let link;
17    let node;
18    let selectedNode;
19    let selectedSubNode;
20
21    function init() {
22        let domListCol = document.createElement("div");
23        domListCol.id = "violate_list_column";
24        let domGraphCol = document.createElement("div");
25        domGraphCol.id = "dep_graph_column";
26        let domResetBtn = document.createElement("button");
27        domResetBtn.id = "reset_btn";
28        domResetBtn.innerHTML = "Reset";
29        domGraphCol.appendChild(domResetBtn);
30
31        document.body.appendChild(domListCol);
32        document.body.appendChild(domGraphCol);
33
34        let canvas = d3.select("#dep_graph_column").append("svg");
35        canvas.attr("width", diameter + 200);
36        canvas.attr("height", diameter);
37
38        let svg = canvas.append("g");
39        svg.attr("transform", "translate(" + (radius + 100) + "," + radius + ")");
40
41        link = svg.append("g").selectAll(".link");
42        node = svg.append("g").selectAll(".node");
43
44        showResult(depData, violatedLibs);
45    }
46
47    function showList(depMap, violatedLibs) {
48        function makeTitle(tagName) {
49            let domTitle = document.createElement("div");
50            let domText = document.createElement("h3");
51            domText.innerHTML = tagName;
52            domTitle.appendChild(domText);
53            return domTitle;
54        }
55        function makeButton(libName, count) {
56            let domButton = document.createElement("button");
57            domButton.className = "violate";
58            domButton.innerHTML = libName + " (" + count + ")";
59            domButton.onclick = function() {
60                this.classList.toggle("active");
61                let currentList = this.nextElementSibling;
62                if (currentList.style.display === "block") {
63                    currentList.style.display = "none";
64                    selectedNode = undefined;
65                    if (selectedSubNode) {
66                        selectedSubNode.classList.toggle("active");
67                        selectedSubNode.nextElementSibling.style.display = "none";
68                        selectedSubNode = undefined;
69                    }
70                    resetclicked();
71                } else {
72                    currentList.style.display = "block";
73                    for (let i = 1; i < currentList.childElementCount; i += 2) {
74                        currentList.childNodes[i].style.display = "none";
75                    }
76                    if (selectedNode) {
77                        selectedNode.classList.toggle("active");
78                        selectedNode.nextElementSibling.style.display = "none";
79                        if (selectedSubNode) {
80                            selectedSubNode.classList.toggle("active");
81                            selectedSubNode.nextElementSibling.style.display = "none";
82                            selectedSubNode = undefined;
83                        }
84                    }
85                    selectedNode = domButton;
86                    mouseclicked(depMap[libName]);
87                }
88            };
89            return domButton;
90        }
91        function makeSubButton(libName, count) {
92            let domButton = document.createElement("button");
93            domButton.className = "violate-list";
94            domButton.innerHTML = libName + " (" + count + ")";
95            domButton.onclick = function() {
96                this.classList.toggle("active");
97                let currentSubList = this.nextElementSibling;
98                if (currentSubList.style.display === "block") {
99                    currentSubList.style.display = "none";
100                    selectedSubNode = undefined;
101                } else {
102                    currentSubList.style.display = "block";
103                    for (let i = 0; i < currentSubList.childElementCount; i++) {
104                        if (currentSubList.childNodes[i].childElementCount > 0) {
105                            currentSubList.childNodes[i].childNodes[1].style.display = "none";
106                        }
107                    }
108                    if (selectedSubNode) {
109                        selectedSubNode.classList.toggle("active");
110                        selectedSubNode.nextElementSibling.style.display = "none";
111                    }
112                    selectedSubNode = domButton;
113                }
114            };
115            return domButton;
116        }
117        function changeFormat(symbol) {
118            let res = "";
119            let i;
120            for (i = 0; i < symbol.length; i++) {
121                if (symbol.charAt(i) >= '0' && symbol.charAt(i) <= '9') {
122                    break;
123                }
124            }
125            while (i < symbol.length) {
126                if (symbol.charAt(i) < '0' || symbol.charAt(i) > '9') {
127                    break;
128                }
129                let len = parseInt(symbol.substr(i, symbol.length));
130                let count = 1;
131                if (len < 10) {
132                    count = 0;
133                }
134                res = res + "::" + symbol.substr(i + 1 + count, len);
135                i = i + 1 + count + len;
136            }
137            return res.substr(2, res.length);
138        }
139        function makeList(domList, list)
140        {
141            for (let i = 0; i < list.length; i++) {
142                domList.appendChild(makeButton(list[i][0], list[i][1]));
143                let domDepList = document.createElement("div");
144                let depItem = depMap[list[i][0]];
145                let violates = depItem.data.violates;
146                for (let j = 0; j < violates.length; j++) {
147                    let domDepLib = document.createElement("div");
148                    let tag = depMap[violates[j][0]].data.tag;
149                    let symbols = violates[j][1];
150                    let domDepButton = makeSubButton(violates[j][0] + " ["
151                            + tag.substring(tag.lastIndexOf(".") + 1) + "]", symbols.length);
152                    for (let k = 0; k < symbols.length; k++) {
153                        let domDepSym = document.createElement("div");
154                        domDepSym.className = "violate-list-sym";
155                        domDepSym.innerHTML = symbols[k];
156                        if (symbols[k].indexOf("_Z") === 0) {
157                            let cplusplusSym = document.createElement("span");
158                            cplusplusSym.className = "cplusplus-sym";
159                            cplusplusSym.innerHTML =
160                                changeFormat(symbols[k].substr(2, symbols[k].length));
161                            domDepSym.appendChild(cplusplusSym);
162                            domDepSym.onmouseover = function(e) {
163                                e.currentTarget.style.position = "relative";
164                                e.currentTarget.childNodes[1].style.display = "block";
165                            };
166                            domDepSym.onmouseout = function(e) {
167                                e.currentTarget.style.position = "static";
168                                e.currentTarget.childNodes[1].style.display = "none";
169                            };
170                        }
171                        domDepLib.appendChild(domDepSym);
172                    }
173                    domDepList.appendChild(domDepButton);
174                    domDepList.appendChild(domDepLib);
175                }
176                domList.appendChild(domDepList);
177                domDepList.style.display = "none";
178            }
179        }
180
181        let domViolatedList = document.getElementById("violate_list_column");
182        if ("vendor.private.bin" in violatedLibs) {
183            let list = violatedLibs["vendor.private.bin"];
184            domViolatedList.appendChild(makeTitle("VENDOR (" + list.length + ")"));
185            makeList(domViolatedList, list);
186        }
187        for (let tag in violatedLibs) {
188            if (tag === "vendor.private.bin")
189                continue;
190            let list = violatedLibs[tag];
191            if (tag === "system.private.bin")
192                tag = "SYSTEM";
193            else
194                tag = tag.substring(tag.lastIndexOf(".") + 1).toUpperCase();
195            domViolatedList.appendChild(makeTitle(tag + " (" + list.length + ")"));
196            makeList(domViolatedList, list);
197        }
198    }
199
200    function showResult(depDumps, violatedLibs) {
201        let root = tagHierarchy(depDumps).sum(function(d) { return 1; });
202        cluster(root);
203
204        let libsDepData = libsDepends(root.leaves());
205        showList(libsDepData[1], violatedLibs);
206        link = link.data(libsDepData[0])
207                   .enter()
208                   .append("path")
209                   .each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
210                   .attr("class", function(d) { return d.allow ? "link" : "link--violate" })
211                   .attr("d", line);
212
213        node = node.data(root.leaves())
214                   .enter()
215                   .append("text")
216                   .attr("class",
217                       function(d) {
218                           return d.data.parent.parent.parent.key == "system" ?
219                               (d.data.parent.parent.key == "system.public" ?
220                                        "node--sys-pub" :
221                                        "node--sys-pri") :
222                               "node";
223                       })
224                   .attr("dy", "0.31em")
225                   .attr("transform",
226                       function(d) {
227                           return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" +
228                               (d.x < 180 ? "" : "rotate(180)");
229                       })
230                   .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
231                   .text(function(d) { return d.data.key; })
232                   .on("click", mouseclicked);
233        document.getElementById("reset_btn").onclick = resetclicked;
234    }
235
236    function resetclicked() {
237        if (selectedNode) {
238            selectedNode.classList.toggle("active");
239            selectedNode.nextElementSibling.style.display = "none";
240            if (selectedSubNode) {
241                selectedSubNode.classList.toggle("active");
242                selectedSubNode.nextElementSibling.style.display = "none";
243                selectedSubNode = undefined;
244            }
245            selectedNode = undefined;
246        }
247        link.classed("link--target", false)
248            .classed("link--source", false);
249        node.classed("node--target", false)
250            .classed("node--source", false)
251            .classed("node--selected", false);
252    }
253
254    function mouseclicked(d) {
255        node.each(function(n) { n.target = n.source = false; });
256
257        link.classed("link--target",
258                function(l) {
259                    if (l.target === d) {
260                        l.source.source = true;
261                        return true;
262                    } else {
263                        return false;
264                    }
265                })
266            .classed("link--source",
267                function(l) {
268                    if (l.source === d) {
269                        l.target.target = true;
270                        return true;
271                    } else {
272                        return false;
273                    }
274                })
275            .filter(function(l) { return l.target === d || l.source === d; })
276            .raise();
277
278        node.classed("node--target",
279                function(n) {
280                    return n.target;
281                })
282            .classed("node--source",
283                function(n) { return n.source; })
284            .classed("node--selected",
285                function(n) {
286                    return n === d;
287                });
288    }
289
290    function tagHierarchy(depDumps) {
291        let map = {};
292
293        function find(name, tag, data) {
294            let node = map[name], i;
295            if (!node) {
296                node = map[name] = data || { name : name, children : [] };
297                if (name.length) {
298                    node.parent = find(tag, tag.substring(0, tag.lastIndexOf(".")));
299                    node.parent.children.push(node);
300                    node.key = name;
301                }
302            }
303            return node;
304        }
305
306        depDumps.forEach(function(d) { find(d.name, d.tag, d); });
307
308        return d3.hierarchy(map[""]);
309    }
310
311    function libsDepends(nodes) {
312        let map = {}, depends = [];
313
314        // Compute a map from name to node.
315        nodes.forEach(function(d) { map[d.data.name] = d; });
316
317        // For each dep, construct a link from the source to target node.
318        nodes.forEach(function(d) {
319            if (d.data.depends)
320                d.data.depends.forEach(function(i) {
321                    let l = map[d.data.name].path(map[i]);
322                    l.allow = true;
323                    depends.push(l);
324                });
325            if (d.data.violates.length) {
326                map[d.data.name].not_allow = true;
327                d.data.violates.forEach(function(i) {
328                    map[i[0]].not_allow = true;
329                    let l = map[d.data.name].path(map[i[0]]);
330                    l.allow = false;
331                    depends.push(l);
332                });
333            }
334        });
335
336        return [ depends, map ];
337    }
338
339    window.onload = init;
340})();
341