/*
 * Copyright 2016 FUJITSU LIMITED
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

'use strict';

angular
    .module('monitoring.directives', [
        'horizon.framework.widgets',
        'monitoring.filters',
        'monitoring.services',
        'gettext'
    ])
    .directive('monAlarmExpression',
        ['monitoringApp.staticPath', monAlarmExpressionsDirective]
    )
    .directive('monAlarmSubExpression',
        ['monitoringApp.staticPath', monAlarmSubExpressionDirective]
    );

function monAlarmExpressionsDirective(staticPath){

    return {
        restrict: 'E',
        scope: {
            metrics: '=metrics',
            functions: '=functions',
            operators: '=operators',
            comparators: '=comparators',
            connectable: '=connectable'
        },
        templateUrl: staticPath + 'expression/expression.tpl.html',
        controller: ['$q', '$scope', 'monExpressionBuilder', AlarmExpressionController],
        controllerAs: 'vm',
        bindToController: true
    };

    function AlarmExpressionController($q, $scope, monExpressionBuilder) {
        // private
        var vm = this,
            deterministic = false,
            matchBy = undefined;

        // scope
        vm.expression = '';
        vm.subExpressions = undefined;
        vm.expressionValid = true;

        // api
        vm.touch = touch;
        vm.addExpression = addExpression;
        vm.removeExpression = removeExpression;
        vm.reorderExpression = reorderExpression;

        // listen
        $scope.$on('$destroy', destroy);

        // init
        $scope.$applyAsync(init);

        function addExpression($event, $index) {
            if ($event) {
                $event.preventDefault();
            }

            vm.subExpressions.splice($index, 0, {});
            if ($index >= 1 && vm.subExpressions[$index - 1].$valid) {
                // hide previous expression
                // if it is valid
                vm.subExpressions[$index - 1]['preview'] = true;
            }

            applyConnectable();

            return true;
        }

        function removeExpression($event, index) {
            if ($event) {
                $event.preventDefault();
            }
            vm.subExpressions.splice(index, 1);

            applyConnectable();
            touch();

            if (vm.subExpressions.length === 0) {
                vm.expression = '';
                vm.subExpressions.push({});
            }

            return true;
        }

        function reorderExpression($event, which, where) {
            $event.preventDefault();
            vm.subExpressions[where]['op'] = [
                vm.subExpressions[which]['op'],
                vm.subExpressions[which]['op'] = vm.subExpressions[where]['op']
            ][0];
            vm.subExpressions[where] = vm.subExpressions.splice(which, 1, vm.subExpressions[where])[0];

            applyConnectable();

            return true;
        }

        function touch() {
            var hasInvalid = false;
            var expression = undefined;

            matchBy = [];
            deterministic = true;

            angular.forEach(vm.subExpressions, subExpressionIt);

            if (hasInvalid) {
                expression = undefined;
            } else {
                $scope.$emit('mon_match_by_changed', matchBy.sort());
                $scope.$emit('mon_deterministic_changed', deterministic);

                expression = monExpressionBuilder.asString(vm.subExpressions, true);

                // change preview only if valid
                vm.expression = expression;
            }

            // update that always, regardless if it's valid or not
            // for invalid case that will reset input's value to empty
            // disallowing form to be accepted by django
            $('#expression').val(expression);
            vm.expressionValid = !hasInvalid;

            return true;

            function subExpressionIt(expr) {
                if (!expr.$valid) {
                    return !(hasInvalid = true);
                }

                angular.forEach(expr.matchBy || [], function it(mb) {
                    if(matchBy.indexOf(mb) < 0) {
                        matchBy.push(mb);
                    }
                });
                deterministic = deterministic && (expr.deterministic || false);

                return true;
            }
        }

        function init() {
            if(vm.metrics.length) {
                vm.subExpressions = [];
                vm.matchBy = [];

                addExpression(undefined, 0);
            }
        }

        function destroy() {
            delete vm.metrics;
            delete vm.expression;
            delete vm.subExpressions;
            delete vm.deterministic;
        }

        function applyConnectable() {
            var count = vm.subExpressions.length;

            switch(count) {
                case 1:
                    vm.subExpressions[0]['connectable'] = false;
                    break;
                default: {
                    angular.forEach(vm.subExpressions, function(expr, index) {
                        expr.connectable = index >= 1 && index < vm.subExpressions.length;
                        if (!expr.connectable) {
                            delete expr['op'];
                        }
                    });
                }
            }

        }
    }

}

function monAlarmSubExpressionDirective(staticPath) {
    return {
        restrict: 'E',
        require: '^monAlarmExpression',
        scope: {
            metrics: '=metrics',
            functions: '=functions',
            comparators: '=comparators',
            operators: '=operators',
            model: '=subExpression',
            connectable: '=connectable',
            preview: '=preview'
        },
        templateUrl: staticPath + 'expression/sub-expression.tpl.html',
        link: linkFn,
        controller: ['$q', '$scope', 'monExpressionBuilder', AlarmSubExpressionController],
        controllerAs: 'vm',
        bindToController: true
    };

    function linkFn(scope, el, attrs, monAlarmExpressions) {
        el.on('$destroy', (function(){

            var watcher = scope.$watch('vm.model', function(expr, oldExpr) {
                if (expr !== oldExpr) {
                    monAlarmExpressions.touch();
                }
            }, true);

            return function destroyer() {
                watcher();
            };

        }()));
    }

    function AlarmSubExpressionController($q, $scope, monExpressionBuilder) {
        var vm = this;

        vm.tags = [];
        vm.matchingMetrics = [];

        // api
        vm.possibleDimensions = possibleDimensions;
        vm.onMetricChanged = onMetricChanged;
        vm.onDimensionsUpdated = onDimensionsUpdated;

        vm.updateExpression = updateExpression;
        vm.resetExpression = resetExpression;

        // init
        $scope.$on('$destroy', destroyerFactory());

        function opRemoverListener(nval) {
            if (vm.model && 'op' in vm.model && !nval) {
                delete vm.model['op'];
            }
        }

        function destroyerFactory() {

            var watcher = $scope.$watch('vm.model.connectable', opRemoverListener, true);

            return function destroyer() {
                watcher();

                delete vm.tags;
                delete vm.matchingMetrics;
                delete vm.model;
            };

        }

        function updateExpression() {
            var dim = [],
                formController = $scope.$$childHead.subExpressionForm;

            vm.model.$valid = !formController.$invalid;

            if (vm.model.$valid) {

                if (vm.tags.length > 0) {
                    angular.forEach(vm.tags, function(value, key) {
                        dim.push(value['text']);
                    });
                    vm.model.dimensions = dim;
                } else {
                    vm.model.dimensions = [];
                }

            }

        }

        function resetExpression() {
            vm.matchingMetrics = [];
            vm.tags = [];
        }

        function possibleDimensions(query) {
            return $q(function(resolve) {
                var dim = {},
                    dimList = [];

                angular.forEach(vm.matchingMetrics, function(value, name) {
                    for (var key in value.dimensions) {
                        if (value.dimensions.hasOwnProperty(key)) {
                            var dimStr = key + "=" + value.dimensions[key];
                            if (dimStr.indexOf(query) === 0) {
                                dim[dimStr] = dimStr;
                            }
                        }
                    }
                });

                angular.forEach(dim, function(value, name) {
                    dimList.push(value);
                });

                resolve(dimList);
            });
        }

        function onDimensionsUpdated() {
            onMetricChanged(vm.model.metric);
        }

        function onMetricChanged(metric) {
            handleMetricChanged(metric);
            updateExpression();
        }

        function handleMetricChanged(metric) {
            var mm = [],
                matchBy = [],
                tags = vm.tags || [];

            angular.forEach(vm.metrics, function(value, key) {
                if (value.name === metric.name) {
                    var match = true;
                    for (var i = 0; i < tags.length; i++) {
                        var vals = tags[i]['text'].split('=');
                        if (value.dimensions[vals[0]] !== vals[1]) {
                            match = false;
                            break;
                        }

                    }
                    if (match) {
                        mm.push(value);
                    }
                }
            });

            angular.forEach(mm, function(value, key){
                angular.forEach(value.dimensions, function(value, key){
                    if(matchBy.indexOf(key) < 0){
                        matchBy.push(key);
                    }
                });
            });

            vm.matchingMetrics = mm;
            vm.model.matchBy = matchBy.sort();

        }

    }

}
