{% import 'macros_profile_overview.html.jinja2' as profile_overview %}
{% import 'macros_html_widgets.html.jinja2' as widgets %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }}</title>
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.3/css/dataTables.dataTables.min.css">
    <script type="text/javascript" charset="utf8" src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src='https://cdn.plot.ly/plotly-2.30.0.min.js'></script>
    <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/2.0.3/js/dataTables.min.js"></script>
    <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/select/2.0.0/js/dataTables.select.min.js"></script>
    <style>
        html {
            font-family: "Courier New", Courier, monospace;
        }
        .column {
            width: 48%;
            margin: 0 1%;
        }
        .left {
            float: left;
        }
        .right {
            float: right;
        }
        .middle {
            width: 98%;
            float: left;
            margin: 0 1%;
            justify-content: center;
        }
        .column-head {
            border-bottom: 1px solid #ddd;
            border-top: 1px solid #ddd;
            text-align: center;
        }

        div.tools table {
            border-collapse: collapse;
            border-bottom: 1px solid #ddd;
            margin: 0 auto 0 auto;
        }

        div.tools td.value {
            padding: 1em 0.5em;
            text-align: left;
        }

        div.tools td.key {
            padding: 1em 0.5em;
            text-align: right;
            font-weight: bold;
        }
        div.tools td.topkey {
            padding: 1em 0 0;
            text-align: center;
            font-weight: bold;
        }
        {{ profile_overview.css_style() }}
        {{ widgets.chip_list_style() }}
        {{ widgets.range_picker_style() }}

        #label_base {
            border-bottom: 5px solid {{ palette.Baseline }};
        }
        #label_target {
            border-bottom: 5px solid {{ palette.Target }};
        }
        #label_incr {
            border-bottom: 5px solid {{ palette.NotInBaseline }};
        }
        #label_both {
            border-bottom: 5px solid {{ palette.InBoth }};
        }
        #label_decr {
            border-bottom: 5px solid {{ palette.NotInTarget }};
        }
    </style>
</head>
<body>

<div class="left column">
    <h2 class="column-head">{{ lhs_tag }}</h2>
    {{ profile_overview.overview_table('toggleLeftCollapse', 'left-info', lhs_header, rhs_header, "Profile Specification") }}
    <div style="margin: 0 10px;">&nbsp;</div>
</div>

<div class="right column">
    <h2 class="column-head">{{ rhs_tag }}</h2>
    {{ profile_overview.overview_table('toggleRightCollapse', 'right-info', rhs_header, lhs_header, "Profile Specification") }}
    <div style="margin: 0 10px;">&nbsp;</div>
</div>

<div class="middle">
    <div class="tools">
        <table id="table" class="display" style="width: 100%;">
            <thead>
            <tr>
                <th>Unit</th>
                <th>Absolute</th>
                <th>Relative</th>
            </tr>
            </thead>
        </table>
        <div class="left column">
            <table>
                <tbody>
                    {{ widgets.radios([('v', 'Vertical'), ('h', 'Horizontal')], 'position_radios', 'positions', 'Orientation') }}
                    {{ widgets.radios([('snap', 'Reorder'), ('fixed', 'Select')], 'mode_radios', 'modes', 'Mode') }}
                    {{ widgets.radios([('merged', 'Merged'), ('split', 'Split')], 'edge_mode_radios', 'edge_modes', 'Edges') }}
                </tbody>
            </table>
        </div>
        <div class="right column">
            <table>
                <tbody>
                    <tr>
                        <td class="key" style="width: 20%;">Threshold:</td>
                        <td colspan="3">{{ widgets.range_picker() }}</td>
                    </tr>
                    {{ widgets.checkboxes([('base', 'Baseline'), ('target', 'Target'), ('labels', 'Labels')], 'shown_merged_edges', 'Show') }}
                    {{ widgets.checkboxes([('incr', 'Incr (vs base)'), ('both', 'Equal'), ('decr', 'Decr (vs base)')], 'shown_split_edges', 'Show') }}
                </tbody>
            </table>
        </div>
        {{ widgets.chip_list('shown_functions', 'Filter') }}
    </div>

   <div id="sankey_graph"><!-- DO NOT CHANGE: SANKEY GRAPH IS RENDERED HERE--></div>
</div>

<script>
    let data = [{
        type: "sankey",
        arrangement: getMode(),
        orientation: getPosition(),
        node: {
            pad: 15,
            thickness: 20,
            line: {
                color: "black",
                width: 1
            },
            label: [],
        },
        link: {
            source: [],
            target: [],
            value: [],
            color: []
        }
        }],
        filtered_nodes = [],
        steps = [1],
        layout = {
            title: "",
            font: {
                size: 14
            },
        },
        selected_uid = -1,
        selected_uid_text = "",
        plot = document.getElementById('sankey_graph');

    // Selection table
    let table = {
        "data": [
            {% for val in selection_table %}
                {
                    "uid": "{{ val.uid }}",
                    "index": "{{ val.index }}",
                    "abs": "{{ val.abs_amount }}",
                    "rel": "{{ val.rel_amount }}"
                },
            {%- endfor %}
        ]
    };
    let selection_table = $("#table").DataTable({
        data: table.data,
        columns: [
            { data: "uid" },
            {
                data: "abs",
                className: "dt-body-right",
                render: function (data, type) {
                    if (type === 'display') {
                        const floatValue = parseFloat(data);
                        const intValue = parseInt(data);
                        if (!isNaN(intValue) && intValue.toString() === data) {
                            let formatted_int= intValue.toLocaleString();
                            return `${formatted_int}`;
                        } else if(!isNaN(floatValue) && floatValue.toString() === data) {
                            let formatted_float = floatValue.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
                            return `${formatted_float}`;
                        } else {
                            return data;
                        }
                    }

                    return data;
                },
            },
            { data: "rel" },
            { data: "index", visible: false},
        ],
        order: [[2, "desc"]],
        iDisplayLength: 5,
        select: true
    })

    // Graph data
    let graphs = [
        {% for graph in sankey_graphs %}
            {
                label: {{ graph.label }},
                customdata: {{ graph.customdata}},
                node_colors: {{ graph.node_colors }},
                link: {
                    merged: {
                        source: {{ graph.linkage["merged"].source }},
                        target: {{ graph.linkage["merged"].target }},
                        value: {{ graph.linkage["merged"].value }},
                        color: {{ graph.linkage["merged"].color }},
                    },
                    split: {
                        source: {{ graph.linkage["split"].source }},
                        target: {{ graph.linkage["split"].target }},
                        value: {{ graph.linkage["split"].value }},
                        color: {{ graph.linkage["split"].color }},
                    }
                },
                height: {{ graph.height }},
                width: {{ graph.width }},
                min: {{ graph.min }},
                max: {{ graph.max }},
            },
        {%- endfor %}
    ]
    {{ profile_overview.toggle_script('toggleLeftCollapse', 'left-info') }}
    {{ profile_overview.toggle_script('toggleRightCollapse', 'right-info') }}
    {{ widgets.add_to_chip_list() }}
    {{ widgets.checkbox_handler() }}
    {{ widgets.get_radio_handler('getPosition', 'positions') }}
    {{ widgets.get_radio_handler('getMode', 'modes') }}
    {{ widgets.get_radio_handler('getEdgeMode', 'edge_modes') }}
    {{ widgets.range_handlers() }}

    function getData(index) {
        let data = {
            type: "sankey",
            arrangement: getMode(),
            orientation: getPosition(),
            node: {
                pad: 15,
                thickness: 20,
                line: {
                    color: "black",
                    width: 1
                },
                label: graphs[index].label.slice(),
                color: graphs[index].node_colors.slice(),
                customdata: graphs[index].customdata.slice(),
                hovertemplate: '%{customdata[1]}<extra>%{value}</extra>',
                hoverlabel: {
                    align: "left"
                }
            },
            link: {
                source: graphs[index].link[getEdgeMode()].source.slice(),
                target: graphs[index].link[getEdgeMode()].target.slice(),
                value: graphs[index].link[getEdgeMode()].value.slice(),
                color: graphs[index].link[getEdgeMode()].color.slice()
            }
        }
        return [data]
    }

    function reselectData() {
        if (selected_uid === -1)
            return
        data = getData(selected_uid);
        filtered_nodes = [];
        steps = Array.from(new Set(data[0].link.value)).sort((a, b) => a - b);
        layout = {
            title: selected_uid_text,
            font: {
                size: 14
            },
            height: 200 + graphs[selected_uid].width* 100,
        };

        let fromSlider = document.getElementById('fromSlider');
        let toSlider = document.getElementById('toSlider');

        fromSlider.min = graphs[selected_uid].min;
        fromSlider.max = graphs[selected_uid].max + 1;
        fromSlider.value = graphs[selected_uid].min;
        toSlider.min = graphs[selected_uid].min;
        toSlider.max = graphs[selected_uid].max + 1;
        toSlider.value = graphs[selected_uid].max;

        let fromInput = document.getElementById('fromInput');
        let toInput = document.getElementById('toInput');

        setInput(fromInput, graphs[selected_uid].min);
        setInput(toInput, graphs[selected_uid].max);

        fillSlider(fromSlider, toSlider, '#C6C6C6', '#25daa5', toSlider);
    }

    function render() {
        Plotly.react('sankey_graph', data, layout);
        annotateEdges();
    }

    function newplot() {
        Plotly.newPlot('sankey_graph', data, layout);
        annotateEdges();
        refreshHandlers();
    }

    function backwardReachableEdges(nodes, source, target) {
        // Create a set of reachable nodes
        const reachable = new Set(nodes);

        // Initialize a queue with the initial nodes
        const queue = [...nodes];

        // Create an object to store visited edges
        const visited = {};

        // Initialize an array to store reachable edges
        const reachableEdges = [];

        // Perform BFS
        while (queue.length > 0) {
            const currentNode = queue.shift();

            // Find all edges connected to the current node
            for (let i = 0; i < source.length; i++) {
                if (target[i] === currentNode && !visited[i]) {
                    visited[i] = true;
                    const nextNode = source[i];

                    // Check if the next node is reachable
                    if (!reachable.has(nextNode)) {
                        reachable.add(nextNode);
                        queue.push(nextNode);
                    }

                    // Add the edge to the list of reachable edges
                    reachableEdges.push(i);
                }
            }
        }

        return reachableEdges;
    }
    function forwardReachableEdges(nodes, source, target) {
        // Create a set of reachable nodes
        const reachable = new Set(nodes);

        // Initialize a queue with the initial nodes
        const queue = [...nodes];

        // Create an object to store visited edges
        const visited = {};

        // Initialize an array to store reachable edges
        const reachableEdges = [];

        // Perform BFS
        while (queue.length > 0) {
            const currentNode = queue.shift();

            // Find all edges connected to the current node
            for (let i = 0; i < source.length; i++) {
                if (source[i] === currentNode && !visited[i]) {
                    visited[i] = true;
                    const nextNode = target[i];

                    // Check if the next node is reachable
                    if (!reachable.has(nextNode)) {
                        reachable.add(nextNode);
                        queue.push(nextNode);
                    }

                    // Add the edge to the list of reachable edges
                    reachableEdges.push(i);
                }
            }
        }

        return reachableEdges;
    }


    function restoreEdges() {
        data[0].link = {
            source: graphs[selected_uid].link[getEdgeMode()].source.slice(),
            target: graphs[selected_uid].link[getEdgeMode()].target.slice(),
            value: graphs[selected_uid].link[getEdgeMode()].value.slice(),
            color: graphs[selected_uid].link[getEdgeMode()].color.slice()
        }
    }

    function filterEdgesByValue() {
        const fromInput = document.querySelector('#fromInput').value;
        const toInput = document.querySelector('#toInput').value;
        for (let i = data[0].link.color.length - 1; i >= 0; i--) {
            if (data[0].link.value[i] > toInput || data[0].link.value[i] < fromInput) {
                data[0].link.source.splice(i, 1);
                data[0].link.target.splice(i, 1);
                data[0].link.value.splice(i, 1);
                data[0].link.color.splice(i, 1);
            }
        }
    }

    function filterEdgesByReachable() {
        if (filtered_nodes.length !== 0) {
            fwdEdges = forwardReachableEdges(filtered_nodes, data[0].link.source, data[0].link.target);
            let s = [], t = [], v = [], c = [];
            for (let i = data[0].link.color.length - 1; i >= 0; i--) {
                s.push(data[0].link.source[fwdEdges[i]]);
                t.push(data[0].link.target[fwdEdges[i]]);
                v.push(data[0].link.value[fwdEdges[i]]);
                c.push(data[0].link.color[fwdEdges[i]]);
            }
            data[0].link.source = s;
            data[0].link.target = t;
            data[0].link.value = v;
            data[0].link.color = c;
        }
    }

    function filterEdgesByColor(color) {
        for (let i = data[0].link.color.length - 1; i >= 0; i--) {
            if (data[0].link.color[i] === color) {
                data[0].link.source.splice(i, 1);
                data[0].link.target.splice(i, 1);
                data[0].link.value.splice(i, 1);
                data[0].link.color.splice(i, 1);
            }
        }
    }

    function filterEdges() {
        restoreEdges()
        let mode = getEdgeMode();

        if (mode === "split") {
            if (!isChecked("incr")) {
                filterEdgesByColor("{{ palette.NotInBaseline }}")
            }
            if (!isChecked("decr")) {
                filterEdgesByColor("{{ palette.NotInTarget }}")
            }
            if (!isChecked("both")) {
                filterEdgesByColor("{{ palette.InBoth }}")
            }
        } else if (mode === "merged") {
            if(!isChecked("base")) {
                filterEdgesByColor("{{ palette.Baseline }}")
            }
            if(!isChecked("target")) {
                filterEdgesByColor("{{ palette.Target }}")
            }
        }
        filterEdgesByValue();
        filterEdgesByReachable();
        render();
    }

    function clearAnnotations() {
        const annotations = document.querySelectorAll('.sankey-annotation');
        annotations.forEach(annotation => {
            annotation.remove()
        });
    }

    function annotateEdges() {
        clearAnnotations();
        if (isChecked('labels')) {
            const paths = document.querySelectorAll('.sankey-link');
            const edge_mode = getEdgeMode();

            paths.forEach((path, i) => {
                const bbox = path.getBBox();

                const xMid = bbox.x + bbox.width / 2;
                const yMid = bbox.y + bbox.height / 2;

                const topLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");
                topLabel.setAttribute("x", "0");
                topLabel.setAttribute("y", "0");
                topLabel.setAttribute("class", "sankey-annotation");
                topLabel.setAttribute("style", "font-family: 'Impact', sans-serif; font-style: bold; font-size: 10px; fill: rgb(0, 0, 0); fill-opacity: 0.6;")
                topLabel.setAttribute("transform", `translate(${xMid}, ${yMid}) scale(-1,1) rotate(90)`);
                topLabel.textContent = formatSize(graphs[selected_uid].link[edge_mode].value[i]);
                path.parentNode.appendChild(topLabel);
            })
        }
    }

    function refreshHandlers() {
        plot.on('plotly_afterplot', function () {
            annotateEdges();
        });
        plot.on('plotly_click', function (idata) {
            filtered_nodes = [];
            let tagList = document.getElementById('shown_functions');
            tagList.querySelectorAll('div').forEach((el) => {
                tagList.removeChild(el);
            });
            let tag = idata.points[0].customdata[0];
            if (tag === undefined)
                return;
            for (let i = 0; i < data[0].node.customdata.length; i++) {
                if (data[0].node.customdata[i][0] === tag) {
                    filtered_nodes.push(i);
                    break;
                }
            }
            addTag('shown_functions', tag);
            filterEdges();
        });
    }

    // Event handlers
    selection_table.on('click', 'tbody tr', (el) => {
        let classList = el.currentTarget.classList;

        if (classList.contains('selected')) {
            classList.remove('selected');
        } else {
            selection_table.rows('.selected').nodes().each((row) => row.classList.remove('selected'));
            classList.add('selected');
        }
    })

    selection_table.on('select', function(e, dt, type, indexes) {
        if (type === 'row') {
            let data = selection_table.rows(indexes).data();
            if (data.length > 0) {
                selected_uid = data[0].index;
                selected_uid_text = data[0].uid + "(" + data[0].rel + ", " + data[0].abs + ")";

                reselectData();
                filterEdges();
                newplot();
            }
        }
    })

    document.querySelectorAll('input[name="positions"]').forEach((button) => {
        button.addEventListener('change', () => {
            if (button.checked) {
                data[0].orientation = getPosition();
                render();
            }
        });
    });

    document.querySelectorAll('input[name="modes"]').forEach((button) => {
        button.addEventListener('change', () => {
            if (button.checked) {
                data[0].arrangement = getMode();
                render();
            }
        });
    });

    document.querySelectorAll('input[name="edge_modes"]').forEach((button) => {
        button.addEventListener('change', () => {
            if (button.checked) {
                reselectData();
                filterEdges();
                newplot();
            }
            let merged_edges_checkboxes = document.getElementById('shown_merged_edges'),
                split_edges_checkboxes = document.getElementById('shown_split_edges'),
                edge_mode = getEdgeMode();
            if (edge_mode === "merged") {
                merged_edges_checkboxes.querySelectorAll('input[type="checkbox"]').forEach((checkbox) => {checkbox.disabled = false;})
                split_edges_checkboxes.querySelectorAll('input[type="checkbox"]').forEach((checkbox) => {checkbox.disabled = true;})
            } else if (edge_mode === "split") {
                split_edges_checkboxes.querySelectorAll('input[type="checkbox"]').forEach((checkbox) => {checkbox.disabled = false;})
                merged_edges_checkboxes.querySelectorAll('input[type="checkbox"]').forEach((checkbox) => {checkbox.disabled = true;})
            }
        });
    });
    document.querySelector('input[name="labels"]').addEventListener('change', function() {
        if (isChecked('labels')) {
            annotateEdges();
        } else {
            clearAnnotations();
        }
    });
    document.querySelector('input[name="incr"]').addEventListener('change', filterEdges);
    document.querySelector('input[name="target"]').addEventListener('change', filterEdges);
    document.querySelector('input[name="decr"]').addEventListener('change', filterEdges);
    document.querySelector('input[name="base"]').addEventListener('change', filterEdges);
    document.querySelector('input[name="target"]').addEventListener('change', filterEdges);
    document.querySelector('#fromSlider').addEventListener('change', filterEdges);
    document.querySelector('#toSlider').addEventListener('change', filterEdges);

    document.getElementById('shown_split_edges').querySelectorAll('input[type="checkbox"]').forEach((checkbox) => {checkbox.disabled = true;})

    newplot()
</script>

</body>
</html>
