<!--
  (C) 2013-22 - ntop.org
-->

<template>
    <div class="row">
        <div class="col-md-12 col-lg-12">
            <div class="card card-shadow">
                <Loading v-if="loading"></Loading>
                <Sankey ref="sankey_chart" :width="width" :height="height" :no_data_message="no_data_message"
                    :sankey_data="sankey_data" @update_width="update_width" @update_height="update_height"
                    @node_click="on_node_click">
                </Sankey>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted, onBeforeMount } from "vue";
import { default as Loading } from "./loading.vue"
import { ntopng_utility, ntopng_url_manager } from "../services/context/ntopng_globals_services.js";
import { default as Sankey } from "./sankey.vue";

const active_filter_list = {}
const props = defineProps({
    ifid: Number,
    available_filters: Object,
});

const _i18n = (t) => i18n(t);
const max_entries_reached = ref(false)
const max_entry_title = _i18n('ports_analysis.max_entries')
const no_data_message = _i18n('ports_analysis.no_data')
const sankey_chart = ref(null)
const body_div = ref(null);
const width = ref(null);
const height = ref(null);
const sankey_data = ref({});
const live_rest = `${http_prefix}/lua/pro/rest/v2/get/vlan/live_ports.lua`
const historical_rest = `${http_prefix}/lua/pro/rest/v2/get/vlan/historical_ports.lua`
const loading = ref(false)

onBeforeMount(() => {
    /* Before mounting the various widgets, update the url to the correct one, by adding ifid, ecc. */
});

onMounted(() => {
    update_height();
    update_width();
    set_sankey_data();
});

function on_node_click(node) {
    if (node.is_link_node == true) { return; }
    if (node.link) { ntopng_url_manager.go_to_url(node.link); }
}

const reload = function () {
    update_sankey()
}

const click_item = function (item) {
    ntopng_url_manager.set_key_to_url(item.filter_name, item.id)
    update_sankey();
}

const update_sankey = function () {
    set_sankey_data();
}

function check_max_entries(data) {
    max_entries_reached.value = data.max_entries_reached
}

async function set_sankey_data() {
    loading.value = true;
    let data = {
	nodes: [
	    { index: 0, name: "Liikevaihto", hours: "100%" },
	    { index: 1, name: "Kiinteät kulut", hours: "85%" },
	    { index: 2, name: "Muuttuvat kulut", hours: "3:00" },
	    { index: 3, name: "Palkkakulut", hours: "1:20" },
	    { index: 4, name: "Muut kiinte", hours: "1:40" },
	    { index: 5, name: "Kate", hours: "1:40" }
	],
	links: [
	    { source: 0, target: 1, value: 75, hours: "+1:00" },
	    { source: 0, target: 2, value: 10, hours: "+2:00" },
	    { source: 1, target: 3, value: 69, hours: "+1:20" },
	    { source: 1, target: 4, value: 6, hours: "+1:40" },
	    { source: 0, target: 5, value: 15, hours: "+1:40" }
	]
    };

    sankey_data.value = data;
    loading.value = false;
}

async function get_sankey_data() {
    const url_request = get_sankey_url();
    let graph = await ntopng_utility.http_request(url_request);
    check_max_entries(graph);
    graph = make_complete_graph(graph);
    const sankey_data = get_sankey_data_from_rest_data(graph);
    /* In case no data is returned, show the No Data message */
    (sankey_data.links.length > 0 && sankey_data.nodes.length > 0) ?
        sankey_chart.value.set_no_data_flag(false) :
        sankey_chart.value.set_no_data_flag(true);


    return sankey_data;
}

function get_sankey_url() {
    let vlan = ntopng_url_manager.get_url_entry("vlan");
    let timeframe = ntopng_url_manager.get_url_entry("timeframe");
    let l4proto = ntopng_url_manager.get_url_entry("l4proto");
    if (vlan == 'none') { vlan = ''; }
    if (timeframe == 'none') { timeframe = ''; }
    if (l4proto == 'none') { l4proto = ''; }

    let url_request = '';
    let params = {
        ifid: ntopng_url_manager.get_url_entry("ifid"),
        vlan: vlan,
        timeframe: timeframe,
        l4proto: l4proto
    };
    let url_params = ntopng_url_manager.obj_to_url_params(params);

    if (timeframe == '') { url_request = `${live_rest}?${url_params}`; }
    else { url_request = `${historical_rest}?${url_params}`; }

    return url_request;
}

function get_sankey_data_from_rest_data(res) {
    let node_dict = {}, link_to_nodes_dict = {};
    // create a node dict
    res.nodes.forEach((node) => node_dict[node.node_id] = node);

    let f_get_link_node_id = (link) => {
        return `${link.source_node_id}_${link.label}`;
    };
    // merge all links by label
    res.links.forEach((link) => {
        let link_node_id = f_get_link_node_id(link);
        let link_to_nodes = link_to_nodes_dict[link_node_id];
        if (link_to_nodes == null) {
            link_to_nodes = {
                id: link_node_id,
                label: link.label,
                link: link.optional_info.link,
                node_links: [],
            };
            link_to_nodes_dict[link_node_id] = link_to_nodes;
        }
        link_to_nodes.node_links.push({
            source: node_dict[link.source_node_id],
            target: node_dict[link.target_node_id],
            value: link.value,
        });
    });

    // create nodes and links
    let nodes = res.nodes.map((n) => n), links = [];
    for (let link_node_id in link_to_nodes_dict) {
        let link_to_nodes = link_to_nodes_dict[link_node_id];
        let link_node = {
            node_id: link_to_nodes.id,
            label: link_to_nodes.label,
            link: link_to_nodes.link,
        };
        nodes.push(link_node);
        link_to_nodes.node_links.forEach((link) => {
            links.push({
                source_node_id: link.source.node_id,
                target_node_id: link_node.node_id,
                label: `${link.source.label} - ${link.target.label}: ${link_node.label}`,
                value: link.value,
            });
            links.push({
                source_node_id: link_node.node_id,
                target_node_id: link.target.node_id,
                label: `${link.source.label} - ${link.target.label}: ${link_node.label}`,
                value: link.value,
            });
        });
    }
    let sankey_nodes = nodes.map((n, index) => {
        return { index, label: n.label, data: n };
    });
    let sankey_node_dict = {};
    sankey_nodes.forEach((sn, index) => sankey_node_dict[sn.data.node_id] = sn);
    let sankey_links = links.map((l) => {
        let source_index = sankey_node_dict[l.source_node_id].index;
        let target_index = sankey_node_dict[l.target_node_id].index;
        return {
            source: source_index,
            target: target_index,
            value: l.value,
            label: l.label,
        };
    });
    return { nodes: sankey_nodes, links: sankey_links };
}

// remove all links with a not existing node
function make_complete_graph(graph) {
    let f_log_link = (l) => console.error(`link (source: ${l.source_node_id}, target: ${l.target_node_id}) removed for not existing source/target node`);
    let links = get_links_with_existing_node(graph, f_log_link);
    return { nodes: graph.nodes, links };
}

// remeove all circular links and return a dag graph
function make_dag_graph(graph) {
    let nodes_dest_dict = {}; // dictionary { [node_source_id]: nodes_target[] }
    graph.links.forEach((l) => {
        let nodes_dest = nodes_dest_dict[l.source_node_id];
        if (nodes_dest == null) {
            nodes_dest = [];
            nodes_dest_dict[l.source_node_id] = nodes_dest;
        }
        nodes_dest.push(l.target_node_id);
    });
    let nodes_to_check = {}; // temp dictionary used from f_add_circular_link 
    graph.nodes.forEach((n) => {
        nodes_to_check[n.node_id] = { checked: false, visited: false };
    });

    // circular links dict (key: `${source_node_id}_${target_node_id}`)
    let circular_links = {};
    let f_get_link_key = (source_id, target_id) => `${source_id}_${target_id}`;

    // deep navigate starting from node_id and add circular_links visited in circular_links dict 
    let f_set_circular_links = (node_id, from_node_id) => {
        let node_to_check = nodes_to_check[node_id];
        if (node_to_check.checked == true) { return; }
        else if (node_to_check.visited == true) {
            let link_key = f_get_link_key(from_node_id, node_id);
            circular_links[link_key] = true;
            console.error(`Link (source: ${from_node_id}, target: ${node_id} ) is a circular link`);
            node_to_check.visited = false;
            return;
        }
        node_to_check.visited = true;
        let nodes_dest = nodes_dest_dict[node_id];
        if (nodes_dest != null) {
            for (let i = 0; i < nodes_dest.length; i += 1) {
                let target_node_id = nodes_dest[i];
                f_set_circular_links(target_node_id, node_id);
            }
        }
        node_to_check.visited = false;
        node_to_check.checked = true;
    };
    // set circular_links dictionary
    graph.nodes.forEach((n) => f_set_circular_links(n.node_id));

    // remove no dag nodes/links
    let f_filter_link = (l) => {
        let link_key = f_get_link_key(l.source_node_id, l.target_node_id);
        let take_link = circular_links[link_key] == null;
        return take_link;
    };
    let f_log_link = (l) => console.error(`link (source: ${l.source_node_id}, target: ${l.target_node_id}) removed for circular links`);
    let links = filter_log(graph.links, f_filter_link, f_log_link);

    let f_log_node = (n) => console.error(`node ${n.node_id} removed for circular links`);
    let nodes = get_nodes_with_existing_link({ nodes: graph.nodes, links }, f_log_node);

    // return a dag graph
    return { nodes, links };
}

function get_links_with_existing_node(graph, f_log) {
    let node_dict = {};
    graph.nodes.forEach((n) => node_dict[n.node_id] = true);
    let f_filter = (l) => node_dict[l.source_node_id] != null && node_dict[l.target_node_id] != null;
    let links = filter_log(graph.links, f_filter, f_log);
    return links;
}

function get_nodes_with_existing_link(graph, f_log) {
    let link_source_dict = {};
    let link_target_dict = {};
    graph.links.forEach((l) => {
        link_source_dict[l.source_node_id] = true;
        link_target_dict[l.target_node_id] = true;
    });
    let f_filter = (n) => link_source_dict[n.node_id] == true || link_target_dict[n.node_id] == true;
    let nodes = filter_log(graph.nodes, f_filter, f_log);
    return nodes;
}

// log elements deleted if f_log != null
function filter_log(elements, f_filter, f_log) {
    return elements.filter((e) => {
        const take_element = f_filter(e);
        if (take_element == false && f_log != null) {
            f_log(e);
        }
        return take_element;
    });
}

function update_height() {
    height.value = $(body_div.value).height() - 100;
}

function update_width() {
    width.value = $(body_div.value).width() - 10;
}

</script>
