if (!Array.prototype.front) {
    Array.prototype.front = function () {
        return this[0];
    };
};

if (!Array.prototype.back) {
    Array.prototype.back = function () {
        return this[this.length - 1];
    };
};

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

    /* Cluster controller: fetch cluster data */
    .controller('clusterViewController', ['$scope', '$rootScope', '$route', '$log', '$interval', 'quasardbSettings', 'quasardbAPIService', 'dataFunctions',
        function ($scope, $rootScope, $route, $log, $interval, quasardbSettings, quasardbAPIService, dataFunctions) {
            var clusterInfo = new HashMap();
            $rootScope.nodelist = [];
            var historyLoaded = false;
            var status;

            // Build a NodeInfoValue object
            function buildNodeInfoValue(info) {
                $log.debug('clusterViewController : buildNodeInfoValue(info)', info);

                var default_info = {
                    local: {
                        hostname: "unknown",
                        depot: {
                            max_bytes: 0
                        },
                        limiter: {
                            max_bytes: 0,
                            max_resident_entries: quasardbSettings.max_count
                        }
                    },
                    global: {
                        cluster: {
                            transient: false
                        }
                    }
                };

                // Use default values if something is missing in info.
                var combined_info = extend(default_info, info);

                return {
                    "node_hostname": combined_info.hostname,
                    "r_max_count": combined_info.local.limiter.max_resident_entries,
                    "r_max_bytes": info.local.limiter.max_bytes,
                    "p_max_bytes": info.local.depot.max_bytes,
                    "transient_mode": info.global.cluster.transient
                };
            }

            // Empty cluster
            function emptyCluster() {
                status = 'instable';
                if ($rootScope.nodelist.length === 0) {
                    $rootScope.nodesStatus = 'no node found !';
                } else {
                    // Some nodes in ring => update status
                    $rootScope.nodesStatus = 'cluster is offline !';
                    angular.forEach($rootScope.nodelist, function (node) {
                        node.status = 'offline';
                        node.icon = 'close';
                    });
                }
            }

            // Get all data from cluster
            function getCluster(id, mock) {
                $log.debug('clusterViewController : getCluster(id, mock) => ', id, mock, historyLoaded);

                // Retrieve cluster topology :
                quasardbAPIService.getClusterTopology(id, mock)
                    //  -> request successfull : there are some nodes
                    .success(function (responseClusterTopology) {
                        if ((responseClusterTopology.nodes !== undefined) && (responseClusterTopology.nodes.length > 0)) {
                            $log.debug('clusterViewController : getCluster(id, mock) - getNodeTopology(id, mock) ', id, mock, ' => ', responseClusterTopology);

                            if ($rootScope.nodelist.length != responseClusterTopology.nodes.length) {
                                $rootScope.nodelist = [];
                            }

                            angular.forEach(responseClusterTopology.nodes, function (node, idxNode) {
                                // Store nodes informations
                                var thisNodeInfo = clusterInfo.get(node.endpoint)
                                if (thisNodeInfo === undefined) {
                                    // Ask node configuration in order to get max number of objects
                                    quasardbAPIService.getNodeConfiguration(node.endpoint, $route.current.params.mock)
                                        //  -> node configuration retrieved
                                        .success(function (responseNodeConfiguration) {
                                            $log.debug('clusterViewController -> getCluster(id, mock) - getNodeConfiguration(node, mock) ', node.endpoint, $route.current.params.mock, ' => ', responseNodeConfiguration, $rootScope);
                                            responseNodeConfiguration.hostname = node.hostname;
                                            thisNodeInfo = buildNodeInfoValue(responseNodeConfiguration)
                                            clusterInfo.add(node.endpoint, thisNodeInfo);

                                            // Build cluster ring
                                            if ($rootScope.nodelist.length != responseClusterTopology.nodes.length) {
                                                $rootScope.nodelist.push({
                                                    "id": node.reference,
                                                    "status": "online",
                                                    "ip": node.endpoint,
                                                    "hostname": node.hostname,
                                                    "degree": (360 * parseInt(node.reference, 16) / 0xFFFFFFFFFFFFFFFF),
                                                    "showTooltip": false,
                                                    "icon": 'data',
                                                    "transient_mode": thisNodeInfo.transient_mode
                                                });
                                            }

                                            // Add first nodeId to rootscope (for default node view tab)
                                            if (idxNode == 0) {
                                                $rootScope.nodeId = node.reference;
                                                $rootScope.nodeIp = node.endpoint;
                                            }

                                            // Setup cluster's status and cluster's nb nodes
                                            $rootScope.status = 'stable';
                                            $rootScope.nodesStatus = $rootScope.nodelist.length + ' / ' + responseClusterTopology.nodes.length;
                                        })

                                        //  -> node configuration KO
                                        .error(function (data, status, headers, config) {
                                            $log.info('clusterViewController : getCluster(id, mock) - getNodeConfiguration(node, mock) ', node.endpoint, $route.current.params.mock, ' => ERROR - ', data, status, headers, config);
                                            clusterInfo.add(node.endpoint, buildNodeInfoValue(null));
                                        });
                                } else {
                                    // Cluster becomes stable
                                    if (status === 'instable') {
                                        angular.forEach($rootScope.nodelist, function (node) {
                                            node.status = 'online';
                                            node.icon = 'data';
                                        });
                                        status = 'stable';
                                    }
                                }
                            });

                            // Ask cluster for historical data -> 1 day ago
                            if ((historyLoaded !== true) && ($scope.tab == 1)) {
                                $scope.$parent.clusterData = [];
                                quasardbAPIService.getClusterHistoricalData(responseClusterTopology.nodes.front().endpoint, $route.current.params.mock, null, null)
                                    //  -> cluster historic retrieved
                                    .success(function (responseClusterHistoricalData) {
                                        dataFunctions.buildHistory($scope, clusterInfo, responseClusterHistoricalData);
                                        if (responseClusterHistoricalData.count != 0) {
                                            $scope.$root.$broadcast('cluster_data_ready');
                                        } else {
                                            $scope.refresh();
                                        }
                                        historyLoaded = true;
                                    })

                                    //  -> cluster historic KO
                                    .error(function (data, status, headers, config) {
                                        $log.info('clusterViewController : getCluster(id, mock) - getClusterHistoricalData(node, mock, start, end) ', responseClusterTopology.nodes.front().endpoint, $route.current.params.mock, 'null, null => ERROR - ', data, status, headers, config);
                                        // TODO handle error
                                    });
                            } else {
                                // Cluster history already loaded => ask for latest cluster sample
                                $log.debug('clusterViewController : getCluster(id, mock) => history already loaded : ', $scope.$parent.clusterData);
                                (($scope.tab == 1)
                                    ? quasardbAPIService.getClusterData(responseClusterTopology.nodes.front().endpoint, $route.current.params.mock)
                                    : quasardbAPIService.getNodeData(responseClusterTopology.nodes[$scope.$root.indexCurrentNode].endpoint, $route.current.params.mock))
                                    //  -> latest cluster data retrieved
                                    .success(function (responseData) {
                                        var data = ($scope.tab == 1) ? $scope.$parent.clusterData : $scope.$parent.nodeData;
                                        if ((data !== undefined) && (responseData.cpu_times !== undefined)) {
                                            if ($route.current.params.mock) {
                                                responseData.timestamp = new Date();
                                                responseData.cpu = Math.round(Math.floor(Math.random() * 101) * 100) / 100;
                                            } else {
                                                // CPU
                                                responseData.cpu = (data.back() === undefined)
                                                    ? 0
                                                    : 100 - (Math.max(responseData.cpu_times.idle - data.back().value.cpu_idle, 0) / responseData.hardware_concurrency / quasardbSettings.refresh / 10);
                                                responseData.cpu = (responseData.cpu < 0) ? data.back().value.cpu_average : Math.round(responseData.cpu * 100) / 100;
                                                responseData.cpu_idle = responseData.cpu_times.idle;
                                            }
                                            data.push({
                                                key: responseData.timestamp,
                                                value: dataFunctions.buildValues(clusterInfo, responseData)
                                            });
                                            $rootScope.$broadcast(($scope.tab == 1) ? 'cluster_data_ready' : (($scope.$root.usingCarousel == false) ? 'node_changed' : 'refresh_node'));
                                        }
                                    })

                                    //  -> latest cluster data KO
                                    .error(function (data, status, headers, config) {
                                        $log.info('clusterViewController : getCluster(id, mock) - getClusterData(node, mock) ', ($scope.tab == 1) ? responseClusterTopology.nodes.front().endpoint : responseClusterTopology.nodes[$scope.$root.indexCurrentNode].endpoint, $route.current.params.mock, ' => ERROR - ', data, status, headers, config);
                                        // TODO handle error
                                    });
                            }
                        } else {
                            emptyCluster();
                        }
                    })

                    //  -> request failed : no nodes
                    .error(function (data, status, headers, config) {
                        $log.info('clusterViewController : getCluster(id, mock) - getNodeTopology(id, mock) ', id, mock, ' => ERROR - ', data, status, headers, config);
                        emptyCluster();

                        // Stop searching animation => see directive.js
                        if ($scope.$root != null) {
                            $scope.$root.status = 'offline';
                        }
                    });
            }

            // Hide or display node tooltip
            $scope.hover = function (node) {
                $log.debug('clusterViewController : node changed => ', node);
                return node.showTooltip = !node.showTooltip;
            };

            // Show node view for a specific node
            $scope.showNode = function (nodeId) {
                $log.debug('clusterViewController : showNode(nodeId) => ', nodeId, $scope, $rootScope);
                $rootScope.nodelist.forEach(function (value, index) {
                    $log.debug('clusterViewController -> showNode(nodeId) -> $rootScope.nodelist.forEach', nodeId, value, index);
                    if (value.id === nodeId) {
                        $rootScope.indexCurrentNode = index;
                    }
                });
                $rootScope.nodeId = nodeId;
                $scope.$parent.tab = 2;
                $scope.$parent.myVar2 = 'b-tabs__item_state_current';
                $scope.$parent.myVar1 = '';
            };

            // Handle refresh request (windows close events and refresh button)
            $scope.refresh = function () {
                $log.debug('clusterViewController : refresh() => ', $scope, $rootScope);
                getCluster(null, $route.current.params.mock);
            };

            // Get cluster data (cluster topology, nodes configuration, cluster history)
            getCluster(null, $route.current.params.mock);

            // Refresh data at regular interval
            $interval($scope.refresh, quasardbSettings.refresh);
        }
    ])

    /* Cluster graphs controller */
    .controller('clusterChartsController', ['$scope', '$log', 'quasardbSettings', 'dataFunctions', 'shortcuts', function ($scope, $log, quasardbSettings, dataFunctions, shortcuts) {
        // Refresh cluster data
        function refreshClusterData(scope) {
            if ((scope.clusterData !== undefined) && (scope.tab == 1)) {
                dataFunctions.setGraphData(scope, scope.clusterData);
            }
        }

        // Refresh GUI on new data
        $scope.$on('cluster_data_ready', function () {
            $log.debug('clusterChartsController : $scope.$on(cluster_data_ready) => ', $scope.clusterData);
            refreshClusterData($scope);
        });

        // Detect active tab
        $scope.$watch('tab', function () {
            $log.debug('clusterChartsController : $watch(tab) => ', $scope.tab, $scope);
            refreshClusterData($scope);
            if ($scope.tab == 1) {
                $scope.$root.usingCarousel = false;
                $scope.$parent.nodeData = undefined;
                shortcuts.resetNodesLoaded();
            }
        });

        // Initialization part
        $log.debug('clusterChartsController : init => ', $scope);
    }])

    /* Nodes controller (handle node view) */
    .controller('nodesController', ['$log', '$route', '$scope', 'quasardbAPIService', 'dataFunctions', function ($log, $route, $scope, quasardbAPIService, dataFunctions) {
        $log.debug('nodesController.init($scope)', $scope);
        var loaded = false;

        // Refresh node view with incoming data
        refreshNodeView = function () {
            $log.debug('nodesController - refreshNodeView() => ', $scope);

            var currentNode = $scope.$root.nodelist[$scope.$root.indexCurrentNode];
            // Refresh scope
            $scope.node = currentNode;

            // Get selected node data
            (($scope.$parent.nodeData === undefined)
                ? quasardbAPIService.getNodeHistoricalData($scope.node.ip, $route.current.params.mock, null, null)
                : quasardbAPIService.getNodeData($scope.node.ip, $route.current.params.mock, null, null))
                //  -> latest cluster data retrieved
                .success(function (response) {
                    if ($scope.$parent.nodeData === undefined) {
                        if (response.count > 0) {
                            // Build history
                            dataFunctions.buildHistory($scope, null, response);

                            // Refresh graphs
                            dataFunctions.setGraphData($scope, $scope.$parent.nodeData);

                            // There are historic data
                            quasardbAPIService.getNodeData($scope.node.ip, $route.current.params.mock)
                                .success(function (responseData) {
                                    responseData.cpu = 0.5;

                                    responseData.cpu_idle = responseData.cpu_times.idle;
                                    $scope.$parent.nodeData.push({
                                        key: responseData.timestamp,
                                        value: dataFunctions.buildValues($scope.$parent.clusterData.front(), responseData)
                                    });
                                    dataFunctions.setGraphData($scope, $scope.$parent.nodeData);
                                })

                                .error(function () {
                                    // TODO
                                    $scope.$parent.nodeData = [];
                                    refreshNodeView();
                                });
                        } else {
                            // No history yet or transient mode activated => get current data
                            $scope.$parent.nodeData = [];
                            refreshNodeView();
                        }
                    } else {
                        // Fresh data are fetched => refresh some metrics
                        var total = response.cpu_times.system + response.cpu_times.user + response.cpu_times.idle;
                        var occupied = /*response.cpu_times.system +*/ response.cpu_times.user;
                        var idle = response.cpu_times.idle;
                        response.cpu = occupied / total * 100;
                        response.cpu_idle = idle / total * 100;
                        $scope.$parent.nodeData.push({
                            key: response.timestamp,
                            value: dataFunctions.buildValues($scope.$parent.clusterData.front(), response)
                        });
                        dataFunctions.setGraphData($scope, $scope.$parent.nodeData);
                    }

                    // Refresh nodelist
                    var currentNode = $scope.$root.nodelist[$scope.$root.indexCurrentNode];
                    currentNode.build = ($scope.$parent.nodeData !== undefined) && ($scope.$parent.nodeData.back() !== undefined) ? $scope.$parent.nodeData.back().value.build : response.engine_build_date;
                    currentNode.version = ($scope.$parent.nodeData !== undefined) && ($scope.$parent.nodeData.back() !== undefined) ? $scope.$parent.nodeData.back().value.version : response.engine_version;
                    loaded = true;
                    $scope.$root.$broadcast('node_changed');
                })

                //  -> latest cluster data KO
                .error(function (data, status, headers, config) {
                    $log.info('clusterViewController : refreshNodeView() - getNodeHistoricalData(node, mock, start, end) ', $scope.node.ip, $route.current.params.mock, 'null, null => ERROR - ', data, status, headers, config);
                    // TODO handle error
                });
        };

        // Listen the following event : new view is selected
        $scope.$watch('tab', function () {
            if ($scope.tab == 2) {
                $log.debug('nodesController.$watch(tab) => ', $scope);
                $scope.$root.usingCarousel = true;
                refreshNodeView();
            }
        });

        // Listen the following event : node view has changed
        $scope.$on('node_changed', function () {
            if ($scope.tab == 2) {
                $log.debug('nodeSessionsController => node_changed event => ', $scope);
                if (!loaded) {
                    refreshNodeView();
                } else {
                    loaded = false;
                }
            }
        });

        // TODO
        $scope.$on('refresh_node', function () {
            if ($scope.tab == 2) {
                dataFunctions.setGraphData($scope, $scope.$parent.nodeData);
            }
        });
    }])

    /* Node Sessions controller */
    .controller('nodeSessionsController', ['$scope', '$log', function ($scope, $log) {
        $scope.maxSessions = 2000;
        $scope.nodePartitions = [];

        // Refresh node sessions
        refreshNodeSessions = function () {
            var partitions = [];
            $log.debug('nodeSessionsController => buildSessions() ', $scope.$parent.$parent.nodeData);
            if (($scope.$parent.$parent.nodeData !== undefined) && ($scope.$parent.nodeData.back() !== undefined)) {
                $scope.maxSessions = $scope.$parent.nodeData.back().value.partitions.max_sessions;
                for (var i = 0; i < $scope.$parent.nodeData.back().value.partitions.count; i++) {
                    partitions.push({
                        id: i,
                        value: $scope.$parent.$parent.nodeData.back().value.partitions.available_sessions[i]
                    });
                }
            }
            $scope.nodePartitions = partitions;

            // If no data available
            if ($scope.nodePartitions.length === 0) {
                $scope.nodePartitions = [{
                    id: "data available yet",
                    value: "0"
                }];
            }
        };

        // Listen the following event : new tab is selected
        $scope.$watch('tab', function () {
            if ($scope.tab == 2) {
                $log.debug('nodeSessionsController => $watch(tab) => ', $scope);
                refreshNodeSessions();
            }
        });

        // Listen the following event : node view has changed
        $scope.$on('node_changed', function () {
            if ($scope.tab == 2) {
                $log.debug('nodeSessionsController => node_changed event => ', $scope);
                refreshNodeSessions();
            }
        });
    }])

    /* Node carousel controller */
    .controller('nodeCarouselController', ['$scope', '$log', function ($scope, $log) {
        $log.debug('nodeCarouselController.init($rootScope, $scope) => ', $scope.$root, $scope.$root.nodeId);
        $scope.$root.indexCurrentNode = 0;
        $scope.$root.usingCarousel = false;

        // Change quasardb build and quasardb version
        var initController = function () {
            var currentNode = $scope.$root.nodelist[$scope.$root.indexCurrentNode];
            $log.debug('nodeCarouselController.initController() => ', $scope.$root, currentNode);
            if (currentNode !== undefined) {
                $scope.nodeBuild = currentNode.build;
                $scope.nodeVersion = currentNode.version;
            }
        };

        // Listen the following event : new tab is selected
        $scope.$watch('tab', function () {
            if ($scope.$parent.$parent.tab == 2) {
                initController();
            }
        });

        // Listen the following event : node view has changed
        $scope.$on('node_changed', function () {
            if ($scope.$parent.$parent.tab == 2) {
                initController();
            }
        });

        // Browse previous node available node on the cluster ring
        $scope.showPreviousNode = function () {
            $log.debug('nodeCarouselController : showPreviousNode() => ', $scope);
            var currentNode = $scope.$root.nodelist[$scope.$root.indexCurrentNode];
            if (currentNode === undefined) {
                return;
            }

            // Erase node data
            $scope.$parent.$parent.nodeData = undefined;

            // Update scope
            $scope.$root.indexCurrentNode = ($scope.$root.indexCurrentNode == 0)
                ? ($scope.$root.nodelist.length - 1)
                : (Math.abs($scope.$root.indexCurrentNode - 1) % $scope.$root.nodelist.length);
            $scope.nodeBuild = currentNode.build;
            $scope.nodeVersion = currentNode.version;
            $scope.$root.node = currentNode;
            $scope.$root.nodeId = currentNode.id;

            // Notify that new data are ready to be displayed
            $scope.$root.usingCarousel = true;
            $scope.$emit('node_changed');
        };

        // Browse next node available node on the cluster ring
        $scope.showNextNode = function () {
            $log.debug('nodeCarouselController : showNextNode() => ', $scope);
            var currentNode = $scope.$root.nodelist[$scope.$root.indexCurrentNode];
            if (currentNode === undefined) {
                return;
            }

            // Erase node data
            $scope.$parent.$parent.nodeData = undefined;

            // Update scope
            $scope.$root.indexCurrentNode = Math.abs(($scope.$root.indexCurrentNode + 1) % $scope.$root.nodelist.length);
            $scope.nodeBuild = currentNode.build;
            $scope.nodeVersion = currentNode.version;
            $scope.$root.node = currentNode;
            $scope.$root.nodeId = currentNode.id;

            // Notify that new data are ready to be displayed
            $scope.$root.usingCarousel = true;
            $scope.$emit('node_changed');
        };
    }])

    /* Node download controller */
    .controller('nodeDownloadController', ['$rootScope', '$scope', '$log', '$route', '$http', function ($rootScope, $scope, $log, $route, $http) {
        $scope.filename = '';

        // Download a JSON file containing live data for the current node.
        $scope.getRawNodeData = function () {
            $log.debug('nodeDownloadController.getRawNodeData() => ', $rootScope, $scope, $route.current.params.mock);

            // Set filename
            $scope.filename = 'data_' + $rootScope.nodeIp + '_' + (new Date()).format('yyyymmddHHMM') + '.json';

            // Compute the right url
            var url = '../global_status' + ($rootScope.nodeIp ? '?daemon=' + $rootScope.nodeIp + '&callback=handleJSONP' : '');
            if ($route.current.params.mock) {
                url = 'mocks/' + ($rootScope.nodeIp ? $rootScope.nodeIp : 'node1') + '_data.json';
            }

            // Get data with url
            $http.get(url).then(function (response) {
                $scope.$emit('downloaded', response.data);
            });
        };

        // Download a JSON file containing configuration data for the current node.
        $scope.getRawNodeConf = function () {
            $log.debug('nodeDownloadController.getRawNodeConf() => ', $rootScope, $scope, $route.current.params.mock);

            // Set filename
            $scope.filename = 'config_' + $rootScope.nodeIp + '.json';

            // Compute the right url
            var url = '../config' + ($rootScope.nodeIp ? '?daemon=' + $rootScope.nodeIp + '&callback=handleJSONP' : '');
            if ($route.current.params.mock) {
                url = 'mocks/' + ($rootScope.nodeIp ? $rootScope.nodeIp : 'node1') + '_conf.json';
            }

            // Get data with url
            $http.get(url).then(function (response) {
                $scope.$emit('downloaded', response.data);
            });
        };

        // When the download finishes, attach the data to a link balise.
        $scope.$on('downloaded', function (event, data) {
            var output = new Blob([JSON.stringify(data, null, '\t')], { type: "text/plain;charset=utf-8" });
            if (!$route.current.params.mock) {
                output = eval(data);
            }
            saveAs(output, $scope.filename);
        });

        // Callback function to call
        handleJSONP = function (data) {
            return new Blob([JSON.stringify(data, null, '\t')], { type: "text/plain;charset=utf-8" });
        };
    }]);
