/**
 * logging
 */

define([
    'jquery',
    'app/init',
    'app/util',
    'app/counter',
    'bootbox',
    'app/lib/resize'
], ($, Init, Util, Counter, bootbox) => {

    'use strict';

    let logData = [];                                                                   // cache object for all log entries
    let logDataTable = null;                                                            // "Datatables" Object

    // Morris charts data
    let maxGraphDataCount = 30;                                                         // max date entries for a graph
    let chartData = {};                                                                 // chart Data object for all Morris Log graphs

    let config = {
        taskDialogId: 'pf-task-dialog',                                                 // id for map "task manager" dialog
        timestampCounterClass: 'pf-timestamp-counter',                                  // class for "timestamp" counter
        taskDialogStatusAreaClass: 'pf-task-dialog-status',                             // class for "status" dynamic area
        taskDialogLogTableAreaClass: 'pf-task-dialog-table',                            // class for "log table" dynamic area
        logGraphClass: 'pf-log-graph',                                                  // class for all log Morris graphs
        moduleHeadlineIconClass: 'pf-module-icon-button'                                // class for toolbar icons in the head
    };

    /**
     * updated "sync status" dynamic dialog area
     */
    let updateSyncStatus = () => {
        // check if task manager dialog is open
        let logDialog = $('#' + config.taskDialogId);
        if(logDialog.length){
            // dialog is open
            let statusArea = logDialog.find('.' + config.taskDialogStatusAreaClass);
            statusArea.destroyTooltips(true);
            requirejs(['text!templates/modules/sync_status.html', 'mustache'], (templateSyncStatus, Mustache) => {
                let data = {
                    timestampCounterClass: config.timestampCounterClass,
                    syncStatus: Init.syncStatus,
                    isWebSocket: () => {
                        return (Util.getSyncType() === 'webSocket');
                    },
                    isAjax: () => {
                        return (Util.getSyncType() === 'ajax');
                    }
                };

                let syncStatusElement = $(Mustache.render(templateSyncStatus, data ));
                Counter.destroyTimestampCounter(statusArea, true);

                statusArea.html(syncStatusElement);

                let counterElements = syncStatusElement.find('.' + config.timestampCounterClass);
                Counter.initTimestampCounter(counterElements);

                statusArea.initTooltips({
                    placement: 'right'
                });
            });
        }

    };

    /**
     * shows the logging dialog
     */
    let showDialog = () => {
        requirejs(['text!templates/dialog/task_manager.html', 'mustache'], function(templateTaskManagerDialog, Mustache){
            let data = {
                id: config.taskDialogId,
                dialogDynamicAreaClass: Util.config.dynamicAreaClass,
                taskDialogStatusAreaClass: config.taskDialogStatusAreaClass,
                taskDialogLogTableAreaClass: config.taskDialogLogTableAreaClass
            };

            let contentTaskManager = $( Mustache.render(templateTaskManagerDialog, data) );

            let rowElementGraphs = contentTaskManager.find('.row');
            let taskDialogLogTableAreaElement  = contentTaskManager.find('.' + config.taskDialogLogTableAreaClass);

            let logTable = $('<table>', {
                class: ['compact', 'stripe', 'order-column', 'row-border'].join(' ')
            });

            taskDialogLogTableAreaElement.append(logTable);

            // init log table
            logDataTable = logTable.DataTable({
                dom: '<"flex-row flex-between"<"flex-col"l><"flex-col"B><"flex-col"fS>>' +
                    '<"flex-row"<"flex-col flex-grow"tr>>' +
                    '<"flex-row flex-between"<"flex-col"i><"flex-col"p>>',
                buttons: {
                    name: 'tableTools',
                    buttons: [
                        {
                            extend: 'copy',
                            tag: 'a',
                            className: config.moduleHeadlineIconClass,
                            text: '<i class="fas fa-fw fa-copy"></i> copy',
                            exportOptions: {
                                orthogonal: 'export'
                            }
                        },
                        {
                            extend: 'csv',
                            tag: 'a',
                            className: config.moduleHeadlineIconClass,
                            text: '<i class="fas fa-fw fa-download"></i> csv',
                            exportOptions: {
                                orthogonal: 'export'
                            }
                        }
                    ]
                },
                paging: true,
                ordering: true,
                order: [1, 'desc'],
                hover: false,
                pageLength: 10,
                lengthMenu: [[5, 10, 25, 50, 100, -1], [5, 10, 25, 50, 100, 'All']],
                data: logData,                      // load cached logs (if available)
                language: {
                    emptyTable:  'No entries',
                    zeroRecords: 'No entries found',
                    lengthMenu:  'Show _MENU_',
                    info:        'Showing _START_ to _END_ of _TOTAL_ entries'
                },
                columnDefs: [
                    {
                        targets: 0,
                        name: 'status',
                        title: '<i class="fas fa-tag"></i>',
                        width: 18,
                        searchable: false,
                        class: ['text-center'].join(' '),
                        data: 'status',
                        render: {
                            display: (cellData, type, rowData, meta) => {
                                let statusClass = Util.getLogInfo(cellData, 'class');
                                return '<i class="fas fa-fw fa-circle txt-color ' + statusClass + '"></i>';
                            }
                        }
                    },{
                        targets: 1,
                        name: 'time',
                        title: '<i class="far fa-fw fa-clock"></i>',
                        width: 50,
                        searchable: true,
                        class: 'text-right',
                        data: 'timestamp',
                        render: {
                            display: (cellData, type, rowData, meta) => rowData.timestampFormatted
                        }
                    },{
                        targets: 2,
                        name: 'duration',
                        title: '<i class="fas fa-fw fa-history"></i>',
                        width: 35,
                        searchable: false,
                        class: 'text-right',
                        data: 'duration',
                        render: {
                            display: (cellData, type, rowData, meta) => {
                                let logStatus = getLogStatusByDuration(rowData.key, cellData);
                                let statusClass = Util.getLogInfo(logStatus, 'class');
                                return '<span class="txt-color ' + statusClass + '">' + cellData + '<small>ms</small></span>';
                            }
                        }
                    },{
                        targets: 3,
                        name: 'description',
                        title: 'description',
                        searchable: true,
                        data: 'description'
                    },{
                        targets: 4,
                        name: 'logType',
                        title: 'type',
                        width: 40,
                        searchable: true,
                        class: ['text-center'].join(' '),
                        data: 'logType',
                        render: {
                            display: (cellData, type, rowData, meta) => {
                                let typeIconClass = getLogTypeIconClass(cellData);
                                return '<i class="fas ' + typeIconClass + '"></i>';
                            }
                        }
                    },{
                        targets: 5,
                        name: 'process',
                        title: 'Prozess-ID',
                        width: 80,
                        searchable: false,
                        class: 'text-right',
                        data: 'key'
                    }
                ]

            });

            // open dialog
            let logDialog = bootbox.dialog({
                title: 'Task-Manager',
                message: contentTaskManager,
                size: 'large',
                buttons: {
                    close: {
                        label: 'close',
                        className: 'btn-default'
                    }
                }
            });

            // modal dialog is shown
            logDialog.on('shown.bs.modal', function(e){
                updateSyncStatus();

                // show Morris graphs ----------------------------------------------------------

                let labelYFormat = val => Math.round(val) + 'ms';

                for(let key in chartData){
                    if(chartData.hasOwnProperty(key)){
                        // create a chart for each key

                        let colElementGraph = $('<div>', {
                            class: ['col-md-6'].join(' ')
                        });


                        // graph element
                        let graphElement = $('<div>', {
                            class: config.logGraphClass
                        });

                        let graphArea = $('<div>', {
                            class: Util.config.dynamicAreaClass
                        }).append(  graphElement );

                        let headline = $('<h4>', {
                            text: key
                        }).prepend(
                            $('<span>', {
                                class: ['txt-color', 'txt-color-grayLight'].join(' '),
                                text: 'Prozess-ID: '
                            })
                        );

                        // show update ping between function calls
                        let updateElement = $('<small>', {
                            class: ['txt-color', 'txt-color-blue', 'pull-right'].join(' ')
                        });
                        headline.append(updateElement).append('<br>');

                        // show average execution time
                        let averageElement = $('<small>', {
                            class: 'pull-right'
                        });
                        headline.append(averageElement);

                        colElementGraph.append(headline);
                        colElementGraph.append(graphArea);

                        graphArea.showLoadingAnimation();

                        rowElementGraphs.append( colElementGraph );

                        // cache DOM Elements that will be updated frequently
                        chartData[key].averageElement = averageElement;
                        chartData[key].updateElement = updateElement;

                        chartData[key].graph = Morris.Area({
                            element: graphElement,
                            data: [],
                            xkey: 'x',
                            ykeys: ['y'],
                            labels: [key],
                            lineColors: ['#375959'],
                            lineWidth: 2,
                            pointSize: 3,
                            pointFillColors: ['#477372'],
                            pointStrokeColors: ['#313335'],
                            ymin: 0,
                            smooth: false,
                            hideHover: true,
                            parseTime: false,
                            postUnits: 'ms',
                            yLabelFormat: labelYFormat,
                            goals: [],
                            goalStrokeWidth: 1,
                            goalLineColors: ['#66c84f'],
                            grid: true,
                            gridTextColor: '#63676a',
                            gridTextSize: 9,
                            gridTextFamily: 'Arial, "Oxygen Bold"',
                            gridTextWeight: 'bold',
                            gridStrokeWidth: 0.3,
                            resize: true, // we use our own resize function
                            dataLabels: false,
                            hoverReversed: true,
                            // Area chart specific options
                            behaveLikeLine: true,
                            fillOpacity: 0.5,
                            belowArea: true,
                            areaColors: ['#3c3f41'],
                            // Not documented but working
                            padding: 8,
                        });

                        updateLogGraph(key);

                        graphArea.hideLoadingAnimation();
                    }
                }
/*
                Util.getResizeManager().observe(
                    this.querySelector('.modal-dialog'),
                    (el, contentRect) => Object.values(chartData).forEach(data => {
                        if(data.graph) data.graph.redraw();
                    }),
                    {debounce: true, ms: 100}
                );*/
            });

            // modal dialog before hide
            logDialog.on('hide.bs.modal', function(e){
                Object.entries(chartData).forEach(([key, data]) => {
                    if(data.graph){
                        data.graph.destroy();
                        delete chartData[key].graph;
                    }
                });

                // destroy logTable
                logDataTable.destroy(true);
                logDataTable= null;

                // remove event -> prevent calling this multiple times
                $(this).off('hide.bs.modal');
            });

        });

    };

    /**
     * updates the log graph for a log key
     * @param key
     * @param duration (if undefined -> just update graph with current data)
     */
    let updateLogGraph = (key, duration) => {

        // check if graph data already exist
        if(!chartData.hasOwnProperty(key)){
            chartData[key] = {
                data: [],
                graph: null,
                averageElement: null,
                updateElement: null
            };
        }

        // add new value
        if(duration !== undefined){
            chartData[key].data.unshift(duration);
        }

        if(chartData[key].data.length > maxGraphDataCount){
            chartData[key].data = chartData[key].data.slice(0, maxGraphDataCount);
        }

        let getGraphData = data => {
            let tempChartData = {
                data: [],
                dataSum: 0,
                average: 0
            };

            for(let x = 0; x < maxGraphDataCount; x++){
                let value = 0;
                if(data[x]){
                    value = data[x];
                    tempChartData.dataSum = Number((tempChartData.dataSum + value).toFixed(2));
                }

                tempChartData.data.push({
                    x: x,
                    y: value
                });
            }

            // calculate average
            tempChartData.average = Number((tempChartData.dataSum / data.length).toFixed(2));

            return tempChartData;
        };

        let tempChartData = getGraphData(chartData[key].data);

        // add new data to graph (Morris chart) - if is already initialized
        if(chartData[key].graph){
            let avgElement = chartData[key].averageElement;
            let updateElement = chartData[key].updateElement;

            let delay = Util.getCurrentTriggerDelay(key, 0);

            if(delay){
                updateElement[0].textContent = ' delay: ' + delay.toFixed(2) + ' ms';
            }

            // set/change average line
            chartData[key].graph.options.goals = [tempChartData.average];

            // change avg. display
            avgElement[0].textContent = 'avg. ' + tempChartData.average.toFixed(2) + ' ms';

            let avgStatus = getLogStatusByDuration(key, tempChartData.average);
            let avgStatusClass = Util.getLogInfo( avgStatus, 'class');

            //change avg. display class
            if( !avgElement.hasClass(avgStatusClass) ){
                // avg status changed!
                avgElement.removeClass().addClass('pull-right txt-color ' + avgStatusClass);

                // change goals line color
                if(avgStatus === 'warning'){
                    chartData[key].graph.options.goalLineColors = ['#e28a0d'];
                    $(document).setProgramStatus('slow connection');
                }else{
                    chartData[key].graph.options.goalLineColors = ['#5cb85c'];
                }
            }

            // set new data and redraw
            chartData[key].graph.setData( tempChartData.data );
        }

        return tempChartData.data;
    };

    /**
     * get the log "status" by log duration (ms).
     * If duration > warning limit -> show as warning
     * @param logKey
     * @param logDuration
     * @returns {string}
     */
    let getLogStatusByDuration = (logKey, logDuration) => {
        let logStatus = 'info';
        if(logDuration > Init.timer[logKey].EXECUTION_LIMIT){
            logStatus = 'warning';
        }
        return logStatus;
    };

    /**
     * get the css class for a specific log type
     * @param logType
     * @returns {string}
     */
    let getLogTypeIconClass = logType => {
        let logIconClass = '';

        switch(logType){
            case 'client':
                logIconClass = 'fa-user';
                break;
            case 'server':
                logIconClass = 'fa-download';
                break;
        }

        return logIconClass;
    };

    /**
     * init logging -> set global log events
     */
    let init = () => {

        let maxEntries = 150;

        $(window).on('pf:syncStatus', () => {
            updateSyncStatus();
        });

        // set global logging listener
        $(window).on('pf:log', (e, logKey, options) => {

            // check required logging information
            if(
                options &&
                options.duration &&
                options.description
            ){
                let logDescription = options.description;
                let logDuration = options.duration;
                let logType = options.type;

                // update graph data
                updateLogGraph(logKey, logDuration);

                let time = Util.getServerTime();
                let timestamp = time.getTime();
                let timestampFormatted = time.toLocaleTimeString('en-US', { hour12: false });

                let logRowData = {
                    status: getLogStatusByDuration(logKey, logDuration),
                    timestamp: timestamp,
                    timestampFormatted: timestampFormatted,
                    duration: logDuration,
                    description: logDescription,
                    logType: logType,
                    key: logKey
                };

                if(logDataTable){
                    // add row if dataTable is initialized before new log
                    logDataTable.row.add( logRowData ).draw(false);
                }else{
                    // add row data to cache
                    logData.push(logRowData);
                }
            }

            // delete old log entries from table ---------------------------------
            if(logData.length >= maxEntries){
                if(logDataTable){
                    logDataTable.rows(0, {order:'index'}).remove().draw(false);
                }else{
                    logData.shift();
                }
            }

            // cache logs in order to keep previous logs in table after reopening the dialog
            if(logDataTable){
                logData = logDataTable.rows({order:'index'}).data();
            }

        });
    };


    return {
        init: init,
        showDialog: showDialog
    };
});