/**
 * @class Invert
 * @memberof module:plugins
 * @description Allows to invert a rule operator, a group condition or the entire builder.
 * @param {object} [options]
 * @param {string} [options.icon='glyphicon glyphicon-random']
 * @param {boolean} [options.recursive=true]
 * @param {boolean} [options.invert_rules=true]
 * @param {boolean} [options.display_rules_button=false]
 * @param {boolean} [options.silent_fail=false]
 */
QueryBuilder.define('invert', function(options) {
    var self = this;
    var Selectors = QueryBuilder.selectors;

    // Bind events
    this.on('afterInit', function() {
        self.$el.on('click.queryBuilder', '[data-invert=group]', function() {
            var $group = $(this).closest(Selectors.group_container);
            self.invert(self.getModel($group), options);
        });

        if (options.display_rules_button && options.invert_rules) {
            self.$el.on('click.queryBuilder', '[data-invert=rule]', function() {
                var $rule = $(this).closest(Selectors.rule_container);
                self.invert(self.getModel($rule), options);
            });
        }
    });

    // Modify templates
    if (!options.disable_template) {
        this.on('getGroupTemplate.filter', function(h) {
            var $h = $(h.value);
            $h.find(Selectors.condition_container).after(
                '<button type="button" class="btn btn-xs btn-default" data-invert="group">' +
                '<i class="' + options.icon + '"></i> ' + self.translate('invert') +
                '</button>'
            );
            h.value = $h.prop('outerHTML');
        });

        if (options.display_rules_button && options.invert_rules) {
            this.on('getRuleTemplate.filter', function(h) {
                var $h = $(h.value);
                $h.find(Selectors.rule_actions).prepend(
                    '<button type="button" class="btn btn-xs btn-default" data-invert="rule">' +
                    '<i class="' + options.icon + '"></i> ' + self.translate('invert') +
                    '</button>'
                );
                h.value = $h.prop('outerHTML');
            });
        }
    }
}, {
    icon: 'glyphicon glyphicon-random',
    recursive: true,
    invert_rules: true,
    display_rules_button: false,
    silent_fail: false,
    disable_template: false
});

QueryBuilder.defaults({
    operatorOpposites: {
        'equal':            'not_equal',
        'not_equal':        'equal',
        'in':               'not_in',
        'not_in':           'in',
        'less':             'greater_or_equal',
        'less_or_equal':    'greater',
        'greater':          'less_or_equal',
        'greater_or_equal': 'less',
        'between':          'not_between',
        'not_between':      'between',
        'begins_with':      'not_begins_with',
        'not_begins_with':  'begins_with',
        'contains':         'not_contains',
        'not_contains':     'contains',
        'ends_with':        'not_ends_with',
        'not_ends_with':    'ends_with',
        'is_empty':         'is_not_empty',
        'is_not_empty':     'is_empty',
        'is_null':          'is_not_null',
        'is_not_null':      'is_null'
    },

    conditionOpposites: {
        'AND': 'OR',
        'OR': 'AND'
    }
});

QueryBuilder.extend(/** @lends module:plugins.Invert.prototype */ {
    /**
     * Invert a Group, a Rule or the whole builder
     * @param {Node} [node]
     * @param {object} [options] {@link module:plugins.Invert}
     * @fires module:plugins.Invert.afterInvert
     * @throws InvertConditionError, InvertOperatorError
     */
    invert: function(node, options) {
        if (!(node instanceof Node)) {
            if (!this.model.root) return;
            options = node;
            node = this.model.root;
        }

        if (typeof options != 'object') options = {};
        if (options.recursive === undefined) options.recursive = true;
        if (options.invert_rules === undefined) options.invert_rules = true;
        if (options.silent_fail === undefined) options.silent_fail = false;
        if (options.trigger === undefined) options.trigger = true;

        if (node instanceof Group) {
            // invert group condition
            if (this.settings.conditionOpposites[node.condition]) {
                node.condition = this.settings.conditionOpposites[node.condition];
            }
            else if (!options.silent_fail) {
                Utils.error('InvertCondition', 'Unknown inverse of condition "{0}"', node.condition);
            }

            // recursive call
            if (options.recursive) {
                var tempOpts = $.extend({}, options, { trigger: false });
                node.each(function(rule) {
                    if (options.invert_rules) {
                        this.invert(rule, tempOpts);
                    }
                }, function(group) {
                    this.invert(group, tempOpts);
                }, this);
            }
        }
        else if (node instanceof Rule) {
            if (node.operator && !node.filter.no_invert) {
                // invert rule operator
                if (this.settings.operatorOpposites[node.operator.type]) {
                    var invert = this.settings.operatorOpposites[node.operator.type];
                    // check if the invert is "authorized"
                    if (!node.filter.operators || node.filter.operators.indexOf(invert) != -1) {
                        node.operator = this.getOperatorByType(invert);
                    }
                }
                else if (!options.silent_fail) {
                    Utils.error('InvertOperator', 'Unknown inverse of operator "{0}"', node.operator.type);
                }
            }
        }

        if (options.trigger) {
            /**
             * After {@link module:plugins.Invert.invert} method
             * @event afterInvert
             * @memberof module:plugins.Invert
             * @param {Node} node - the main group or rule that has been modified
             * @param {object} options
             */
            this.trigger('afterInvert', node, options);

            this.trigger('rulesChanged');
        }
    }
});