{% extends "django_dramatiq_charts/base.html" %}

{% block title %}Dramatiq task timeline chart{% endblock %}

{% block description %}Dramatiq task timeline chart{% endblock %}

{% block content %}
    <form method="GET" class="filter_form">
        {{ form }}
        <nobr>
            <button type="submit">Build chart</button>
            <a href="."><i>Reset</i></a>
            {% if cache_enabled %}
                <a href="{% url 'ddc_clean_cache' %}"><i>Update cache</i></a>
            {% endif %}
        </nobr>
    </form>
    {% if empty_qs %}
        <p class="msg">🔍 there is no data for the specified criteria</p>
        <p class="text-center">{{ chart_title|safe }}</p>
    {% elif chart_data %}
        <div id="chart"></div>
        <div id="status_color" class="status_color_label"></div>
    {% else %}
        <p class="msg">🖦 specify build criteria</p>
    {% endif %}
{% endblock %}

{% block extrabottom %}
    <script>
        $("#id_status").select2({
            placeholder: "All statuses",
            multiple: true,
        });
        $("#id_actor").select2({
            placeholder: "All actors",
            multiple: true,
        });
        $("#id_queue").select2({
            placeholder: "All queues",
            multiple: true,
        });

        function msToString(milliseconds) {
            // converting milliseconds to '{hours}h {minutes}m {seconds}s {milliseconds}ms'
            if (milliseconds <= 0) {
                return 'undefined';
            }
            let ms = Math.round(milliseconds % 1000) + 'ms';
            let s = Math.floor((milliseconds / 1000) % 60) + 's';
            let m = Math.floor((milliseconds / (1000 * 60)) % 60) + 'm';
            let h = Math.floor(milliseconds / (1000 * 60 * 60)) + 'h';
            let time = '';
            [h, m, s, ms].forEach(function (t) {
                if (/[1-9]/.test(t)) {
                    time += t + ' ';
                }
            });
            return time;
        }

        function heightCalculator(count, width, extra, min, max) {
            // calculating the chart height depending on the parameters:
            // count - number of data instances, width - width of the instance with a gap,
            // extra - additional height, min - minimum height, max - maximum height
            extra = extra || 0;
            let height = extra + count * width;
            if (min) {
                height = Math.max(height, min);
            }
            if (max) {
                height = Math.min(height, max);
            }
            return height;
        }

        // colors for status
        const status_color = {
            enqueued: 'rgb(126,59,243)',
            delayed: 'rgb(232,157,17)',
            running: 'rgb(72,139,222)',
            failed: 'rgb(239,77,50)',
            done: 'rgb(30,190,54)',
            skipped: 'rgb(100,100,100)',
        };

        let chart_data = ({{ chart_data|safe|default:'[]' }});
        let filter_data = ({{ filter_data|safe|default:'[]' }});
        let data = [];
        let unique_actors = new Set();
        let unique_status = new Set();
        let bargap = 1.25;    // indent between intervals in fractions of the interval width by y
        let y_time = [];    // task start time
        let y = 0;
        let chart = document.getElementById('chart');
        let chart_config = {responsive: true};

        chart_data.forEach(function (task) {
            let actor = task['actor'];
            let status = task['status'];
            let color = status_color[status];
            let duration = msToString(task['duration']);
            let text =
                `Actor: ${actor}<br>` +
                `Queue: ${task['queue']}<br>` +
                `Status: <span style="color:${color}">${status}</span><br>` +
                `Duration: ${duration}<br>` +
                `Start: ${task['start'].slice(0, -3)}<br>` +
                `End: ${task['end'].slice(0, -3)}`;

            // time interval correction
            let x_start = new Date(
                (task['start'] > filter_data['start_date']) ? task['start'] : filter_data['start_date']
            );
            let x_end = new Date(task['end']);
            if (task['duration'] < 1000) {
                x_end = new Date(x_start.getTime() + 1000);
            }
            x_end = (x_end < new Date(filter_data['end_date'])) ? x_end : new Date(filter_data['end_date']);

            // status border
            data.push({
                x: [x_start, x_start, x_start, x_end, x_end, x_end, x_start, x_start],
                y: [y - 0.5, y, y + 0.5, y + 0.5, y, y - 0.5, y - 0.5, y],
                type: 'scatter',
                mode: 'lines',
                line: {
                    color: color,
                    width: 3,
                },
                hoverinfo: 'none',
                legendgroup: actor,
                showlegend: false,
                xaxis: 'x2',
            });

            // actor box
            data.push({
                x: [x_start, x_start, x_end, x_end],
                y: [y - 0.5, y + 0.5, y + 0.5, y - 0.5],
                type: 'scatter',
                mode: 'lines',
                line: {width: 0},
                fill: 'toself',
                fillcolor: task['color'],
                opacity: 0.65,
                name: actor,
                text: text,
                hoverinfo: 'text',
                legendgroup: actor,
                showlegend: !unique_actors.has(actor),
            });
            unique_actors.add(actor);
            unique_status.add(status);
            y_time.push(new Date(task['start']).toLocaleTimeString());
            y += bargap;
        });

        let layout = {
            title: {
                text: "{{ chart_title|safe }}",
                font: {
                    size: 15,
                },
            },
            xaxis: {
                range: [filter_data['start_date'], filter_data['end_date']],
            },
            xaxis2: {
                range: [filter_data['start_date'], filter_data['end_date']],
                matches: 'x',
                overlaying: 'x',
                side: 'top',
            },
            yaxis: {
                title: 'Start time',
                zeroline: false,
                automargin: false,
                range: [-1, (y_time.length - 1) * bargap * 2.4 + 3.5],
                type: 'category',
                tickmode: 'array',
                tickvals: [...Array(y_time.length).keys()].map(i => i * bargap),
                ticktext: [...y_time],
            },
            legend: {
                title: {
                    text: `Actor <br>`,
                },
                itemclick: false,
                itemdoubleclick: false,
            },
            hoverlabel: {
                bgcolor: '#eaeaea',
                align: 'left',
            },
            height: heightCalculator(y_time.length, 25, 300),
        };

        if (data.length > 0) {
            Plotly.newPlot(chart, data, layout, chart_config);

            // status color label
            let status_color_label = 'Border colors: ';
            unique_status.forEach(function (status) {
                status_color_label += `<span style="color:${status_color[status]}">${status}</span> `;
            });
            let status_color_obj = document.getElementById("status_color");
            status_color_obj.innerHTML = status_color_label;

            // changing the cursor when hovering over an interval
            let cursor_obj = document.getElementsByClassName('nsewdrag')[0];
            chart.on('plotly_hover', function () {
                cursor_obj.style.cursor = 'default';
            }).on('plotly_unhover', function () {
                cursor_obj.style.cursor = '';
            });

            // changing the height of the chart when zooming
            let tasks_number = y_time.length;
            chart.on('plotly_relayout', function (eventdata) {
                if (eventdata['yaxis.range[0]']) {
                    // selected number of tasks
                    let tasks_number_selected = (eventdata['yaxis.range[1]'] - eventdata['yaxis.range[0]'] - 1.5) / 3;

                    if (tasks_number_selected !== tasks_number) {
                        tasks_number = tasks_number_selected;
                        // minimum chart height according to document height
                        let height_min = document.documentElement.clientHeight * 0.88;
                        Plotly.react(chart, data, Object.assign(
                            {}, layout, {height: heightCalculator(tasks_number, 25, 300, height_min)}
                        ));
                    }
                }
            });
        }
    </script>
{% endblock %}