/*!
 * Authentic Theme (https://github.com/authentic-theme/authentic-theme)
 * Copyright Ilia Rostovtsev <ilia@virtualmin.com>
 * Licensed under MIT (https://github.com/authentic-theme/authentic-theme/blob/master/LICENSE)
 */

/* jshint strict: true */
/* jshint esversion: 6 */
/* jshint jquery: true */

'use strict';

// Stats module
const stats = {
    general: {
        timeout: settings_sysinfo_query_timeout,
        stopped: 1,
        killed: 0,
        error: 0,
        requery: 0,
        call: {},

        // Import globals
        /* jshint -W117 */
        extend: {
            prefix: v___location_prefix,
            error: connection_error,
            prevent: theme_updating,
            language: theme_language,
            convert: {
                size: Convert.nice_size
            },
            chart: Chartist,
            dayjs: dayjs,
            locale: {
                time: config_portable_theme_locale_format_time,
                offset: () => {
                    return get_utc_offset()
                }
            },
            state: () => {
                return theme.visibility.get()
            },
            stored_length: () => {
                return settings_sysinfo_real_time_stored_length
            },
            enabled: () => {
                return settings_sysinfo_real_time_status
            },
        },

        // Define selectors
        selector: {
            chart: {
                container: {
                    parent: 'live_stats',
                    data: 'data-chart',
                },
                loader: 'data-charts-loader'

            },
            collapse: 'collapse',
            dashboard: 'system-status',
            slider: 'info-container',
            piechart: 'piechart'
        },

        // Get data
        query: function() {

            // Repeat right after success
            (this.stopped && !this.killed) && (() => {
                this.stopped = 0;
                this.call = {};

                // Disable stats upon theme update
                if (this.extend.prevent()) {
                    return
                }

                // Live charts already loaded
                let extra = String();
                if (document.querySelector(`[${this.selector.chart.loader}]`)) {
                    extra = '&sdata=1';
                }

                this.call = $.ajax({
                    context: this,
                    url: `${this.extend.prefix}/stats.cgi?xhr-stats=general${extra}`,
                    error: function(xhr) {

                        // Abort is not an error, don't retry
                        if (xhr.statusText === 'abort') {
                            return
                        }

                        // Set error counter
                        this.error++;

                        // Show error
                        if (this.error > 3) {
                            this.extend.error(xhr, 1), this.stopped = 1, this.error = 0, this.requery = 0;
                            return;
                        }

                        // Retry again
                        !this.requery && (this.requery = setTimeout(() => {
                            this.stopped = 1, this.requery = 0, this.query();
                        }, (this.timeout * 6)));
                    },
                    success: function(data) {

                        // Reset error counter
                        this.error = 0;

                        // Take half a second delay, render and restart
                        setTimeout(() => {
                            this.render(data);
                        }, this.timeout);
                        this.stopped = 1;
                    },
                    dataType: "json",
                })
            })();
        },

        // Display changes
        render: function(data) {

            // Iterate through response
            Object.entries(data).map(([target, data]) => {
                let v = parseInt(data),
                    vo = (typeof data === 'object' ? data[(data.length - 1)] : false),
                    vt = (vo ? vo : v),
                    $pc = $(`#${this.selector.dashboard} .${this.selector.piechart}[data-charts*="${target}"]`),
                    $lc = $(`.${this.selector.slider} .${target}_percent`),
                    $od = $(`#${this.selector.dashboard} span[data-id="sysinfo_${target}"], 
                             .${this.selector.slider} span[data-data="${target}"]`),
                    cached = (target === 'fcached' ? 2 : (target === 'scached' ? 1 : 0));

                if (Number.isInteger(v)) {

                    // Update pie-charts
                    if ($pc.length) {
                        let piechart = $pc.data('easyPieChart');
                        piechart && piechart.update(v);
                    }

                    // Update line-charts
                    if ($lc.length) {
                        $lc.find('.bar').attr('style', 'width:' + v + '%');

                        // Update line-charts' text
                        let $dp = $lc.find('.description'),
                            $lb = $dp.text().split(":")[0];

                        $dp.attr('title', vo).text($lb + ": " + v + '% (' + vo + ')');
                    }

                    // Update other data
                    if ($od.length) {
                        if ($od.find('a').length) {
                            $od.find('a').text(vt);
                        } else {
                            $od.text(vt);
                        }
                    }
                }

                // Draw history graphs
                if (cached) {
                    let lds = `${this.selector.chart.container.parent}-${this.selector.collapse}`,
                        ld = $(`#${lds}`).find(`[${this.selector.chart.loader}]`);

                    // Process each supplied graph
                    Object.entries(data).map(([type, array]) => {
                        let options = {
                                chart: {
                                    type: () => {
                                        return (type === 'proc' || type === 'disk' || type === 'net');
                                    },
                                    bandwidth: () => {
                                        return (type === 'disk' || type === 'net');
                                    },
                                    fill: function() { return this.type() ? false : true },
                                    high: function() { return this.type() ? undefined : 100 },
                                    threshold: function() { return this.type() ? -1 : 50 },
                                    height: '100px',
                                }
                            },
                            lg = this.extend.language(`${this.selector.chart.container.parent}_${type}`),
                            tg = $(`#${lds}`).find(`[${this.selector.chart.container.data}=${type}]`),
                            sr = [{
                                name: `series-${type}`,
                                data: array
                            }];

                        // Don't run further in case there is no container
                        if (!tg.length) {
                            return
                        }

                        // Extend data object expected way to draw multiple series in single graph
                        if (array[0] && typeof array[0].y === 'object') {
                            sr = [];
                            array[0].y.forEach(function(x, i) {
                                let data = [];
                                array.forEach(function(n) {
                                    data.push({
                                        data: { x: n.x, y: n.y[i] }
                                    })
                                });
                                sr.push({
                                    name: `series-${type}-${i}`,
                                    data: data
                                })
                            });
                        }

                        // Update series if chart already exist
                        if (tg[0] && tg[0].textContent) {
                            if (cached === 1) {
                                let lf = parseInt(this.extend.stored_length());
                                if (lf < 600 || lf > 86400) {
                                    lf = 600;
                                }
                                let tdata = sr,
                                    cdata = this[`chart_${type}`].data.series,
                                    cdata_start,
                                    cdata_end,
                                    cdata_ready = new Promise((resolve) => {
                                        tdata.forEach(function(d, i, a) {
                                            cdata_start = cdata[i].data[0].x || cdata[i].data[0].data.x;
                                            cdata_end = cdata[i].data[cdata[i].data.length - 1].x || cdata[i].data[cdata[i].data.length - 1].data.x;
                                            cdata[i].data.push(d.data[0]);
                                            if (cdata_end - cdata_start > lf) {
                                                cdata[i].data.shift();
                                            }
                                            if (i === a.length - 1) {
                                                resolve()
                                            }
                                        });
                                    });
                                cdata_ready.then(() => {
                                    this[`chart_${type}`].update({
                                        series: cdata
                                    });
                                });
                            }
                        }

                        // Initialize chart the first time
                        else if (cached === 2) {
                            this[`chart_${type}`] = new this.extend.chart.Line(tg[0], {
                                series: sr,
                            }, {
                                axisX: {
                                    type: this.extend.chart.FixedScaleAxis,
                                    divisor: 12,
                                    labelInterpolationFnc: (value) => {
                                        return this.extend.dayjs(value * 1000).utcOffset(this.extend.locale.offset()).format(this.extend.locale.time);
                                    }
                                },
                                height: options.chart.height,
                                showArea: options.chart.fill(),
                                showPoint: !options.chart.fill(),
                                high: options.chart.high(),
                                low: 0,
                                fullWidth: true,
                                chartPadding: {
                                    left: 25
                                },
                                axisY: {
                                    onlyInteger: true,
                                    labelInterpolationFnc: (value) => {
                                        if (options.chart.fill()) {
                                            return (value ? (value + '%') : value);
                                        } else if (options.chart.bandwidth(value)) {
                                            if (type === 'net') {
                                                return (value ? this.extend.convert.size(value, {
                                                    'fixed': 0,
                                                    'bits': 1,
                                                    'round': 1,
                                                }) : value)
                                            }
                                            return (value ? this.extend.convert.size(value * 1000, {
                                                'fixed': 0,
                                                'round': 1,
                                            }) : value)
                                        } else {
                                            return value
                                        }
                                    },
                                },
                                plugins: [
                                    this.extend.chart.plugins.ctAxisTitle({
                                        axisY: {
                                            axisTitle: lg,
                                            axisClass: "ct-axis-title",
                                            offset: {
                                                x: 0,
                                                y: 9
                                            },
                                            flipTitle: true,
                                        }
                                    }),
                                    this.extend.chart.plugins.ctThreshold({
                                        threshold: options.chart.threshold(),
                                    }),
                                ]
                            });

                            // Remove loader
                            this[`chart_${type}`].on('created', () => ld.remove());
                        }
                    })
                }
            });
            (this.extend.state() || this.extend.enabled() === 2) && this.query();
        },

        // Stop querying
        disable: function() {
            let abort = this.call.abort;

            typeof abort === "function" &&
                (
                    abort.call(),
                    this.killed = 1,
                    this.stopped = 1,
                    this.call = {}
                );
        },

        // Check to enable stats after stop
        enable: function() {
            if (this.extend.enabled()) {
                this.stopped = 1, this.killed = 0, this.query();
            }
        }
    }
}