# -*- coding: utf-8 -*-
from operator import itemgetter
import ipaddress

# navigation chart in tangled tree visualization:
def navchartTangledtree2(nodes, links, owndomain, omit):

    # const levels = [[{'id': 'start'}], [{'id': 'business', 'parents': ['start']}], [{'id': 'comparison', 'parents': ['start']}]
    levels = [[{'id': 'start', 'name': 'start'}]]
    print("links: " + str(links))

    # append root nodes to levels L1:
    # root nodes are sources that never appear in target
    tmplevel1 = []
    for n1 in nodes:
        found = False
        for l1 in links:
            if l1['target'] == n1['id']:
                found = True
                break
        if found == False:
            tmp = {}
            tmp['id'] = n1['id']
            tmp['name'] = n1['name']
            tmp['parents'] = ['start']
            tmplevel1.append(tmp)
    levels.append(tmplevel1)

    print("levels1: " + str(levels) )

    # 2nd Level:
    tmplevel2 = []
    for l1 in tmplevel1:
        print("l1: " + str(l1))
        found = False
        for li2 in links:
            if li2['source'] == l1['id']:
                found = True
                break
        if found == True:
            # check if l1 is already there --> add to parents
            parent_found = False
            for l2a in tmplevel2:
                if l2a['id'] == li2['target']:
                    parent_found = True
                    l2a['parents'].append(l1['id'])
                    break
            if parent_found == False:
                tmp = {}
                tmp['id'] = li2['target']
                tmp['name'] = li2['target']
                tmp['parents'] = [l1['id']]
                for n2 in nodes:
                    if li2['target'] == n2['id']:
                        tmp['name'] = n2['name']
                        break
                tmplevel2.append(tmp)
            
    levels.append(tmplevel2)

    # 3rd Level:
    tmplevel3 = []
    for l2 in tmplevel2:
        print("l2: " + str(l2))
        found = False
        for li3 in links:
            if li3['source'] == l2['id']:
                found = True
                break
        if found == True:
            # check if l2 is already there --> add to parents
            parent_found = False
            for l3a in tmplevel3:
                if l3a['id'] == li3['target']:
                    parent_found = True
                    l3a['parents'].append(l2['id'])
                    break
            if parent_found == False:
                tmp = {}
                tmp['id'] = li3['target']
                tmp['name'] = li3['target']
                tmp['parents'] = [l2['id']]
                for n3 in nodes:
                    if li3['target'] == n3['id']:
                        tmp['name'] = n3['name']
                        break
                tmplevel3.append(tmp)
            
    levels.append(tmplevel3)
 

    # d3js horizontal bubble char in case results are available
    h = "\n\n"
    h += '<div class="col-md-12 col-lg-12 col-xxl-12">'
    h += '<div class="card mt-2"><div class="card-body">'
    h += '<h3 class="card-title">Navigation Chart</h3>'
    h += '<p class="card-text">User Navigation Chart for ' + owndomain + ':</p>'
    h += '<div id="navchart-tangledtree2-container"></div>'
    h += '<script type="module">' + "\n"
    h += 'const levels = ' + str(levels) + ';' + "\n"
    h += 'document.getElementById("navchart-tangledtree2-container").innerHTML = renderChart(levels);'

    h += 'const color = d3.scaleOrdinal(d3.schemeDark2);'

    h += 'const rect = document.getElementById("navchart-tangledtree2-container").getBoundingClientRect();'
    h += 'const margins = { top: 20, right: 20, bottom: 40, left: 100 };'
    h += 'const width = Math.round(rect.width) - margins.left - margins.right;'
    h += 'const height = width;'  + "\n"   # make height at least as width
    h += 'const totalWidth = width + margins.left + margins.right;'
    h += 'const totalHeight = height + margins.top + margins.bottom;'

    h += "\n\n"
    h += 'constructTangleLayout = (levels, options={}) => {'
    h += '// precompute level depth'
    h += 'levels.forEach((l, i) => l.forEach(n => (n.level = i)));'
    h += 'var nodes = levels.reduce((a, x) => a.concat(x), []);'
    h += 'var nodes_index = {};'
    h += 'nodes.forEach(d => (nodes_index[d.id] = d));'
    h += '// objectification'
    h += 'nodes.forEach(d => {'
    h += 'd.parents = (d.parents === undefined ? [] : d.parents).map('
    h += '  p => nodes_index[p]'
    h += ');});' + "\n"

    h += '// precompute bundles'
    h += 'levels.forEach((l, i) => {'
    h += 'var index = {};'
    h += 'l.forEach(n => { if (n.parents.length == 0) { return;}'
    h += 'var id = n.parents'
    h += '.map(d => d.id)'
    h += '.sort()'
    h += '.join('-X-');'
    h += 'if (id in index) {'
    h += 'index[id].parents = index[id].parents.concat(n.parents);'
    h += '} else {'
    h += 'index[id] = { id: id, parents: n.parents.slice(), level: i, span: i - d3.min(n.parents, p => p.level) };'
    h += '}'
    h += 'n.bundle = index[id];'
    h += '});'
    h += 'l.bundles = Object.keys(index).map(k => index[k]);'
    h += 'l.bundles.forEach((b, i) => (b.i = i));'
    h += '});'

    h += 'var links = [];'
    h += 'nodes.forEach(d => {'
    h += 'd.parents.forEach(p =>'
    h += 'links.push({ source: d, bundle: d.bundle, target: p })'
    h += ');'
    h += '});'

    h += 'var bundles = levels.reduce((a, x) => a.concat(x.bundles), []);'

    h += '// reverse pointer from parent to bundles'
    h += 'bundles.forEach(b =>'
    h += 'b.parents.forEach(p => {'
    h += 'if (p.bundles_index === undefined) {'
    h += 'p.bundles_index = {};'
    h += '}'
    h += 'if (!(b.id in p.bundles_index)) {'
    h += 'p.bundles_index[b.id] = [];'
    h += '}'
    h += 'p.bundles_index[b.id].push(b);'
    h += '})'
    h += ');'

    h += 'nodes.forEach(n => {'
    h += 'if (n.bundles_index !== undefined) {'
    h += 'n.bundles = Object.keys(n.bundles_index).map(k => n.bundles_index[k]);'
    h += '} else {'
    h += 'n.bundles_index = {};'
    h += 'n.bundles = [];'
    h += '}'
    h += 'n.bundles.sort((a,b) => d3.descending(d3.max(a, d => d.span), d3.max(b, d => d.span)))'
    h += 'n.bundles.forEach((b, i) => (b.i = i));'
    h += '});'

    h += 'links.forEach(l => {'
    h += 'if (l.bundle.links === undefined) {'
    h += 'l.bundle.links = [];'
    h += '}'
    h += 'l.bundle.links.push(l);'
    h += '});'

    h += '// layout'
    h += 'const padding = 8;'
    h += 'const node_height = 22;'
    h += 'const node_width = 70;'
    h += 'const bundle_width = 14;'
    h += 'const level_y_padding = 16;'
    h += 'const metro_d = 4;'
    h += 'const min_family_height = 22;'
  
    h += 'options.c ||= 16;'
    h += 'const c = options.c;'
    h += 'options.bigc ||= node_width+c;'

    h += 'nodes.forEach('
    h += 'n => (n.height = (Math.max(1, n.bundles.length) - 1) * metro_d)'
    h += ');'

    h += 'var x_offset = padding;'
    h += 'var y_offset = padding;'
    h += 'levels.forEach(l => {'
    h += 'x_offset += l.bundles.length * bundle_width;'
    h += 'y_offset += level_y_padding;'
    h += 'l.forEach((n, i) => {'
    h += 'n.x = n.level * node_width + x_offset;'
    h += 'n.y = node_height + y_offset + n.height / 2;'

    h += 'y_offset += node_height + n.height;'
    h += '});'
    h += '});'

    h += 'var i = 0;'
    h += 'levels.forEach(l => {'
    h += 'l.bundles.forEach(b => {'
    h += 'b.x ='
    h += 'd3.max(b.parents, d => d.x) +'
    h += 'node_width +'
    h += '(l.bundles.length - 1 - b.i) * bundle_width;'
    h += 'b.y = i * node_height;'
    h += '});'
    h += 'i += l.length;'
    h += '});'

    h += 'links.forEach(l => {'
    h += 'l.xt = l.target.x;'
    h += 'l.yt ='
    h += 'l.target.y +'
    h += 'l.target.bundles_index[l.bundle.id].i * metro_d -'
    h += '(l.target.bundles.length * metro_d) / 2 +'
    h += 'metro_d / 2;'
    h += 'l.xb = l.bundle.x;'
    h += 'l.yb = l.bundle.y;'
    h += 'l.xs = l.source.x;'
    h += 'l.ys = l.source.y;'
    h += '});'
  
    h += '// compress vertical space'
    h += 'var y_negative_offset = 0;'
    h += 'levels.forEach(l => {'
    h += 'y_negative_offset +='
    h += '-min_family_height +'
    h += 'd3.min(l.bundles, b =>'
    h += 'd3.min(b.links, link => link.ys - 2*c - (link.yt + c))'
    h += ') || 0;'
    h += 'l.forEach(n => (n.y -= y_negative_offset));'
    h += '});'

    h += 'links.forEach(l => {'
    h += 'l.yt ='
    h += 'l.target.y +'
    h += 'l.target.bundles_index[l.bundle.id].i * metro_d -'
    h += '(l.target.bundles.length * metro_d) / 2 +'
    h += 'metro_d / 2;'
    h += 'l.ys = l.source.y;'
    h += 'l.c1 = l.source.level - l.target.level > 1 ? Math.min(options.bigc, l.xb-l.xt, l.yb-l.yt)-c : c;'
    h += 'l.c2 = c;'
    h += '});'

    h += 'var layout = {'
    h += 'width: d3.max(nodes, n => n.x) + node_width + 2 * padding,'
    h += 'height: d3.max(nodes, n => n.y) + node_height / 2 + 2 * padding,'
    h += 'node_height,'
    h += 'node_width,'
    h += 'bundle_width,'
    h += 'level_y_padding,'
    h += 'metro_d'
    h += '};'

    h += 'return { levels, nodes, nodes_index, links, bundles, layout };'
    h += '}'

    h += 'renderChart = (data, options={}) => {'
    h += 'options.color ||= (d, i) => color(i)'
    h += 'const tangleLayout = constructTangleLayout(_.cloneDeep(data), options);'
    h += '  return svg`<svg width="${tangleLayout.layout.width}" height="${'
    h += 'tangleLayout.layout.height'
    h += '}" style="background-color: ${background_color}">'
    h += '<style>'
    h += 'text { font-family: sans-serif; font-size: 10px;}'
    h += '.node { stroke-linecap: round; }'
    h += '.link { fill: none; }'
    h += '</style>'

    h += '${tangleLayout.bundles.map((b, i) => {'
    h += 'let d = b.links'
    h += '.map('
    h += 'l => `'
    h += 'M${l.xt} ${l.yt}'
    h += 'L${l.xb - l.c1} ${l.yt}'
    h += 'A${l.c1} ${l.c1} 90 0 1 ${l.xb} ${l.yt + l.c1}'
    h += 'L${l.xb} ${l.ys - l.c2}'
    h += 'A${l.c2} ${l.c2} 90 0 0 ${l.xb + l.c2} ${l.ys}'
    h += 'L${l.xs} ${l.ys}`'
    h += ').join("");'
    h += 'return `'
    h += '<path class="link" d="${d}" stroke="${background_color}" stroke-width="5"/>'
    h += '<path class="link" d="${d}" stroke="${options.color(b, i)}" stroke-width="2"/>'
    h += '`;'
    h += '})}'
    h += '${tangleLayout.nodes.map('
    h += 'n => `'
    h += '<path class="selectable node" data-id="${'
    h += 'n.id'
    h += '}" stroke="black" stroke-width="8" d="M${n.x} ${n.y - n.height / 2} L${'
    h += 'n.x'
    h += '} ${n.y + n.height / 2}"/>'
    h += '<path class="node" stroke="white" stroke-width="4" d="M${n.x} ${n.y -'
    h += 'n.height / 2} L${n.x} ${n.y + n.height / 2}"/>'
    h += '<text class="selectable" data-id="${n.id}" x="${n.x + 4}" y="${n.y -'
    h += 'n.height / 2 -'
    h += '4}" stroke="${background_color}" stroke-width="2">${n.id}</text>'
    h += '<text x="${n.x + 4}" y="${n.y -'
    h += 'n.height / 2 -'
    h += '4}" style="pointer-events: none;">${n.id}</text>'
    h += '`'
    h += ')}'
    h += '</svg>`;'
    h += '}'
        
    h += "</script>"
    h += '</div></div></div>'
    return h
