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
20    function init() {
21        let domListCol = document.createElement("div");
22        domListCol.id = "violate_list_column";
23        let domGraphCol = document.createElement("div");
24        domGraphCol.id = "dep_graph_column";
25        let domResetBtn = document.createElement("button");
26        domResetBtn.id = "reset_btn";
27        domResetBtn.innerHTML = "Reset";
28        domGraphCol.appendChild(domResetBtn);
29
30        document.body.appendChild(domListCol);
31        document.body.appendChild(domGraphCol);
32
33        let canvas = d3.select("#dep_graph_column").append("svg");
34        canvas.attr("width", diameter + 200);
35        canvas.attr("height", diameter);
36
37        let svg = canvas.append("g");
38        svg.attr("transform", "translate(" + (radius + 100) + "," + radius + ")");
39
40        link = svg.append("g").selectAll(".link");
41        node = svg.append("g").selectAll(".node");
42
43        showResult(depData, violatedLibs);
44    }
45
46    function showList(depMap, violatedLibs) {
47        function makeTitle(tagName) {
48            let domTitle = document.createElement("div");
49            let domText = document.createElement("h3");
50            domText.innerHTML = tagName;
51            domTitle.appendChild(domText);
52            return domTitle;
53        }
54        function makeButton(libName, count) {
55            let domButton = document.createElement("button");
56            domButton.className = "violate";
57            domButton.innerHTML = libName + " (" + count + ")";
58            domButton.onclick = function() {
59                this.classList.toggle("active");
60                let currentList = this.nextElementSibling;
61                if (currentList.style.display === "block") {
62                    currentList.style.display = "none";
63                    selectedNode = undefined;
64                    resetclicked();
65                } else {
66                    currentList.style.display = "block";
67                    if (selectedNode) {
68                        selectedNode.classList.toggle("active");
69                        selectedNode.nextElementSibling.style.display = "none";
70                    }
71                    selectedNode = domButton;
72                    mouseclicked(depMap[libName]);
73                }
74            };
75            return domButton;
76        }
77        function makeList(domList, list)
78        {
79            for (let i = 0; i < list.length; i++) {
80                domList.appendChild(makeButton(list[i][0], list[i][1]));
81                let domDepList = document.createElement("div");
82                let depItem = depMap[list[i][0]];
83                let violates = depItem.data.violates;
84                for (let j = 0; j < violates.length; j++) {
85                    let domDepLib = document.createElement("div");
86                    let tag = depMap[violates[j]].data.tag;
87                    domDepLib.className = "violate-list";
88                    domDepLib.innerHTML = violates[j] + " ["
89                            + tag.substring(tag.lastIndexOf(".") + 1) + "]";
90                    domDepList.appendChild(domDepLib);
91                }
92                domList.appendChild(domDepList);
93                domDepList.style.display = "none";
94            }
95        }
96
97        let domViolatedList = document.getElementById("violate_list_column");
98        if ("vendor.private.bin" in violatedLibs) {
99            let list = violatedLibs["vendor.private.bin"];
100            domViolatedList.appendChild(makeTitle("VENDOR (" + list.length + ")"));
101            makeList(domViolatedList, list);
102        }
103        for (let tag in violatedLibs) {
104            if (tag === "vendor.private.bin")
105                continue;
106            let list = violatedLibs[tag];
107            if (tag === "system.private.bin")
108                tag = "SYSTEM";
109            else
110                tag = tag.substring(tag.lastIndexOf(".") + 1).toUpperCase();
111            domViolatedList.appendChild(makeTitle(tag + " (" + list.length + ")"));
112            makeList(domViolatedList, list);
113        }
114    }
115
116    function showResult(depDumps, violatedLibs) {
117        let root = tagHierarchy(depDumps).sum(function(d) { return 1; });
118        cluster(root);
119
120        let libsDepData = libsDepends(root.leaves());
121        showList(libsDepData[1], violatedLibs);
122        link = link.data(libsDepData[0])
123                   .enter()
124                   .append("path")
125                   .each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
126                   .attr("class", function(d) { return d.allow ? "link" : "link--violate" })
127                   .attr("d", line);
128
129        node = node.data(root.leaves())
130                   .enter()
131                   .append("text")
132                   .attr("class",
133                       function(d) {
134                           return d.data.parent.parent.parent.key == "system" ?
135                               (d.data.parent.parent.key == "system.public" ?
136                                        "node--sys-pub" :
137                                        "node--sys-pri") :
138                               "node";
139                       })
140                   .attr("dy", "0.31em")
141                   .attr("transform",
142                       function(d) {
143                           return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" +
144                               (d.x < 180 ? "" : "rotate(180)");
145                       })
146                   .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
147                   .text(function(d) { return d.data.key; })
148                   .on("click", mouseclicked);
149        document.getElementById("reset_btn").onclick = resetclicked;
150    }
151
152    function resetclicked() {
153        if (selectedNode) {
154            selectedNode.classList.toggle("active");
155            selectedNode.nextElementSibling.style.display = "none";
156            selectedNode = undefined;
157        }
158        link.classed("link--target", false)
159            .classed("link--source", false);
160        node.classed("node--target", false)
161            .classed("node--source", false)
162            .classed("node--selected", false);
163    }
164
165    function mouseclicked(d) {
166        node.each(function(n) { n.target = n.source = false; });
167
168        link.classed("link--target",
169                function(l) {
170                    if (l.target === d) {
171                        l.source.source = true;
172                        return true;
173                    } else {
174                        return false;
175                    }
176                })
177            .classed("link--source",
178                function(l) {
179                    if (l.source === d) {
180                        l.target.target = true;
181                        return true;
182                    } else {
183                        return false;
184                    }
185                })
186            .filter(function(l) { return l.target === d || l.source === d; })
187            .raise();
188
189        node.classed("node--target",
190                function(n) {
191                    return n.target;
192                })
193            .classed("node--source",
194                function(n) { return n.source; })
195            .classed("node--selected",
196                function(n) {
197                    return n === d;
198                });
199    }
200
201    function tagHierarchy(depDumps) {
202        let map = {};
203
204        function find(name, tag, data) {
205            let node = map[name], i;
206            if (!node) {
207                node = map[name] = data || { name : name, children : [] };
208                if (name.length) {
209                    node.parent = find(tag, tag.substring(0, tag.lastIndexOf(".")));
210                    node.parent.children.push(node);
211                    node.key = name;
212                }
213            }
214            return node;
215        }
216
217        depDumps.forEach(function(d) { find(d.name, d.tag, d); });
218
219        return d3.hierarchy(map[""]);
220    }
221
222    function libsDepends(nodes) {
223        let map = {}, depends = [];
224
225        // Compute a map from name to node.
226        nodes.forEach(function(d) { map[d.data.name] = d; });
227
228        // For each dep, construct a link from the source to target node.
229        nodes.forEach(function(d) {
230            if (d.data.depends)
231                d.data.depends.forEach(function(i) {
232                    let l = map[d.data.name].path(map[i]);
233                    l.allow = true;
234                    depends.push(l);
235                });
236            if (d.data.violates.length) {
237                map[d.data.name].not_allow = true;
238                d.data.violates.forEach(function(i) {
239                    map[i].not_allow = true;
240                    let l = map[d.data.name].path(map[i]);
241                    l.allow = false;
242                    depends.push(l);
243                });
244            }
245        });
246
247        return [ depends, map ];
248    }
249
250    window.onload = init;
251})();