angular.module('quasardbGUIApp.services', []).

// quasardb querying service
factory('quasardbAPIService', ['$http', function($http) {
    var quasardbAPI = {};
    var regexpDateHistory = /^[0-9]{4}[0-1]{1}[0-9]{1}[0-3]{1}[0-9]{1}[0-2]{1}[0-9]{1}[0-5]{1}[0-9]{1}$/;

    // ** Cluster services : **
    //     -> Ask quasardb for cluster topology
    quasardbAPI.getClusterTopology = function(id, mock) {
        if (mock) {
            return $http({
                method: 'GET',
                url: 'mocks/cluster_topology.json'
            });
        } else {
            return $http({
                method : (id ? 'JSONP' : 'GET'),
                headers: {},
                url : '../cluster_topology' + (id ? '?daemon=' + id + '&callback=JSON_CALLBACK': '')
            });
        }
    };

    //     -> Ask quasardb for latest cluster data
    quasardbAPI.getClusterData = function(id, mock) {
        if (mock) {
            return $http({
                method: 'GET',
                url: 'mocks/cluster_status.json'
            });
        } else {
            return $http({
                method : (id ? 'JSONP' : 'GET'),
                headers: {},
                url : '../cluster_status' + (id ? '?daemon=' + id + '&callback=JSON_CALLBACK': '')
            });
        }
    };

    //     -> Ask quasardb for cluster history
    quasardbAPI.getClusterHistoricalData = function(id, mock, start, end) {
        if (mock) {
            return $http({
                method: 'GET',
                url: 'mocks/cluster_history.json'
            });
        } else {
            // Provide yesterday date
            var today = new Date();
            var yesterday = new Date();
            yesterday.setTime(today.getTime() - 24*60*60*1000);
            quasardbAPI.yesterday = yesterday;

            // Launch request
            return $http({
                method : (id ? 'JSONP' : 'GET'),
                headers: {},
                url : '../cluster_history' + (id ? '?daemon=' + id
                    + '&start=' + (regexpDateHistory.test(start)?start:yesterday.format("yyyymmddHHMM"))
                    + '&end=' + (regexpDateHistory.test(end)?end:today.format("yyyymmddHHMM"))
                    + '&callback=JSON_CALLBACK': '')
            });
        }
    };

    // ** Node services : **
    //     -> Ask quasardb for data of one node
    quasardbAPI.getNodeData = function(id, mock) {
        if (mock) {
            return $http({
                method: 'GET',
                url: 'mocks/' + (id ? id : 'node1') + '_data.json'
            });
        } else {
            return eval($http({
                method : (id ? 'JSONP' : 'JSON'),
                url : '../global_status' + (id ? '?daemon=' + id + '&callback=JSON_CALLBACK': '')
            }));
        }
    };

    //     -> Ask quasardb for one node's configuration
    quasardbAPI.getNodeConfiguration = function(id, mock) {
        if (mock) {
            return $http({
                method: 'GET',
                url: 'mocks/' + (id ? id : 'node1') + '_conf.json'
            });
        } else {
            return $http({
                method : (id ? 'JSONP' : 'JSON'),
                url : '../config' + (id ? '?daemon=' + id + '&callback=JSON_CALLBACK': '')
            });
        }
    };

    //     -> Ask quasardb for data of one node
    quasardbAPI.getNodeHistoricalData = function(id, mock, start, end) {
        if (mock) {
            return $http({
                method: 'GET',
                url: 'mocks/' + (id ? id : 'node1') + '_history.json'
            });
        } else {
            // Provide yesterday date
            var today = new Date();
            var yesterday = new Date();
            yesterday.setTime(today.getTime() - 24*60*60*1000);
            quasardbAPI.yesterday = yesterday;

            // Launch request
            return $http({
                method : (id ? 'JSONP' : 'JSON'),
                url : '../history' + (id ? '?daemon=' + id
                    + '&start=' + (regexpDateHistory.test(start)?start:yesterday.format("yyyymmddHHMM"))
                    + '&end=' + (regexpDateHistory.test(end)?end:today.format("yyyymmddHHMM"))
                    + '&callback=JSON_CALLBACK': '')
            });
        }
    };

    return quasardbAPI;
}]).

factory('dataFunctions', ['$log', '$filter', '$route', '$rootScope', 'quasardbSettings', function($log, $filter, $route, $rootScope, quasardbSettings) {
    var dataFunctions = {};

    // Remove duplicate from an array
    dataFunctions.removeDuplicates = function(a) {
        var seen = {};
        var out = [];
        var len = a.length;
        var j = 0;
        for (var i = 0; i < len; i++) {
            var item = a[i];
            if (seen[item] !== 1) {
                seen[item] = 1;
                out[j++] = item;
            }
        }
        return out;
       };

    // Return a compliant JSON from cluster or node data
    dataFunctions.buildValues = function(infos, values) {
        $log.debug('dataFunctions.buildValues(infos, values) -> ', infos, values, $rootScope);
        if (values === null) {
            return {};
        } else {
            var r_max_count = 0;
            var r_max_bytes = 0;
            var p_max_bytes = 0;

            // Check data case :
            if (infos instanceof Array) {
                //     => Historical : get the last point
                r_max_count = infos[infos.length-1].value.r_max_count;
                r_max_bytes = infos[infos.length-1].value.r_max_bytes;
                p_max_bytes = infos[infos.length-1].value.p_max_bytes;
            } else if (infos.values) {
                //     => Cluster's case : aggregate each max values
                angular.forEach(infos.values, function(value) {
                    r_max_count += value.r_max_count;
                    r_max_bytes += value.r_max_bytes;
                    p_max_bytes += value.p_max_bytes;
                });
            } else {
                //     => Node's case : read max values
                r_max_count = infos.value.r_max_count;
                r_max_bytes = infos.value.r_max_bytes;
                p_max_bytes = infos.value.p_max_bytes;
            }

            // Format output :
            return {
                //     -> Common part :
                "date" :                 d3.time.format.iso.parse(values.timestamp),
                "status" :                 ($rootScope.nodelist[$rootScope.indexCurrentNode] === undefined) ? 'online' : $rootScope.nodelist[$rootScope.indexCurrentNode].status,
                "put" :                 values.operations.put.count,
                "put_failed" :             values.operations.put.failures,
                "get" :                 values.operations.get.count,
                "get_failed" :             values.operations.get.failures,
                "update" :                 values.operations.update.count,
                "update_failed" :         values.operations.update.failures,
                "get_and_update" :             values.operations.get_and_update.count,
                "get_and_update_failed" :     values.operations.get_and_update.failures,
                "remove" :                 values.operations.remove.count,
                "remove_failed" :         values.operations.remove.failures,
                "remove_if" :             values.operations.remove_if.count,
                "remove_if_failed" :     values.operations.remove_if.failures,
                "get_and_remove" :             values.operations.get_and_remove.count,
                "get_and_remove_failed" :     values.operations.get_and_remove.failures,
                "cas" :                 values.operations.compare_and_swap.count,
                "cas_failed" :             values.operations.compare_and_swap.failures,
                "vm" :                     values.memory.vm.used,
                "mem" :                 values.memory.physmem.used,
                "disk" :                 values.disk_usage.total,
                "free" :                 values.disk_usage.free,
                "evictions" :             values.overall.evictions,
                "entries" :             values.overall.count,
                "p_size" :                 values.entries.persisted.size,
                "p_count" :             values.entries.persisted.count,
                "r_size" :                 values.entries.resident.size,
                "r_count" :             values.entries.resident.count,
                "cpu_average" :         values.cpu,
                "cpu_idle" :             values.cpu_idle,
                "ram" :                 values.memory.physmem.total,
                "r_max_count" :         r_max_count,
                "r_max_bytes" :         r_max_bytes,
                "p_max_bytes" :         p_max_bytes,
                "max_size" :             values.disk_usage.total,

                //     -> Node part :
                "node_id" :             values.node_id,
                "node_hostname" :         ($rootScope.nodelist[$rootScope.indexCurrentNode] === undefined) ? 'unknown' : $rootScope.nodelist[$rootScope.indexCurrentNode].hostname,
                "node_ip" :             (values.network !== undefined) ? (values.network.listening_address + ':' + values.network.listening_port) : '',
                "os" :                     values.operating_system,
                "build" :                 values.engine_build_date,
                "version" :             values.engine_version,
                "nbCpu" :                 values.hardware_concurrency,
                "partitions" :             (values.network !== undefined) ? values.network.partitions : '',
                "uptime" :              Date.DateDiff(new Date(values.startup), new Date(/*values.timestamp*/)),
                "transient_mode" :         ($rootScope.nodelist[$rootScope.indexCurrentNode] === undefined) ? false : $rootScope.nodelist[$rootScope.indexCurrentNode].transient_mode
            };
        }
    };

    // Build history from provided values
    dataFunctions.buildHistory = function(scope, clusterInfo, values) {
        if (values.count > 0) {
            $log.debug('dataFunctions.buildHistory(scope, clusterInfo, values) - ', scope, clusterInfo, values, scope.$root);

            // Compute new data :
            var firstCPU = 0;
            var secondCPU = 0;
            var nbCpu = 0;
            var timestamp = null;

            if (scope.tab == 2 && scope.$parent.nodeData === undefined) {
                scope.$parent.nodeData = [];
            }

            // Browse history samples :
            angular.forEach(values.results, function(result) {
                //  -> Compute cluster CPU usage
                secondCPU = firstCPU;
                firstCPU = result.cpu_times.idle;
                var cpu = (timestamp == null || nbCpu !== result.hardware_concurrency)? 0 : (100 - (Math.max(firstCPU - secondCPU, 0) / result.hardware_concurrency / (new Date(result.timestamp).getTime() - new Date(timestamp).getTime()) / 10));

                //  -> Send computed data
                timestamp = result.timestamp;
                nbCpu = result.hardware_concurrency;
                result.cpu = Math.round(cpu * 100) / 100;
                result.cpu_idle = firstCPU;

                //   -> Fetch cluster data
                if (scope.tab == 1) {
                    scope.$parent.clusterData.push({
                        key     : result.timestamp,
                        value     : dataFunctions.buildValues(clusterInfo, result)
                    });
                } else {
                    //     -> Fetch node data
                    scope.$parent.nodeData.push({
                        key     : result.timestamp,
                        value     : dataFunctions.buildValues(scope.$parent.clusterData, result)
                    });
                }
            });
        }
    };

    // Add computed data to graphs
    dataFunctions.setGraphData = function($scope, data) {
        $log.debug("dataFunctions.setGraphData($scope, data) => ", $scope, data);

        // No data => return
        if ((data === undefined) || (data.length == 0)) {
            return;
        }

        // Update scope
        $scope.charts = quasardbSettings.charts;
        $scope.ranges = quasardbSettings.ranges;

        // Compute limiters :
        var p_max_count = (data[0].value.r_max_count ? data[0].value.r_max_count : quasardbSettings.max_count);
        var r_max_count = p_max_count;
        var p_max_size = (data[0].value.p_max_bytes ? data[0].value.p_max_bytes : 0);
        //   -> max_size == 0 means no limit setted up => max_size = max disk space
        if (p_max_size == 0) {
            p_max_size = data[0].value.max_size;
        }
        var r_max_size = (data[0].value.r_max_bytes ? data[0].value.r_max_bytes : 0);
        $log.debug("dataFunctions.setGraphData() -> maxs are ", p_max_count, r_max_count, p_max_size, r_max_size, ' - initial data is ', data[0]);

        // Update scope in order to refresh graphs
        // TODO : fill preview events
        angular.forEach($scope.charts, function(chart, idxChart) {
            var newMax = [];
            angular.forEach(chart.max, function(value) {
                if (eval(value) !== undefined) {
                    newMax.push(eval(value));
                } else {
                    newMax.push(value);
                }
            });
            $scope.charts[idxChart].max = newMax;

            var newData = [];
            angular.forEach(chart.data, function(value) {
                var toEval ='var ' + value + '= [];' +
                     'angular.forEach(data, function(aData, idx) { ' +
                     '   var timestamp = d3.time.format.iso.parse(aData.key).getTime() / 1000;' +
                     '   angular.forEach(aData.value, function(value, name) {' +
                     '      if (name == "'+ value + '") { ' +
                               value + '.push({' +
                     '              x: timestamp, ' +
                     '              y: value' +
                     '         });' +
                     '      }' +
                     '    });' +
                     '}); ' +
                     'if (' + value + '.length > quasardbSettings.maxPoints) ' + value + '.shift();' +
                     '$scope.' + value + ' = ' + value + ';';
                $log.debug('dataFunctions.setGraphData() toEval => ', toEval, ' - Values => ', data);
                eval(toEval);
                newData.push(value);
            });
            $scope.charts[idxChart].data = newData;

            $log.debug('dataFunctions.setGraphData() ', chart.data, ' *** ', $scope);
        });
    };

    // Update graph data and preview graphs
    dataFunctions.updateData = function(scope, graph, spinner, computedData) {
        $log.debug('dataFunctions.updateData(scope, graph, spinner, computedData) => ', scope, graph, spinner, computedData);
        var newData = [[],[]];

        // Don't refresh already loaded period
        if (scope.histoLoaded !== undefined) {
            scope.histoLoaded[scope.selectedRange] = true;
        }

        // Compute all new data
        angular.forEach(computedData, function(data) {
            $log.debug('dataFunctions.updateData => ', data);
            newData[0].push({
                x : d3.time.format.iso.parse(data.key).getTime() / 1000,
                y : eval('data.value.' + scope.data[0])
            });
            if (scope.data.length > 1) {
                newData[1].push({
                    x : d3.time.format.iso.parse(data.key).getTime() / 1000,
                    y : eval('data.value.' + scope.data[1])
                });
            }
        });
        $log.debug('dataFunctions.updateData => newData = ', newData);

        // Refresh graphs and preview graphs
        if (graph !== null) {
            angular.forEach(graph.series, function(serie, idx) {
                serie.data = newData[idx].sort(function(a,b) {
                    var c = new Date(a.x);
                    var d = new Date(b.x);
                    return c-d;
                  });
            });
            graph.window.xMin = graph.dataDomain()[0];
            graph.window.xMax = graph.dataDomain()[1];

            $log.debug('dataFunctions.updateData(scope, graph, spinner, computedData) => ', scope, graph, computedData, ' - domains => ', graph.dataDomain(), graph.window.xMin, graph.window.xMax);

            if (spinner !== null) {
                spinner.stop();
            }
            graph.update();
        }
    };

    return dataFunctions;
}]).

factory('shortcuts', ['$log', '$route', '$rootScope', 'quasardbSettings', 'quasardbAPIService', 'dataFunctions', function($log, $route, $rootScope, quasardbSettings, quasardbAPIService, dataFunctions) {
    var nodesLoaded = new HashMap();
    return {
        // Reset nodes list
        resetNodesLoaded : function() {
            nodesLoaded.clear();
        },

        // function dynamically called when browsing specific historical periods
        //     => See app.js for configuration
        setRange : function(scope, graph, period) {
            var activeTab = scope.$parent.tab;
            $log.debug('shortcuts.setRange(scope, graph, period) => ', scope, graph, period, ' - Tab => ', activeTab);
            if (graph !== null) {
                var startDate = new Date();
                if (period === "h") {
                    startDate.setHours(startDate.getHours() - 4);
                } else {
                    startDate.setDate(startDate.getDate() - 2);
                }
                var data = (activeTab == 1) ? scope.$parent.clusterData : scope.$parent.nodeData;
                data = data.filter(function(element) {
                    return (new Date(element.key)).getTime() > startDate.getTime();
                });
                $log.debug('shortcuts.setRange(scope, graph, period) => ', (activeTab == 1) ? scope.$parent.clusterData : [], ' - filtered data : ', data);
                dataFunctions.updateData(scope, graph, null, data);
            }
        },

        // function dynamically called when browsing specific historical periods
        //     => See app.js for configuration
        getHistoric : function(scope, graph, start, end) {
            $log.debug('shortcuts Service : getHistoric(scope, start, end) => ', scope, start, end);
            var activeTab = scope.$parent.tab;
            var spinner = new Spinner(quasardbSettings.loaderOptions).spin(graph.element);

            // Check if data aren't already loaded
               if (scope.$root.clusterHistoricLoaded === undefined) {
                scope.$root.clusterHistoricLoaded = [];
                angular.forEach(scope.ranges, function() {
                    scope.$root.clusterHistoricLoaded.push(false);
                });
            }
            $log.debug('shortcuts Service : getHistoric() : histoLoaded => ', scope.$root.clusterHistoricLoaded, nodesLoaded.get(scope.$root.nodelist[scope.$root.indexCurrentNode].ip));

            // Don't get already loaded period
            if (((scope.$root.clusterHistoricLoaded[scope.selectedRange] === true) && (activeTab == 1)) || (nodesLoaded.get(scope.$root.nodelist[scope.$root.indexCurrentNode].ip) !== undefined)) {
                var data = (activeTab == 1) ? scope.$parent.clusterData : scope.$parent.nodeData;
                data = data.filter(function(element) {
                    return (new Date(element.key)).getTime() > (new Date(("" + start).substring(0,4), ("" + start).substring(4,6) - 1, ("" + start).substring(6,8), ("" + start).substring(8,10), ("" + start).substring(10,12), 59)).getTime();
                });
                $log.debug('shortcuts.getHistoric(scope, start, end) ', scope, start, end, ' => ', data);
                dataFunctions.updateData(scope, graph, spinner, data);
            } else {
                var computedEnd = quasardbAPIService.yesterday.format("yyyymmddHHMM");
                ((activeTab == 1) ? quasardbAPIService.getClusterHistoricalData(scope.$root.nodelist[0].ip, $route.current.params.mock, start, computedEnd) : quasardbAPIService.getNodeHistoricalData(scope.$root.nodelist[scope.$root.indexCurrentNode].ip, $route.current.params.mock, start, computedEnd)).
                    //  -> cluster historic retrieved
                    success(function (response) {
                        dataFunctions.buildHistory(scope.$parent, (activeTab == 1) ? scope.$parent.clusterData : scope.$parent.nodeData, response);
                        var data = (activeTab == 1) ? scope.$parent.clusterData : scope.$parent.nodeData;
                        dataFunctions.updateData(scope, graph, spinner, data);
                        (activeTab == 1) ? scope.$root.clusterHistoricLoaded[scope.selectedRange] = true : nodesLoaded.add(scope.$root.nodelist[scope.$root.indexCurrentNode].ip, 'OK');
                    }).

                    //  -> cluster historic KO
                    error(function (data, status, headers, config) {
                        $log.info('clusterViewController : getCluster(id, mock) - getClusterHistoricalData(node, mock, start, end) ', (activeTab == 1) ? scope.$root.nodelist[0].ip : scope.$root.nodelist[scope.$root.indexCurrentNode].ip, $route.current.params.mock, 'null, null => ERROR - ', data, status, headers, config);
                        if (spinner !== null) {
                            spinner.stop();
                        }
                    }
                );
            }
        }
       };
}]);
