'use strict';

/* global Chart:false */
/* global asyncForEach:false */
/* global angular:false */
/* global $:false */
/* global asyncSeries */

angular.module('Application').controller('GraphsController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
    Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); });

    $scope.memoryUsageSystem = [];
    $scope.memoryUsageApps = [];
    $scope.activeApp = null;
    $scope.disks = [];
    $scope.memory = null;

    $scope.errorMessage = '';

    $scope.installedApps = Client.getInstalledApps();

    function bytesToMegaBytes(value) {
        return (value/1024/1024).toFixed(2);
    }

    // http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
    function getRandomColor() {
        var letters = '0123456789ABCDEF'.split('');
        var color = '#';
        for (var i = 0; i < 6; i++ ) {
            color += letters[Math.floor(Math.random() * 16)];
        }
        return color;
    }

    var colorIndex = 0;
    var colors = [ '#2196F3', '#3995b1', '#f0ad4e', '#ff4c4c' ];
    function getNextColor() {
        if (colors[colorIndex+1]) return colors[colorIndex++];
        return getRandomColor();
    }

    $scope.setError = function (context, error) {
        $scope.errorMessage = 'Error loading ' + context + ' stats : ' + error.message + '. Try restarting the graphite service.';
    };

    $scope.setMemoryApp = function (app, color) {
        $scope.activeApp = app;

        var timePeriod = 12 * 60;    // in minutes
        var timeBucketSize = 60;    // in minutes

        var target;
        if (app === 'system') target = 'summarize(sum(collectd.localhost.memory.memory-used, collectd.localhost.swap.swap-used), "' + timeBucketSize + 'min", "avg")';
        else target = 'summarize(collectd.localhost.table-' + app.id + '-memory.gauge-rss, "' + timeBucketSize + 'min", "avg")';

        Client.graphs([target], '-' + timePeriod + 'min', {}, function (error, result) {
            if (error) return $scope.setError('memory', error);

            // translate the data from bytes to MB
            var datapoints = result[0].datapoints.map(function (d) { return parseInt((d[0] / 1024 / 1024).toFixed(2)); });
            var labels = datapoints.map(function (d, index) {
                var dateTime = new Date(Date.now() - ((timePeriod - (index * timeBucketSize)) * 60 *1000));
                return ('0' + dateTime.getHours()).slice(-2) + ':00';
            });

            var data = {
                labels: labels,
                datasets: [{
                    label: 'Memory',
                    backgroundColor: color || '#82C4F8',
                    borderColor: color || '#2196F3',
                    borderWidth: 2,
                    pointBackgroundColor: color || 'rgba(151,187,205,1)',
                    pointBorderColor: color || '#2196F3',
                    pointHoverBackgroundColor: color || '#82C4F8',
                    pointHoverBorderColor: color || '#82C4F8',
                    data: datapoints
                }]
            };

            var scaleMax = 0;
            if ($scope.activeApp === 'system') {
                scaleMax = $scope.memory.memory + $scope.memory.swap;
            } else {
                scaleMax = $scope.activeApp.memoryLimit || $scope.activeApp.manifest.memoryLimit || (256 * 1024 * 1024);
            }

            var stepSize;
            if (scaleMax >= (8 * 1024 * 1024 * 1024)) stepSize = 1024;
            else if (scaleMax >= (4 * 1024 * 1024 * 1024)) stepSize = 512;
            else if (scaleMax >= (2 * 1024 * 1024 * 1024)) stepSize = 256;
            else stepSize = 128;

            var options = {
                legend: {
                    display: false
                },
                scales: {
                    yAxes: [{
                        ticks: {
                            min: 0,
                            max: Math.round(scaleMax / (1024 * 1024)),
                            stepSize: stepSize,
                            beginAtZero: true
                        }
                    }]
                }
            };

            var ctx = $('#memoryAppChart').get(0).getContext('2d');
            new Chart(ctx, { type: 'line', data: data, options: options });
        });
    };

    $scope.updateDiskGraphs = function () {
        // https://graphite.readthedocs.io/en/latest/render_api.html#paths-and-wildcards
        // on scaleway, for some reason docker devices are collected as part of collectd
        // until we figure why just hardcode popular disk devices - https://www.mjmwired.net/kernel/Documentation/devices.txt
        Client.disks(function (error, result) {
            if (error) return $scope.setError('disk', error);

            result.disks.forEach(function (disk, index) {
                disk.id = index;
                disk.contains = [];

                if (disk.filesystem === result.platformDataDisk) disk.contains.push({ label: 'Platform data', id: 'platformdata', usage: 0 });
                if (disk.filesystem === result.boxDataDisk) disk.contains.push({ label: 'Box data', id: 'boxdata', usage: 0 });
                if (disk.filesystem === result.dockerDataDisk) disk.contains.push({ label: 'Docker images', id: 'docker', usage: 0 });
                if (disk.filesystem === result.mailDataDisk) disk.contains.push({ label: 'Email data', id: 'maildata', usage: 0 });

                const apps = Object.keys(result.apps).filter(function (appId) { return result.apps[appId] === disk.filesystem; });
                apps.forEach(function (appId) {
                    var app = Client.getCachedAppSync(appId);
                    disk.contains.push({ app: app, label: app.label || app.fqdn, id: appId, usage: 0 });
                });
            });

            $scope.disks = result.disks;

            // lazy fetch graphite data
            $scope.disks.forEach(function (disk) {
                // /dev/sda1 -> sda1
                // /dev/mapper/foo -> mapper_foo (see #348)
                var diskName = disk.filesystem.slice(disk.filesystem.indexOf('/', 1) + 1);
                diskName = diskName.replace(/\//g, '_');

                Client.graphs([
                    'absolute(collectd.localhost.df-' + diskName + '.df_complex-free)',
                    'absolute(collectd.localhost.df-' + diskName + '.df_complex-reserved)',
                    'absolute(collectd.localhost.df-' + diskName + '.df_complex-used)'
                ], '-1min', {}, function (error, data) {
                    if (error) return $scope.setError('disk', error);

                    disk.size = data[2].datapoints[0][0] + data[1].datapoints[0][0] + data[0].datapoints[0][0];
                    disk.free = data[0].datapoints[0][0];
                    disk.occupied = data[2].datapoints[0][0] + data[1].datapoints[0][0];

                    colorIndex = 0;
                    disk.contains.forEach(function (content) {
                        content.color = getNextColor();
                    });

                    // get disk usage data
                    var graphiteQueries = disk.contains.map(function (content) {
                        return 'absolute(collectd.localhost.du-' + content.id + '.capacity-usage)';
                    });

                    Client.graphs(graphiteQueries, '-1day', { noNullPoints: true }, function (error, data) {
                        if (error) return $scope.setError('disk', error);

                        var usageOther = disk.occupied;

                        data.forEach(function (d) {
                            var content = disk.contains.find(function (content) { return d.target.indexOf(content.id) !== -1; });
                            if (!content) return; // didn't match any content

                            var tmp = d.datapoints[d.datapoints.length-1][0];
                            content.usage = tmp;

                            // deduct from overal disk usage to track other
                            usageOther -= tmp;
                        });

                        // add content container for other non tracked data
                        disk.contains.push({
                            label: 'Other',
                            id: 'other',
                            color: '#27CE65',
                            usage: usageOther
                        });
                    });
                });
            });
        });
    };

    $scope.updateMemorySystemChart = function () {
        var targets = [];
        var targetsInfo = [];

        targets.push('summarize(collectd.localhost.memory.memory-used, "1min", "avg")');
        targetsInfo.push({ label: 'Memory (RAM)', color: '#2196F3' });

        targets.push('summarize(collectd.localhost.swap.swap-used, "1min", "avg")');
        targetsInfo.push({ label: 'Swap', color: '#2196A9' });

        targets.push('summarize(sum(collectd.localhost.memory.memory-buffered, collectd.localhost.memory.memory-cached), "1min", "avg")');
        targetsInfo.push({ label: 'Memory (Cached)', color: '#f0ad4e' });

        targets.push('summarize(collectd.localhost.swap.swap-cached, "1min", "avg")');
        targetsInfo.push({ label: 'Swap (Cached)', color: '#f0cd4e' });

        targets.push('summarize(collectd.localhost.memory.memory-free, "1min", "avg")');
        targetsInfo.push({ label: 'Memory (Free)', color: '#27DD65' });

        targets.push('summarize(collectd.localhost.swap.swap-free, "1min", "avg")');
        targetsInfo.push({ label: 'Swap (Free)', color: '#27CE65' });

        Client.graphs(targets, '-1min', {}, function (error, result) {
            if (error) return $scope.setError('memory', error);

            $scope.memoryUsageSystem = result.map(function (data, index) {
                return {
                    value: bytesToMegaBytes(data.datapoints[0][0]),
                    color: targetsInfo[index].color,
                    highlight: targetsInfo[index].color,
                    label: targetsInfo[index].label
                };
            });

            var tmp = {
                datasets: [{
                    data: result.map(function (data) { return bytesToMegaBytes(data.datapoints[0][0]); }),
                    backgroundColor: result.map(function (data, index) { return targetsInfo[index].color; })
                }],
                labels: result.map(function (data, index) { return targetsInfo[index].label; })
            };

            var options = {
                onClick: function (/*event, dataset*/) {
                    $scope.setMemoryApp('system');
                },
                legend: { display: false }
            };

            var ctx = $('#memoryUsageSystemChart').get(0).getContext('2d');
            new Chart(ctx, { type: 'doughnut', data: tmp, options: options });
        });
    };

    $scope.updateMemoryAppsChart = function () {
        var targets = [];
        var targetsInfo = [];

        colorIndex = 0;
        $scope.installedApps.forEach(function (app) {
            targets.push('summarize(collectd.localhost.table-' + app.id + '-memory.gauge-rss, "1min", "avg")');
            targetsInfo.push({
                label: app.fqdn,
                color: getNextColor(),
                app: app
            });
        });

        // we split up the request, to avoid too large query strings into graphite
        var tmp = [];
        var aggregatedResult= [];

        while (targets.length > 0) tmp.push(targets.splice(0, 10));

        asyncForEach(tmp, function (targets, callback) {
            Client.graphs(targets, '-1min', {}, function (error, result) {
                if (error) return callback(error);

                aggregatedResult = aggregatedResult.concat(result);

                callback(null);
            });
        }, function (error) {
            if (error) return $scope.setError('memory', error);

            $scope.memoryUsageApps = aggregatedResult.map(function (data, index) {
                return {
                    value: bytesToMegaBytes(data.datapoints[0][0]),
                    color: targetsInfo[index].color,
                    highlight: targetsInfo[index].color,
                    label: targetsInfo[index].label
                };
            });

            var tmp = {
                datasets: [{
                    data: aggregatedResult.map(function (data) { return bytesToMegaBytes(data.datapoints[0][0]); }),
                    backgroundColor: aggregatedResult.map(function (data, index) { return targetsInfo[index].color; })
                }],
                labels: aggregatedResult.map(function (data, index) { return targetsInfo[index].label; })
            };

            var options = {
                onClick: function (event, dataset) {
                    var selectedDataInfo = targetsInfo.find(function (info) { return info.label === dataset[0]._model.label; });
                    if (selectedDataInfo) $scope.setMemoryApp(selectedDataInfo.app, selectedDataInfo.color);
                },
                legend: { display: false }
            };

            var ctx = $('#memoryUsageAppsChart').get(0).getContext('2d');
            new Chart(ctx, { type: 'doughnut', data: tmp, options: options });
        });
    };

    function getMemory(callback) {
        Client.memory(function (error, memory) {
            if (error) console.error(error);

            $scope.memory = memory;

            callback();
        });
    }

    Client.onReady(function () {
        getMemory(function () {
            $scope.updateDiskGraphs();
            $scope.updateMemorySystemChart();
            $scope.updateMemoryAppsChart();
            $scope.setMemoryApp('system');
        });
    });

    $('.modal-backdrop').remove();
}]);
