'use strict';

/* global angular */
/* global $ */
/* global async */
/* global Chart */

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

    $scope.config = Client.getConfig();
    $scope.memory = null;
    $scope.activeTab = 0;
    $scope.volumesById = {};

    // 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.disks = {
        busy: true,
        errorMessage: '',
        disks: [],

        setError: function (error) {
            $scope.disks.errorMessage = 'Error loading disk : ' + error.message;
        },

        update: function () {
            $scope.disks.busy = true;

            // 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.disks.setError(error);

                // segregate locations into the correct disks based on 'filesystem'
                result.disks.forEach(function (disk, index) {
                    disk.id = index;
                    disk.contains = [];

                    if (disk.filesystem === result.platformDataDisk) disk.contains.push({ type: 'standard', label: 'Platform data', id: 'platformdata', usage: 0 });
                    if (disk.filesystem === result.boxDataDisk) disk.contains.push({ type: 'standard', label: 'Box data', id: 'boxdata', usage: 0 });
                    if (disk.filesystem === result.dockerDataDisk) disk.contains.push({ type: 'standard', label: 'Docker images', id: 'docker', usage: 0 });
                    if (disk.filesystem === result.mailDataDisk) disk.contains.push({ type: 'standard', label: 'Email data', id: 'maildata', usage: 0 });
                    if (disk.filesystem === result.backupsDisk) disk.contains.push({ type: 'standard', label: 'Backup data', id: 'cloudron-backup', 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({ type: 'app', app: app, label: app.label || app.fqdn, id: appId, usage: 0 });
                    });

                    const volumes = Object.keys(result.volumes).filter(function (volumeId) { return result.volumes[volumeId] === disk.filesystem; });
                    volumes.forEach(function (volumeId) {
                        var volume = $scope.volumesById[volumeId];
                        disk.contains.push({ type: 'volume', volume: volume, label: volume.name, id: volumeId, usage: 0 });
                    });
                });

                $scope.disks.disks = result.disks; // [ { filesystem, type, size, used, available, capacity, mountpoint }]

                // render data of each disk
                async.eachSeries(result.disks, function (disk, iteratorCallback) {
                    // /dev/sda1 -> sda1
                    // /dev/mapper/foo -> mapper_foo (see #348)
                    var diskName = disk.filesystem.slice(disk.filesystem.indexOf('/', 1) + 1);
                    diskName = diskName.replace(/\//g, '_');

                    // use collectd instead of df data so the timeframe matches with the du data
                    Client.graphs([
                        'absolute(collectd.localhost.df-' + diskName + '.df_complex-free)',
                        'absolute(collectd.localhost.df-' + diskName + '.df_complex-reserved)', // reserved for root (default: 5%) tune2fs -l/m
                        'absolute(collectd.localhost.df-' + diskName + '.df_complex-used)'
                    ], '-1min', {}, function (error, data) {
                        if (error) return iteratorCallback(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];

                        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 iteratorCallback(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;
                            });

                            disk.contains.sort(function (x, y) { return y.usage - x.usage; }); // sort by usage

                            if ($scope.disks.disks[0] === disk) { // the root mount point is the first disk. keep this 'contains' in the end
                                disk.contains.push({
                                    type: 'standard',
                                    label: 'Everything else (Ubuntu, Swap, etc)',
                                    id: 'other',
                                    color: '#27CE65',
                                    usage: usageOther
                                });
                            } else {
                                disk.contains.push({
                                    type: 'standard',
                                    label: 'Used',
                                    id: 'other',
                                    color: '#27CE65',
                                    usage: usageOther
                                });
                            }

                            iteratorCallback();
                        });
                    });
                }, function iteratorDone(error) {
                    if (error) $scope.disks.setError(error);
                    $scope.disks.busy = false;
                });
            });
        }
    };

    $scope.graphs = {
        error: {},

        period: 6,
        periodLabel: '6 hours',
        memoryChart: null,
        diskChart: null,

        setPeriod: function (hours, label) {
            $scope.graphs.period = hours;
            $scope.graphs.periodLabel = label;
            $scope.graphs.show();
        },

        show: function () {
            var apps = Client.getInstalledApps();

            // both in minutes
            var timePeriod = $scope.graphs.period * 60;
            var timeBucketSize = $scope.graphs.period > 24 ? (6*60) : 5;

            function fillGraph(canvasId, data, additionalData, label, chartPropertyName, max, valueDivider) {
                // translate the data from bytes to MB
                var datapoints = data.datapoints.map(function (d) { return parseInt((d[0] / valueDivider).toFixed(2)); });
                var labels = datapoints.map(function (d, index) {
                    var dateTime = new Date(Date.now() - ((timePeriod - (index * timeBucketSize)) * 60 *1000));

                    if ($scope.graphs.period > 24) {
                        return dateTime.toLocaleDateString();
                    } else {
                        return dateTime.toLocaleTimeString();
                    }
                });

                var datasets = [{
                    label: label,
                    backgroundColor: '#82C4F844',
                    borderColor: '#2196F3',
                    borderWidth: 1,
                    radius: 0,
                    data: datapoints
                }];

                if (Array.isArray(additionalData)) {
                    additionalData.forEach(function (data, index) {
                        datasets.push({
                            label: apps[index].fqdn,
                            fill: false,
                            borderColor: getRandomColor(),
                            borderWidth: 1,
                            radius: 0,
                            data: data.datapoints.map(function (d) { return parseInt((d[0] / valueDivider).toFixed(2)); })
                        });
                    });
                }

                var chartData = {
                    labels: labels,
                    datasets: datasets
                };

                var options = {
                    maintainAspectRatio: true,
                    aspectRatio: 2.5,
                    legend: {
                        display: false
                    },
                    tooltips: {
                        intersect: false
                    },
                    scales: {
                        xAxes: [{
                            ticks: {
                                autoSkipPadding: 20,
                            }
                        }],
                        yAxes: [{
                            ticks: {
                                min: 0,
                                max: max,
                                beginAtZero: true
                            }
                        }]
                    }
                };

                var ctx = $(canvasId).get(0).getContext('2d');

                if ($scope.graphs[chartPropertyName]) $scope.graphs[chartPropertyName].destroy();
                $scope.graphs[chartPropertyName] = new Chart(ctx, { type: 'line', data: chartData, options: options });
            }

            var cpuQuery = 'summarize(sum(collectd.localhost.aggregation-cpu-average.cpu-system, collectd.localhost.aggregation-cpu-average.cpu-user), "' + timeBucketSize + 'min", "avg")';
            var systemMemoryQuery = 'summarize(sum(collectd.localhost.memory.memory-used, collectd.localhost.swap.swap-used), "' + timeBucketSize + 'min", "avg")';

            var appQueries = [];
            apps.forEach(function (app) {
                appQueries.push('summarize(collectd.localhost.table-' + app.id + '-memory.gauge-rss, "' + timeBucketSize + 'min", "avg")');
            });

            Client.graphs([ cpuQuery, systemMemoryQuery ].concat(appQueries), '-' + timePeriod + 'min', {}, function (error, result) {
                if (error) return console.error(error);

                fillGraph('#graphsCPUChart', result[0], null, 'CPU Usage', 'cpuChart', 100, 1);
                fillGraph('#graphsSystemMemoryChart', result[1], result.slice(2), 'Memory', 'memoryChart', Number.parseInt(($scope.memory.memory + $scope.memory.swap) / 1024 / 1024), 1024 * 1024);
            });
        }
    };

    function refreshVolumes(callback) {
        Client.getVolumes(function (error, results) {
            if (error) return console.error(error);

            $scope.volumesById = {};
            results.forEach(function (v) { $scope.volumesById[v.id] = v; });

            callback();
        });
    }

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

            $scope.memory = memory;

            refreshVolumes(function (error) {
                if (error) console.error(error);

                $scope.disks.update();
            });
        });
    });

    Client.onReconnect(function () {
        $scope.reboot.busy = false;
    });

}]);
