#
# Copyright 2024 Centreon (http://www.centreon.com/)
#
# Centreon is a full-fledged industry-strength solution that meets
# the needs in IT infrastructure and application monitoring for
# service performance.
#
# 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.
#

package centreon::plugins::output;

use strict;
use warnings;
use centreon::plugins::misc;

sub new {
    my ($class, %options) = @_;
    my $self  = {};
    bless $self, $class;

    if (!defined($options{options})) {
        print "Class Output: Need to specify 'options' argument to load.\n";
        exit 3;
    }

    $options{options}->add_options(arguments => {
        'explode-perfdata-max:s@' => { name => 'explode_perfdata_max' },
        'range-perfdata:s'        => { name => 'range_perfdata' },
        'filter-perfdata:s'       => { name => 'filter_perfdata' },
        'filter-perfdata-adv:s'   => { name => 'filter_perfdata_adv' },
        'change-perfdata:s@'      => { name => 'change_perfdata' },
        'extend-perfdata:s@'      => { name => 'extend_perfdata' },
        'extend-perfdata-group:s@'=> { name => 'extend_perfdata_group' },
        'change-exit:s@'          => { name => 'change_exit' },
        'change-short-output:s@'  => { name => 'change_short_output' },
        'change-long-output:s@'   => { name => 'change_long_output' },
        'change-output-adv:s@'    => { name => 'change_output_adv' },
        'use-new-perfdata'        => { name => 'use_new_perfdata' },
        'filter-uom:s'            => { name => 'filter_uom' },
        'verbose'                 => { name => 'verbose' },
        'debug'                   => { name => 'debug' },
        'debug-stream'            => { name => 'debug_stream' },
        'opt-exit:s'              => { name => 'opt_exit', default => 'unknown' },
        'output-xml'              => { name => 'output_xml' },
        'output-json'             => { name => 'output_json' },
        'output-ignore-perfdata'  => { name => 'output_ignore_perfdata' },
        'output-ignore-label'     => { name => 'output_ignore_label' },
        'output-openmetrics'      => { name => 'output_openmetrics' },
        'output-file:s'           => { name => 'output_file' },
        'disco-format'            => { name => 'disco_format' },
        'disco-show'              => { name => 'disco_show' },
        'float-precision:s'       => { name => 'float_precision', default => 8 },
        'source-encoding:s'       => { name => 'source_encoding' , default => 'UTF-8' }
    });

    $self->{option_results} = {};
    $self->{option_msg} = [];

    $self->{nodisplay} = 0;
    $self->{noexit_die} = 0;

    $self->{is_output_xml} = 0;
    $self->{is_output_json} = 0;
    $self->{errors} = {OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3, PENDING => 4};
    $self->{errors_num} = {0 => 'OK', 1 => 'WARNING', 2 => 'CRITICAL', 3 => 'UNKNOWN', 4 => 'PENDING'};
    $self->{myerrors} = {0 => "OK", 1 => "WARNING", 3 => "UNKNOWN", 7 => "CRITICAL"};
    $self->{myerrors_mask} = {CRITICAL => 7, WARNING => 1, UNKNOWN => 3, OK => 0};
    $self->{global_short_concat_outputs} = {OK => undef, WARNING => undef, CRITICAL => undef, UNKNOWN => undef, UNQUALIFIED_YET => undef};
    $self->{global_short_outputs} = {OK => [], WARNING => [], CRITICAL => [], UNKNOWN => [], UNQUALIFIED_YET => []};
    $self->{global_long_output} = [];
    $self->{perfdatas} = [];
    $self->{explode_perfdatas} = {};
    $self->{change_perfdata} = {};
    $self->{explode_perfdata_total} = 0;
    $self->{range_perfdata} = 0;
    $self->{global_status} = 0;
    $self->{encode_import} = 0;
    $self->{perlqq} = 0;
    $self->{safe_test} = 0;

    $self->{disco_elements} = [];
    $self->{disco_entries} = [];

    $self->{plugin} = '';
    $self->{mode} = '';

    return $self;
}

sub check_options {
    my ($self, %options) = @_;
    # $options{option_results} = ref to options result

    %{$self->{option_results}} = %{$options{option_results}};
    $self->{option_results}->{opt_exit} = lc($self->{option_results}->{opt_exit});
    if (!$self->is_litteral_status(status => $self->{option_results}->{opt_exit})) {
        $self->add_option_msg(short_msg => "Unknown value '" . $self->{option_results}->{opt_exit}  . "' for --opt-exit.");
        $self->option_exit(exit_litteral => 'unknown');
    }
    # Go in XML Mode
    if ($self->is_disco_show() || $self->is_disco_format()) {
        # By Default XML
        if (!defined($self->{option_results}->{output_json})) {
            $self->{option_results}->{output_xml} = 1;
        }
    }

    if (defined($self->{option_results}->{range_perfdata})) {
        $self->{range_perfdata} = $self->{option_results}->{range_perfdata};
        $self->{range_perfdata} = 1 if ($self->{range_perfdata} eq '');
        if ($self->{range_perfdata} !~ /^[012]$/) {
            $self->add_option_msg(short_msg => "Wrong range-perfdata option '" . $self->{range_perfdata} . "'");
            $self->option_exit();
        }
    }

    if (defined($self->{option_results}->{change_output_adv})) {
        foreach (@{$self->{option_results}->{change_output_adv}}) {
            my ($expr, $short_output, $exit_code) = split /,/;
            next if (!defined($expr) || $expr eq '');

            $expr =~ s/%\{(.*?)\}/\$values->{$1}/g;
            $expr =~ s/%\((.*?)\)/\$values->{$1}/g;

            if ( defined($exit_code) && $exit_code ne '' && defined( $self->{errors}->{uc($exit_code)} ) ) {
                $exit_code = uc($exit_code);
            } else {
                $exit_code = undef;
            }

            $self->{change_output_adv} = [] if (!defined($self->{change_output_adv}));
            push @{$self->{change_output_adv}}, {
                expr => $expr,
                short_output => $short_output,
                exit_code => $exit_code
            };
        }
    }

    if (defined($self->{option_results}->{change_exit})) {
        $self->{change_exit} = {};
        foreach (@{$self->{option_results}->{change_exit}}) {
            my ($src, $dst) = split(/=/);
            next if (!defined($src) || !defined($self->{errors}->{ uc($src) }));
            next if (!defined($dst) || !defined($self->{errors}->{ uc($dst) }));
            $self->{change_exit}->{uc($src)} = uc($dst);
        }
    }

    if (defined($self->{option_results}->{explode_perfdata_max})) {
        if (${$self->{option_results}->{explode_perfdata_max}}[0] eq '') {
            $self->{explode_perfdata_total} = 2;
        } else {
            $self->{explode_perfdata_total} = 1;
            foreach (@{$self->{option_results}->{explode_perfdata_max}}) {
                my ($perf_match, $perf_result) = split /,/;
                if (!defined($perf_result)) {
                    $self->add_option_msg(short_msg => "Wrong explode-perfdata-max option '" . $_ . "' (syntax: match,value)");
                    $self->option_exit();
                }
                $self->{explode_perfdatas}->{$perf_match} = $perf_result;
            }
        }
    }

    if (defined($self->{option_results}->{filter_perfdata_adv}) && $self->{option_results}->{filter_perfdata_adv} ne '') {
        $self->{option_results}->{filter_perfdata_adv} =~ s/%\{(.*?)\}/\$values->{$1}/g;
        $self->{option_results}->{filter_perfdata_adv} =~ s/%\((.*?)\)/\$values->{$1}/g;
        $self->{option_results}->{filter_perfdata_adv} =~ s/alert_triggered\(\)/alert_triggered\(%\$values\)/g;
    }

    $self->load_perfdata_extend_args();
    $self->{option_results}->{use_new_perfdata} = 1 if (defined($self->{option_results}->{output_openmetrics}));

    $self->{source_encoding} = (!defined($self->{option_results}->{source_encoding}) || $self->{option_results}->{source_encoding} eq '') ?
        'UTF-8' : $self->{option_results}->{source_encoding};
}

sub add_option_msg {
    my ($self, %options) = @_;
    # $options{short_msg} = string msg
    # $options{long_msg} = string msg
    $options{severity} = 'UNQUALIFIED_YET';

    $self->output_add(%options);
}

sub set_ignore_label {
    my ($self, %options) = @_;

    $self->{option_results}->{output_ignore_label} = 1;
}

sub set_status {
    my ($self, %options) = @_;
    # $options{exit_litteral} = string litteral exit

    # Nothing to do for 'UNQUALIFIED_YET'
    if (!$self->{myerrors_mask}->{uc($options{exit_litteral})}) {
        return ;
    }
    $self->{global_status} |= $self->{myerrors_mask}->{uc($options{exit_litteral})};
}

sub output_add {
    my ($self, %params) = @_;
    my %args = (
        severity => 'OK',
        separator => ' - ',
        debug => 0,
        short_msg => undef,
        long_msg => undef,
    );
    my $options = { %args, %params };

    if (defined($options->{short_msg})) {
        chomp $options->{short_msg};
        if (defined($self->{global_short_concat_outputs}->{uc($options->{severity})})) {
            $self->{global_short_concat_outputs}->{uc($options->{severity})} .= $options->{separator} . $options->{short_msg};
        } else {
            $self->{global_short_concat_outputs}->{uc($options->{severity})} = $options->{short_msg};
        }

        push @{$self->{global_short_outputs}->{uc($options->{severity})}}, $options->{short_msg};
        $self->set_status(exit_litteral => $options->{severity});
    }

    if (defined($options->{long_msg})) {
        chomp $options->{long_msg};

        push @{$self->{global_long_output}}, $options->{long_msg} if ($options->{debug} == 0 || defined($self->{option_results}->{debug}));
        print $options->{long_msg} . "\n" if (defined($self->{option_results}->{debug_stream}));
    }
}

sub perfdata_add {
    my ($self, %options) = @_;

    my $perfdata = {
        label => '', value => '', unit => '', warning => '', critical => '', min => '', max => '', mode => $self->{mode}
    };
    foreach (keys %options) {
        next if (!defined($options{$_}));
        $perfdata->{$_} = $options{$_};
    }

    if ((defined($self->{option_results}->{use_new_perfdata}) || defined($options{force_new_perfdata})) &&
        defined($options{nlabel})) {
        $perfdata->{label} = $options{nlabel};
    }
    if (defined($options{instances})) {
        $options{instances} = [$options{instances}] if (!ref($options{instances}));
        my ($external_instance_separator, $internal_instance_separator) = ('#', '~');
        if (defined($self->{option_results}->{use_new_perfdata}) || defined($options{force_new_perfdata})) {
            $perfdata->{label} = join('~', @{$options{instances}}) . '#' . $perfdata->{label};
        } else {
            $perfdata->{label} .= '_' . join('_', @{$options{instances}});
        }
    }

    $perfdata->{label} =~ s/'/''/g;
    push @{$self->{perfdatas}}, $perfdata;
}

sub filter_perfdata {
    my ($self, %options) = @_;

    return 1 if (
        defined($self->{option_results}->{filter_perfdata}) &&
        $options{perf}->{label} !~ /$self->{option_results}->{filter_perfdata}/
    );

    return 1 if (
        defined($self->{option_results}->{filter_perfdata_adv}) &&
        $self->{option_results}->{filter_perfdata_adv} ne '' &&
        !$self->test_eval(test => $self->{option_results}->{filter_perfdata_adv}, values => $options{perf})
    );

    return 0;
}

sub range_perfdata {
    my ($self, %options) = @_;

    return if ($self->{range_perfdata} == 0);
    if ($self->{range_perfdata} == 1) {
        for (my $i = 0; $i < scalar(@{$options{ranges}}); $i++) {
            ${${$options{ranges}}[$i]} =~ s/^(@?)-?[0\.]+:/$1/;
        }
    } else {
        for (my $i = 0; $i < scalar(@{$options{ranges}}); $i++) {
            ${${$options{ranges}}[$i]} = '';
        }
    }
}

sub output_json {
    my ($self, %options) = @_;
    my $force_ignore_perfdata = (defined($options{force_ignore_perfdata}) && $options{force_ignore_perfdata} == 1) ? 1 : 0;
    my $force_long_output = (defined($options{force_long_output}) && $options{force_long_output} == 1) ? 1 : 0;
    my $json_content = {
        plugin => {
            name => $self->{plugin},
            mode => $self->{mode},
            exit => $options{exit_litteral},
            outputs => [],
            perfdatas => []
        }
    };

    foreach my $code_litteral (keys %{$self->{global_short_outputs}}) {
        foreach (@{$self->{global_short_outputs}->{$code_litteral}}) {
            my ($child_output, $child_type, $child_msg, $child_exit);
            my $lcode_litteral = ($code_litteral eq 'UNQUALIFIED_YET' ? uc($options{exit_litteral}) : $code_litteral);

            push @{$json_content->{plugin}->{outputs}}, {
                type => 1,
                msg => ($options{nolabel} == 0 ? ($lcode_litteral . ': ') : '') . $_,
                exit => $lcode_litteral
            };
        }
    }

    if (defined($self->{option_results}->{verbose}) || $force_long_output == 1) {
        foreach (@{$self->{global_long_output}}) {
            push @{$json_content->{plugin}->{outputs}}, {
                type => 2,
                msg => $_
            };
        }
    }

    if ($options{force_ignore_perfdata} == 0) {
        $self->change_perfdata();
        foreach my $perf (@{$self->{perfdatas}}) {
            next if ($self->filter_perfdata(perf => $perf));
            $self->range_perfdata(ranges => [\$perf->{warning}, \$perf->{critical}]);

            my %values = ();
            foreach my $key (keys %$perf) {
                $perf->{$key} = '' if (defined($self->{option_results}->{filter_uom}) && $key eq 'unit' &&
                    $perf->{$key} !~ /$self->{option_results}->{filter_uom}/);
                $values{$key} = $perf->{$key};
            }

            push @{$json_content->{plugin}->{perfdatas}}, {
                %values
            };
        }
    }

    print $self->{json_output}->encode($json_content);
}

sub output_xml {
    my ($self, %options) = @_;
    my $force_ignore_perfdata = (defined($options{force_ignore_perfdata}) && $options{force_ignore_perfdata} == 1) ? 1 : 0;
    my $force_long_output = (defined($options{force_long_output}) && $options{force_long_output} == 1) ? 1 : 0;
    my ($child_plugin_name, $child_plugin_mode, $child_plugin_exit, $child_plugin_output, $child_plugin_perfdata);

    my $root = $self->{xml_output}->createElement('plugin');
    $self->{xml_output}->setDocumentElement($root);

    $child_plugin_name = $self->{xml_output}->createElement('name');
    $child_plugin_name->appendText($self->{plugin});

    $child_plugin_mode = $self->{xml_output}->createElement('mode');
    $child_plugin_mode->appendText($self->{mode});

    $child_plugin_exit = $self->{xml_output}->createElement('exit');
    $child_plugin_exit->appendText($options{exit_litteral});

    $child_plugin_output = $self->{xml_output}->createElement('outputs');
    $child_plugin_perfdata = $self->{xml_output}->createElement('perfdatas');

    $root->addChild($child_plugin_name);
    $root->addChild($child_plugin_mode);
    $root->addChild($child_plugin_exit);
    $root->addChild($child_plugin_output);
    $root->addChild($child_plugin_perfdata);

    foreach my $code_litteral (keys %{$self->{global_short_outputs}}) {
        foreach (@{$self->{global_short_outputs}->{$code_litteral}}) {
            my ($child_output, $child_type, $child_msg, $child_exit);
            my $lcode_litteral = ($code_litteral eq 'UNQUALIFIED_YET' ? uc($options{exit_litteral}) : $code_litteral);

            $child_output = $self->{xml_output}->createElement('output');
            $child_plugin_output->addChild($child_output);

            $child_type = $self->{xml_output}->createElement('type');
            $child_type->appendText(1); # short

            $child_msg = $self->{xml_output}->createElement('msg');
            $child_msg->appendText(($options{nolabel} == 0 ? ($lcode_litteral . ': ') : '') . $_);
            $child_exit = $self->{xml_output}->createElement('exit');
            $child_exit->appendText($lcode_litteral);

            $child_output->addChild($child_type);
            $child_output->addChild($child_exit);
            $child_output->addChild($child_msg);
        }
    }

    if (defined($self->{option_results}->{verbose}) || $force_long_output == 1) {
        foreach (@{$self->{global_long_output}}) {
            my ($child_output, $child_type, $child_msg);

            $child_output = $self->{xml_output}->createElement('output');
            $child_plugin_output->addChild($child_output);

            $child_type = $self->{xml_output}->createElement('type');
            $child_type->appendText(2); # long

            $child_msg = $self->{xml_output}->createElement('msg');
            $child_msg->appendText($_);

            $child_output->addChild($child_type);
            $child_output->addChild($child_msg);
        }
    }

    if ($options{force_ignore_perfdata} == 0) {
        $self->change_perfdata();
        foreach my $perf (@{$self->{perfdatas}}) {
            next if ($self->filter_perfdata(perf => $perf));
            $self->range_perfdata(ranges => [\$perf->{warning}, \$perf->{critical}]);

            my ($child_perfdata);
            $child_perfdata = $self->{xml_output}->createElement('perfdata');
            $child_plugin_perfdata->addChild($child_perfdata);
            foreach my $key (keys %$perf) {
                $perf->{$key} = '' if (defined($self->{option_results}->{filter_uom}) && $key eq 'unit' &&
                    $perf->{$key} !~ /$self->{option_results}->{filter_uom}/);
                my $child = $self->{xml_output}->createElement($key);
                $child->appendText($perf->{$key});
                $child_perfdata->addChild($child);
            }
        }
    }

    print $self->{xml_output}->toString(1);
}

sub output_openmetrics {
    my ($self, %options) = @_;

    centreon::plugins::misc::mymodule_load(
        output => $self->{output}, module => 'Time::HiRes',
        error_msg => "Cannot load module 'Time::HiRes'."
    );

    my $time_ms = int(Time::HiRes::time() * 1000);
    $self->change_perfdata();

    foreach my $perf (@{$self->{perfdatas}}) {
        next if ($self->filter_perfdata(perf => $perf));

        $perf->{unit} = '' if (
            defined($self->{option_results}->{filter_uom}) &&
            $perf->{unit} !~ /$self->{option_results}->{filter_uom}/
        );
        $self->range_perfdata(ranges => [\$perf->{warning}, \$perf->{critical}]);
        my $label = $perf->{label};
        my $instance;
        if ($label =~ /^(.*?)#(.*)$/) {
            ($perf->{instance}, $label) = ($1, $2);
        }
        my ($bucket, $append) = ('{plugin="' . $self->{plugin} . '",mode="' . $perf->{mode} . '"', '');
        foreach ('unit', 'warning', 'critical', 'min', 'max', 'instance') {
            if (defined($perf->{$_}) && $perf->{$_} ne '') {
                $bucket .= ',' . $_ . '="' . $perf->{$_} . '"';
            }
        }
        $bucket .= '}';

        print $label . $bucket . ' ' . $perf->{value} . ' ' . $time_ms . "\n";
    }
}

sub output_txt_short_display {
    my ($self, %options) = @_;

    if (defined($self->{global_short_concat_outputs}->{CRITICAL})) {
        print (($options{nolabel} == 0 ? 'CRITICAL: ' : '') . $self->{global_short_concat_outputs}->{CRITICAL} . " ");
    }
    if (defined($self->{global_short_concat_outputs}->{WARNING})) {
        print (($options{nolabel} == 0 ? 'WARNING: ' : '') . $self->{global_short_concat_outputs}->{WARNING} . " ");
    }
    if (defined($self->{global_short_concat_outputs}->{UNKNOWN})) {
        print (($options{nolabel} == 0 ? 'UNKNOWN: ' : '') . $self->{global_short_concat_outputs}->{UNKNOWN} . " ");
    }
    if (uc($options{exit_litteral}) eq 'OK') {
        print (($options{nolabel} == 0 ? 'OK: ' : '') . (defined($self->{global_short_concat_outputs}->{OK}) ? $self->{global_short_concat_outputs}->{OK} : '') . " ");
    }
}

sub output_txt_short {
    my ($self, %options) = @_;

    if (!defined($self->{option_results}->{change_short_output}) && 
        !defined($self->{change_output_adv})) {
        $self->output_txt_short_display(%options);
        return ;
    }

    my $stdout = '';
    {
        local *STDOUT;
        open STDOUT, '>', \$stdout;
        $self->output_txt_short_display(%options);
    }

    foreach (@{$self->{option_results}->{change_short_output}}) {
         my ($pattern, $replace, $modifier) = split /~/;
         next if (!defined($pattern));
         $replace = '' if (!defined($replace));
         $modifier = '' if (!defined($modifier));
         eval "\$stdout =~ s{$pattern}{$replace}$modifier";
    }

    my $exit = defined($options{exit_litteral}) ? uc($options{exit_litteral}) : uc($self->{myerrors}->{ $self->{global_status} });
    foreach (@{$self->{change_output_adv}}) {
        if ($self->test_eval(test => $_->{expr}, values => { short_output => $stdout, exit_code => $self->{errors}->{$exit} })) {
            if (defined($_->{short_output}) && $_->{short_output} ne '') {
                $stdout = $_->{short_output};
            }
            if (defined($_->{exit_code}) && $_->{exit_code} ne '') {
                $self->{coa_save_exit_code} = $_->{exit_code};
            }
        }
    }

    print $stdout;
}

sub output_txt {
    my ($self, %options) = @_;
    my $force_ignore_perfdata = (defined($options{force_ignore_perfdata}) && $options{force_ignore_perfdata} == 1) ? 1 : 0;
    my $force_long_output = (defined($options{force_long_output}) && $options{force_long_output} == 1) ? 1 : 0;

    return if ($self->{nodisplay} == 1);

    if (defined($self->{global_short_concat_outputs}->{UNQUALIFIED_YET})) {
        $self->output_add(severity => uc($options{exit_litteral}), short_msg => $self->{global_short_concat_outputs}->{UNQUALIFIED_YET});
    }

    $self->output_txt_short(%options);

    if ($force_ignore_perfdata == 0) {
        my $pipe = 0;
        $self->change_perfdata();
        foreach my $perf (@{$self->{perfdatas}}) {
            next if ($self->filter_perfdata(perf => $perf));
            $perf->{unit} = '' if (defined($self->{option_results}->{filter_uom}) &&
                $perf->{unit} !~ /$self->{option_results}->{filter_uom}/);
            $self->range_perfdata(ranges => [\$perf->{warning}, \$perf->{critical}]);
            if ($pipe == 0) {
                print '|';
                $pipe = 1;
            }
            print " '" . $perf->{label} . "'=" . $perf->{value} . $perf->{unit} . ';' . $perf->{warning} . ';' . $perf->{critical} . ';' . $perf->{min} . ';' . $perf->{max};
        }
    }

    print "\n";

    if (defined($self->{option_results}->{verbose}) || $force_long_output == 1) {
        if (scalar(@{$self->{global_long_output}})) {
            print join("\n", @{$self->{global_long_output}});
            print "\n";
        }
    }
}

sub change_long_output {
    my ($self, %options) = @_;

    return if (!(defined($self->{option_results}->{verbose}) || $options{force_long_output} == 1));
    return if (!defined($self->{option_results}->{change_long_output}));

    my $long_output = join("\n", @{$self->{global_long_output}});

    foreach (@{$self->{option_results}->{change_long_output}}) {
        my ($pattern, $replace, $modifier) = split /~/;
        next if (!defined($pattern));
        $replace = '' if (!defined($replace));
        $modifier = '' if (!defined($modifier));
        eval "\$long_output =~ s{$pattern}{$replace}$modifier";
    }

    $self->{global_long_output} = [split(/\n/, $long_output)];
}

sub display {
    my ($self, %options) = @_;
    my $nolabel = (defined($options{nolabel}) || defined($self->{option_results}->{output_ignore_label})) ? 1 : 0;
    my $force_ignore_perfdata = ((defined($options{force_ignore_perfdata}) && $options{force_ignore_perfdata} == 1) || $self->{option_results}->{output_ignore_perfdata}) ? 1 : 0;
    my $force_long_output = (defined($options{force_long_output}) && $options{force_long_output} == 1) ? 1 : 0;
    $force_long_output = 1 if (defined($self->{option_results}->{debug}));

    if (defined($self->{option_results}->{output_openmetrics})) {
        $self->perfdata_add(nlabel => 'plugin.mode.status', value => $self->{errors}->{$self->{myerrors}->{$self->{global_status}}});
    }

    if (defined($self->{option_results}->{change_long_output})) {
        $self->change_long_output(force_long_output => $force_long_output);
    }

    return if ($self->{nodisplay} == 1);

    if (defined($self->{option_results}->{output_file})) {
        if (!open (STDOUT, '>', $self->{option_results}->{output_file})) {
            $self->output_add(
                severity => 'UNKNOWN',
                short_msg => "cannot open file  '" . $self->{option_results}->{output_file} . "': $!"
            );
        }
    }
    if (defined($self->{option_results}->{output_xml})) {
        $self->create_xml_document();
        if ($self->{is_output_xml}) {
            $self->output_xml(
                exit_litteral => $self->get_litteral_status(),
                nolabel => $nolabel,
                force_ignore_perfdata => $force_ignore_perfdata, force_long_output => $force_long_output
            );
            return ;
        }
    } elsif (defined($self->{option_results}->{output_json})) {
        $self->create_json_document();
        if ($self->{is_output_json}) {
            $self->output_json(
                exit_litteral => $self->get_litteral_status(),
                nolabel => $nolabel,
                force_ignore_perfdata => $force_ignore_perfdata, force_long_output => $force_long_output
            );
            return ;
        }
    } elsif (defined($self->{option_results}->{output_openmetrics})) {
        $self->output_openmetrics();
        return ;
    }

    $self->output_txt(
        exit_litteral => $self->get_litteral_status(),
        nolabel => $nolabel,
        force_ignore_perfdata => $force_ignore_perfdata, force_long_output => $force_long_output
    );
}

sub die_exit {
    my ($self, %options) = @_;
    # $options{exit_litteral} = string litteral exit
    # $options{nolabel} = interger label display
    my $exit_litteral = defined($options{exit_litteral}) ? $options{exit_litteral} : $self->{option_results}->{opt_exit};
    my $nolabel = (defined($options{nolabel}) || defined($self->{option_results}->{output_ignore_label})) ? 1 : 0;
    # ignore long output in the following case
    $self->{option_results}->{verbose} = undef;

    if (defined($self->{option_results}->{output_xml})) {
        $self->create_xml_document();
        if ($self->{is_output_xml}) {
            $self->output_xml(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1);
            $self->exit(exit_litteral => $exit_litteral);
        }
    } elsif (defined($self->{option_results}->{output_json})) {
        $self->create_json_document();
        if ($self->{is_output_json}) {
            $self->output_json(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1);
            $self->exit(exit_litteral => $exit_litteral);
        }
    }

    $self->output_txt(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1);
    $self->exit(exit_litteral => $exit_litteral);
}

sub option_exit {
    my ($self, %options) = @_;
    # $options{exit_litteral} = string litteral exit
    # $options{nolabel} = interger label display
    my $exit_litteral = defined($options{exit_litteral}) ? $options{exit_litteral} : $self->{option_results}->{opt_exit};
    my $nolabel = (defined($options{nolabel}) || defined($self->{option_results}->{output_ignore_label})) ? 1 : 0;

    if (defined($self->{option_results}->{output_xml})) {
        $self->create_xml_document();
        if ($self->{is_output_xml}) {
            $self->output_xml(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1, force_long_output => 1);
            $self->exit(exit_litteral => $exit_litteral);
        }
    } elsif (defined($self->{option_results}->{output_json})) {
        $self->create_json_document();
        if ($self->{is_output_json}) {
            $self->output_json(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1, force_long_output => 1);
            $self->exit(exit_litteral => $exit_litteral);
        }
    } elsif (defined($self->{option_results}->{output_openmetrics})) {
        $self->set_status(exit_litteral => $exit_litteral);
        $self->output_openmetrics();
        $self->exit(exit_litteral => $exit_litteral);
    }

    $self->output_txt(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1, force_long_output => 1);
    $self->exit(exit_litteral => $exit_litteral);
}

sub exit {
    my ($self, %options) = @_;

    if ($self->{noexit_die} == 1) {
        die 'exit';
    }

    my $exit;
    if (defined($options{exit_litteral})) {
        $exit = uc($options{exit_litteral});
    } else {
        $exit = $self->{myerrors}->{ $self->{global_status} };
    }
 
    if (defined($self->{coa_save_exit_code})) {
        $exit = $self->{coa_save_exit_code};
    }
    if (defined($self->{change_exit}) && defined($self->{change_exit}->{$exit})) {
        $exit = $self->{change_exit}->{$exit};
    }
    exit $self->{errors}->{$exit};
}

sub get_option {
    my ($self, %options) = @_;

    return $self->{option_results}->{$options{option}};
}

sub get_most_critical {
    my ($self, %options) = @_;
    my $current_status = 0; # For 'OK'

    foreach (@{$options{status}}) {
        if ($self->{myerrors_mask}->{uc($_)} > $current_status) {
            $current_status = $self->{myerrors_mask}->{uc($_)};
        }
    }
    return $self->{myerrors}->{$current_status};
}

sub get_litteral_status {
    my ($self, %options) = @_;

    if (defined($options{status})) {
        if (defined($self->{errors_num}->{$options{status}})) {
            return $self->{errors_num}->{$options{status}};
        }
        return $options{status};
    } else {
        return $self->{myerrors}->{$self->{global_status}};
    }
}

sub is_status {
    my ($self, %options) = @_;
    # $options{value} = string status
    # $options{litteral} = value is litteral
    # $options{compare} = string status

    if (defined($options{litteral})) {
        my $value = defined($options{value}) ? $options{value} : $self->get_litteral_status();

        if (uc($value) eq uc($options{compare})) {
            return 1;
        }
        return 0;
    }

    my $value = defined($options{value}) ? $options{value} : $self->{global_status};
    my $dec_val = $self->{myerrors_mask}->{$value};
    my $lresult = $value & $dec_val;
    # Need to manage 0
    if ($lresult > 0 || ($dec_val == 0 && $value == 0)) {
        return 1;
    }
    return 0;
}

sub is_litteral_status {
    my ($self, %options) = @_;
    # $options{status} = string status

    if (defined($self->{errors}->{uc($options{status})})) {
        return 1;
    }

    return 0;
}

sub create_json_document {
    my ($self) = @_;

    if (centreon::plugins::misc::mymodule_load(
        no_quit => 1, module => 'JSON',
        error_msg => "Cannot load module 'JSON'.")
        ) {
        print "Cannot load module 'JSON'\n";
        $self->exit(exit_litteral => 'unknown');
    }
    $self->{is_output_json} = 1;
    $self->{json_output} = JSON->new->utf8();
}

sub create_xml_document {
    my ($self) = @_;

    if (centreon::plugins::misc::mymodule_load(
        no_quit => 1, module => 'XML::LibXML',
        error_msg => "Cannot load module 'XML::LibXML'.")
        ) {
        print "Cannot load module 'XML::LibXML'\n";
        $self->exit(exit_litteral => 'unknown');
    }
    $self->{is_output_xml} = 1;
    $self->{xml_output} = XML::LibXML::Document->new('1.0', 'utf-8');
}

sub plugin {
    my ($self, %options) = @_;
    # $options{name} = string name

    if (defined($options{name})) {
        $self->{plugin} = $options{name};
    }
    return $self->{plugin};
}

sub mode {
    my ($self, %options) = @_;

    if (defined($options{name})) {
        $self->{mode} = $options{name};
    }
    return $self->{mode};
}

sub add_disco_format {
    my ($self, %options) = @_;

    push @{$self->{disco_elements}}, @{$options{elements}};
}

sub display_disco_format {
    my ($self, %options) = @_;

    if (defined($self->{option_results}->{output_xml})) {
        $self->create_xml_document();

        my $root = $self->{xml_output}->createElement('data');
        $self->{xml_output}->setDocumentElement($root);

        foreach (@{$self->{disco_elements}}) {
            my $child = $self->{xml_output}->createElement("element");
            $child->appendText($_);
            $root->addChild($child);
        }

        print $self->{xml_output}->toString(1);
    } elsif (defined($self->{option_results}->{output_json})) {
        $self->create_json_document();
        my $json_content = {data => [] };
        foreach (@{$self->{disco_elements}}) {
            push @{$json_content->{data}}, $_;
        }

        print $self->{json_output}->encode($json_content);
    }
}

sub display_disco_show {
    my ($self, %options) = @_;

    if (defined($self->{option_results}->{output_xml})) {
        $self->create_xml_document();

        my $root = $self->{xml_output}->createElement('data');
        $self->{xml_output}->setDocumentElement($root);

        foreach (@{$self->{disco_entries}}) {
            my $child = $self->{xml_output}->createElement('label');
            foreach my $key (keys %$_) {
                $child->setAttribute($key, $_->{$key});
            }
            $root->addChild($child);
        }

        print $self->{xml_output}->toString(1);
    } elsif (defined($self->{option_results}->{output_json})) {
        $self->create_json_document();
        my $json_content = {data => [] };
        foreach (@{$self->{disco_entries}}) {
            my %values = ();
            foreach my $key (keys %$_) {
                $values{$key} = $_->{$key};
            }
            push @{$json_content->{data}}, {%values};
        }

        print $self->{json_output}->encode($json_content);
    }
}

sub decode {
    my ($self, $value) = @_;

    if ($self->{encode_import} == 0) {
        # Some Perl version dont have the following module (like Perl 5.6.x)
        my $rv = centreon::plugins::misc::mymodule_load(
            no_quit => 1,
            module => 'Encode',
            error_msg => "Cannot load module 'Encode'."
        );
        return $value if ($rv);

        $self->{encode_import} = 1;
        eval '$self->{perlqq} = Encode::PERLQQ';
    }

    return centreon::plugins::misc::trim(Encode::decode($self->{source_encoding}, $value, $self->{perlqq}));
}

sub parameter {
    my ($self, %options) = @_;

    if (defined($options{attr})) {
        $self->{$options{attr}} = $options{value};
    }
    return $self->{$options{attr}};
}

sub add_disco_entry {
    my ($self, %options) = @_;

    push @{$self->{disco_entries}}, {%options};
}

sub is_disco_format {
    my ($self) = @_;

    if (defined($self->{option_results}->{disco_format})) {
        return 1;
    }
    return 0;
}

sub is_disco_show {
    my ($self) = @_;

    if (defined($self->{option_results}->{disco_show})) {
        return 1;
    }
    return 0;
}

sub is_verbose {
    my ($self) = @_;

    if (defined($self->{option_results}->{verbose})) {
        return 1;
    }
    return 0;
}

sub is_debug {
    my ($self) = @_;

    if (defined($self->{option_results}->{debug}) || defined($self->{option_results}->{debug_stream})) {
        return 1;
    }
    return 0;
}

sub load_eval {
    my ($self) = @_;

    my ($code) = centreon::plugins::misc::mymodule_load(
        output => $self->{output}, module => 'Safe',
        no_quit => 1
    );
    if ($code == 0) {
        $self->{safe} = Safe->new();
        $self->{safe}->share('$values');
        $self->{safe}->share('$assign_var');
        $self->{safe}->share_from('centreon::plugins::misc', ['alert_triggered']);
    }

    $self->{safe_test} = 1;
}

sub test_eval {
    my ($self, %options) = @_;

    $self->load_eval() if ($self->{safe_test} == 0);

    my $result;
    if (defined($self->{safe})) {
        our $values = $options{values};
        $result = $self->{safe}->reval($options{test}, 1);
        if ($@) {
            die 'Unsafe code evaluation: ' . $@;
        }
    } elsif (defined($options{values})) {
        my $values = $options{values};
        {
            local $SIG{__WARN__} = sub {}; # ignore

            $result = eval "$options{test}";
            if ($@) {
                die 'Code evaluation error: ' . $@;
            }
        }
    }

    return $result;
}

sub open_eval {
    my ($self, %options) = @_;

    $self->load_eval() if ($self->{safe_test} == 0);
    our $values = $options{values};
    $self->{safe}->reval("$options{eval}", 1);

    return $values;
}

sub assign_eval {
    my ($self, %options) = @_;

    $self->load_eval() if ($self->{safe_test} == 0);

    our $assign_var;
    if (defined($self->{safe})) {
        our $values = $options{values};
        $self->{safe}->reval("\$assign_var = $options{eval}", 1);
        if ($@) {
            die 'Unsafe code evaluation: ' . $@;
        }
    } else {
        my $values = $options{values};
        eval "\$assign_var = $options{eval}";
    }

    return $assign_var;
}

sub use_new_perfdata {
    my ($self, %options) = @_;

    $self->{option_results}->{use_new_perfdata} = $options{value}
        if (defined($options{value}));
    if (defined($self->{option_results}->{use_new_perfdata})) {
        return 1;
    }
    return 0;
}

sub get_instance_perfdata_separator {
    my ($self) = @_;

    if (defined($self->{option_results}->{use_new_perfdata})) {
        return '~';
    }
    return '_';
}

sub parse_pfdata_scale {
    my ($self, %options) = @_;

    # --extend-perfdata=traffic_in,,scale(Mbps),mbps
    my $args = { unit => 'auto' };
    if ($options{args} =~ /^([KMGTPEkmgtpe])?(B|b|bps|Bps|b\/s|auto)$/) {
        $args->{quantity} = defined($1) ? $1 : '';
        $args->{unit} = $2;
    } elsif ($options{args} ne '') {
        return 1;
    }

    return (0, $args);
}

sub parse_pfdata_math {
    my ($self, %options) = @_;

    # --extend-perfdata=perfx,,math(current + 10 - 100, 1)
    my $args = { math => undef, apply_threshold => 0 };
    my ($math, $apply_threshold) = split /\|/, $options{args};
    if ($math =~ /^((?:[\s\.\-\+\*\/0-9\(\)]|current)+)$/) {
        $args->{math} = $1;
    } elsif ($options{args} ne '') {
        return 1;
    }

    if (defined($apply_threshold) && $apply_threshold =~ /^\s*(0|1)\s*$/ ) {
        $args->{apply_threshold} = $1;
    }

    return (0, $args);
}

sub parse_pfdata_eval {
    my ($self, %options) = @_;

    # --extend-perfdata=perfx,,eval(%(label) =~ s/a/A/g)
    my $args = { expr => $options{args} };
    $args->{expr} =~ s/%\{(.*?)\}/\$values->{$1}/g;
    $args->{expr} =~ s/%\((.*?)\)/\$values->{$1}/g;
    return (0, $args);
}

sub parse_group_pfdata {
    my ($self, %options) = @_;

    $options{args} =~ s/^\s+//;
    $options{args} =~ s/\s+$//;
    my $args = { pattern_pf => $options{args} };
    return $args;
}

sub parse_pfdata_min {
    my ($self, %options) = @_;

    my $args = $self->parse_group_pfdata(%options);
    return (0, $args);
}

sub parse_pfdata_max {
    my ($self, %options) = @_;

    my $args = $self->parse_group_pfdata(%options);
    return (0, $args);
}

sub parse_pfdata_average {
    my ($self, %options) = @_;

    my $args = $self->parse_group_pfdata(%options);
    return (0, $args);
}

sub parse_pfdata_sum {
    my ($self, %options) = @_;

    my $args = $self->parse_group_pfdata(%options);
    return (0, $args);
}

sub apply_pfdata_scale {
    my ($self, %options) = @_;

    return if (${$options{perf}}->{unit} !~ /^([KMGTPEkmgtpe])?(B|b|bps|Bps|b\/s)$/);

    my ($src_quantity, $src_unit) = ($1, $2);
    my ($value, $dst_quantity, $dst_unit) = centreon::plugins::misc::scale_bytesbit(
        value => ${$options{perf}}->{value},
        src_quantity => $src_quantity, src_unit => $src_unit, dst_quantity => $options{args}->{quantity}, dst_unit => $options{args}->{unit}
    );
    ${$options{perf}}->{value} = sprintf('%.2f', $value);
    if (defined($dst_unit)) {
       ${$options{perf}}->{unit} = $dst_quantity . $dst_unit;
    } else {
        ${$options{perf}}->{unit} = $options{args}->{quantity} . $options{args}->{unit};
    }

    if (defined(${$options{perf}}->{max}) && ${$options{perf}}->{max} ne '') {
        ($value) = centreon::plugins::misc::scale_bytesbit(value => ${$options{perf}}->{max},
            src_quantity => $src_quantity, src_unit => $src_unit,
            dst_quantity => defined($dst_unit) ? $dst_quantity : $options{args}->{quantity},
            dst_unit => defined($dst_unit) ? $dst_unit : $options{args}->{unit});
        ${$options{perf}}->{max} = sprintf('%.2f', $value);
    }

    foreach my $threshold ('warning', 'critical') {
        next if (${$options{perf}}->{$threshold} eq '');
        my ($status, $result) = centreon::plugins::misc::parse_threshold(threshold => ${$options{perf}}->{$threshold});
        next if ($status == 0);

        if ($result->{start} ne '' && $result->{infinite_neg} == 0) {
            ($result->{start}) = centreon::plugins::misc::scale_bytesbit(value => $result->{start},
                src_quantity => $src_quantity, src_unit => $src_unit,
                dst_quantity => defined($dst_unit) ? $dst_quantity : $options{args}->{quantity},
                dst_unit => defined($dst_unit) ? $dst_unit : $options{args}->{unit});
        }
        if ($result->{end} ne '' && $result->{infinite_pos} == 0) {
            ($result->{end}) = centreon::plugins::misc::scale_bytesbit(value => $result->{end},
                src_quantity => $src_quantity, src_unit => $src_unit,
                dst_quantity => defined($dst_unit) ? $dst_quantity : $options{args}->{quantity},
                dst_unit => defined($dst_unit) ? $dst_unit : $options{args}->{unit});
        }

        ${$options{perf}}->{$threshold} = centreon::plugins::misc::get_threshold_litteral(%$result);
    }
}

sub apply_pfdata_invert {
    my ($self, %options) = @_;

    return if (!defined(${$options{perf}}->{max}) || ${$options{perf}}->{max} eq '');

    ${$options{perf}}->{value} = ${$options{perf}}->{max} - ${$options{perf}}->{value};
    foreach my $threshold ('warning', 'critical') {
        next if (${$options{perf}}->{$threshold} eq '');
        my ($status, $result) = centreon::plugins::misc::parse_threshold(threshold => ${$options{perf}}->{$threshold});
        next if ($status == 0);

        my $tmp = { arobase => $result->{arobase}, infinite_pos => 0, infinite_neg => 0, start => $result->{start}, end => $result->{end} };
        $tmp->{infinite_neg} = 1 if ($result->{infinite_pos} == 1);
        $tmp->{infinite_pos} = 1 if ($result->{infinite_neg} == 1);

        if ($result->{start} ne '' && $result->{infinite_neg} == 0) {
            $tmp->{end} = ${$options{perf}}->{max} - $result->{start};
        }
        if ($result->{end} ne '' && $result->{infinite_pos} == 0) {
            $tmp->{start} = ${$options{perf}}->{max} - $result->{end};
        }

        ${$options{perf}}->{$threshold} = centreon::plugins::misc::get_threshold_litteral(%$tmp);
    }
}

sub apply_pfdata_percent {
    my ($self, %options) = @_;

    return if (!defined(${$options{perf}}->{max}) || ${$options{perf}}->{max} eq '');

    ${$options{perf}}->{value} = sprintf('%.2f', ${$options{perf}}->{value} * 100 / ${$options{perf}}->{max});
    ${$options{perf}}->{unit} = '%';
    foreach my $threshold ('warning', 'critical') {
        next if (${$options{perf}}->{$threshold} eq '');
        my ($status, $result) = centreon::plugins::misc::parse_threshold(threshold => ${$options{perf}}->{$threshold});
        next if ($status == 0);

        if ($result->{start} ne '' && $result->{infinite_neg} == 0) {
            $result->{start} = sprintf('%.2f', $result->{start} * 100 / ${$options{perf}}->{max});
        }
        if ($result->{end} ne '' && $result->{infinite_pos} == 0) {
            $result->{end} = sprintf('%.2f', $result->{end} * 100 / ${$options{perf}}->{max});
        }

        ${$options{perf}}->{$threshold} = centreon::plugins::misc::get_threshold_litteral(%$result);
    }

    ${$options{perf}}->{max} = 100;
}

sub apply_pfdata_eval {
    my ($self, %options) = @_;

    ${$options{perf}} = $self->open_eval(eval => $options{args}->{expr}, values => ${$options{perf}});
}

sub apply_pfdata_math {
    my ($self, %options) = @_;

    my $math = $options{args}->{math};
    $math =~ s/current/\$value/g;

    my $value = ${$options{perf}}->{value};
    eval "\${\$options{perf}}->{value} = $math";

    return if ($options{args}->{apply_threshold} == 0);

    foreach my $threshold ('warning', 'critical') {
        next if (${$options{perf}}->{$threshold} eq '');
        my ($status, $result) = centreon::plugins::misc::parse_threshold(threshold => ${$options{perf}}->{$threshold});
        next if ($status == 0);

        if ($result->{start} ne '' && $result->{infinite_neg} == 0) {
            $value = $result->{start};
            eval "\$result->{start} = $math";
        }
        if ($result->{end} ne '' && $result->{infinite_pos} == 0) {
            $value = $result->{end};
            eval "\$result->{end} = $math";
        }

        ${$options{perf}}->{$threshold} = centreon::plugins::misc::get_threshold_litteral(%$result);
    }

    ${$options{perf}}->{max} = 100;
}

sub apply_pfdata_min {
    my ($self, %options) = @_;

    my $pattern_pf = $self->assign_eval(eval => "\"$options{args}->{pattern_pf}\"");
    my $min;
    for (my $i = 0; $i < scalar(@{$self->{perfdatas}}); $i++) {
        next if ($self->{perfdatas}->[$i]->{label} !~ /$pattern_pf/);
        next if ($self->{perfdatas}->[$i]->{value} !~ /\d+/);
        $min = $self->{perfdatas}->[$i]->{value}
            if (!defined($min) || $min > $self->{perfdatas}->[$i]->{value});
    }

    ${$options{perf}}->{value} = $min
        if (defined($min));
}

sub apply_pfdata_max {
    my ($self, %options) = @_;

    my $pattern_pf = $self->assign_eval(eval => "\"$options{args}->{pattern_pf}\"");
    my $max;
    for (my $i = 0; $i < scalar(@{$self->{perfdatas}}); $i++) {
        next if ($self->{perfdatas}->[$i]->{label} !~ /$pattern_pf/);
        next if ($self->{perfdatas}->[$i]->{value} !~ /\d+/);
        $max = $self->{perfdatas}->[$i]->{value}
            if (!defined($max) || $max < $self->{perfdatas}->[$i]->{value});
    }

    ${$options{perf}}->{value} = $max
        if (defined($max));
}

sub apply_pfdata_sum {
    my ($self, %options) = @_;

    my $pattern_pf = $self->assign_eval(eval => "\"$options{args}->{pattern_pf}\"");
    my ($sum, $num) = (0, 0);
    for (my $i = 0; $i < scalar(@{$self->{perfdatas}}); $i++) {
        next if ($self->{perfdatas}->[$i]->{label} !~ /$pattern_pf/);
        next if ($self->{perfdatas}->[$i]->{value} !~ /\d+/);
        $sum += $self->{perfdatas}->[$i]->{value};
        $num++;
    }

    ${$options{perf}}->{value} = $sum
        if ($num > 0);
}

sub apply_pfdata_average {
    my ($self, %options) = @_;

    my $pattern_pf = $self->assign_eval(eval => "\"$options{args}->{pattern_pf}\"");
    my ($sum, $num) = (0, 0);
    for (my $i = 0; $i < scalar(@{$self->{perfdatas}}); $i++) {
        next if ($self->{perfdatas}->[$i]->{label} !~ /$pattern_pf/);
        next if ($self->{perfdatas}->[$i]->{value} !~ /\d+/);
        $sum += $self->{perfdatas}->[$i]->{value};
        $num++;
    }

    ${$options{perf}}->{value} = sprintf("%.2f", ($sum / $num))
        if ($num > 0);
}

sub apply_perfdata_thresholds {
    my ($self, %options) = @_;

    foreach (('warning', 'critical')) {
        next if (!defined($options{$_}));

        my @thresholds = split(':', $options{$_}, -1);
        for (my $i = 0; $i < scalar(@thresholds); $i++) {
            if ($thresholds[$i] =~ /(\d+(?:\.\d+)?)\s*%/) {
                if (!defined($options{max}) || $options{max} eq '') {
                    $thresholds[$i] = '';
                    next;
                }
                $thresholds[$i] = $1 * $options{max} / 100;
            } elsif ($thresholds[$i] =~ /(\d+(?:\.\d+)?)/) {
                $thresholds[$i] = $1;
            } else {
                $thresholds[$i] = '';
            }
        }

        ${$options{perf}}->{$_} = join(':', @thresholds);
    }
}

sub load_perfdata_extend_args {
    my ($self, %options) = @_;

    foreach (
        [$self->{option_results}->{change_perfdata}, 1],
        [$self->{option_results}->{extend_perfdata}, 2],
        [$self->{option_results}->{extend_perfdata_group}, 3],
    ) {
        next if (!defined($_->[0]));
        foreach my $arg (@{$_->[0]}) {
            $self->parse_perfdata_extend_args(arg => $arg, type => $_->[1]);
        }
    }
}

sub parse_perfdata_extend_args {
    my ($self, %options) = @_;

    # --extend-perfdata=searchlabel,newlabel,method[,[newuom],[min],[max],[warning],[critical]]
    my ($pfdata_match, $pfdata_substitute, $method, $uom_sub, $min_sub, $max_sub, $warn_sub, $crit_sub) =
        split /,/, $options{arg};
    return if ((!defined($pfdata_match) || $pfdata_match eq '') && $options{type} != 3);

    $self->{pfdata_extends} = [] if (!defined($self->{pfdata_extends}));
    my $pfdata_extends = {
        pfdata_match => defined($pfdata_match) && $pfdata_match ne '' ? $pfdata_match : undef,
        pfdata_substitute => defined($pfdata_substitute) && $pfdata_substitute ne '' ? $pfdata_substitute : undef,
        uom_sub => defined($uom_sub) && $uom_sub ne '' ? $uom_sub : undef,
        min_sub => defined($min_sub) && $min_sub ne '' ? $min_sub : undef,
        max_sub => defined($max_sub) && $max_sub ne '' ? $max_sub : undef,
        warn_sub => defined($warn_sub) && $warn_sub ne '' ? $warn_sub : undef,
        crit_sub => defined($crit_sub) && $crit_sub ne '' ? $crit_sub : undef,
        type => $options{type}
    };

    if (defined($method) && $method ne '') {
        if ($method !~ /^\s*(invert|percent|scale|math|min|max|average|sum|eval)\s*\(\s*(.*?)\s*\)\s*$/) {
            $self->output_add(long_msg => "method in argument '$options{arg}' is unknown", debug => 1);
            return ;
        }

        $pfdata_extends->{method_name} = $1;
        my $args = $2;
        if (my $func = $self->can('parse_pfdata_' . $pfdata_extends->{method_name})) {
            (my $status, $pfdata_extends->{method_args}) = $func->($self, args => $args);
            if ($status == 1) {
                $self->output_add(long_msg => "argument in method '$options{arg}' is unknown", debug => 1);
                return ;
            }
        }
    }

    push @{$self->{pfdata_extends}}, $pfdata_extends;
}

sub apply_perfdata_explode {
    my ($self, %options) = @_;

    return if ($self->{explode_perfdata_total} == 0);
    foreach (@{$self->{perfdatas}}) {
        next if ($_->{max} eq '');
        if ($self->{explode_perfdata_total} == 2) {
            $self->perfdata_add(label => $_->{label} . '_max', value => $_->{max}, unit => $_->{unit});
            next;
        }
        foreach my $regexp (keys %{$self->{explode_perfdatas}}) {
            if ($_->{label} =~ /$regexp/) {
                $self->perfdata_add(label => $self->{explode_perfdatas}->{$regexp}, value => $_->{max}, unit => $_->{unit});
                last;
            }
        }
    }
}

sub apply_perfdata_extend {
    my ($self, %options) = @_;

    foreach my $extend (@{$self->{pfdata_extends}}) {
        my $new_pfdata = [];

        # Manage special case when type group and pfdata_match empty
        if ($extend->{type} == 3 && (!defined($extend->{pfdata_match}) || $extend->{pfdata_match} eq '')) {
            next if (!defined($extend->{pfdata_substitute}) || $extend->{pfdata_substitute} eq '');
            my $new_perf = {
                label => $extend->{pfdata_substitute}, value => '',
                unit => defined($extend->{uom_sub}) ? $extend->{uom_sub} : '',
                warning => '', critical => '',
                min => defined($extend->{min_sub}) ? $extend->{min_sub} : '',
                max => defined($extend->{max_sub}) ? $extend->{max_sub} : ''
            };

            if (defined($extend->{method_name})) {
                my $func = $self->can('apply_pfdata_' . $extend->{method_name});
                $func->($self, perf => \$new_perf, args => $extend->{method_args});
            }

            $self->apply_perfdata_thresholds(
                perf => \$new_perf,
                warning => $extend->{warn_sub},
                critical => $extend->{crit_sub},
                max => $new_perf->{max}
            );
            if (length($new_perf->{value})) {
                push @{$self->{perfdatas}}, $new_perf;
            }
            next;
        }

        for (my $i = 0; $i < scalar(@{$self->{perfdatas}}); $i++) {
            next if ($self->{perfdatas}->[$i]->{label} !~ /$extend->{pfdata_match}/);

            my $new_perf = { %{$self->{perfdatas}->[$i]} };
            if ($extend->{type} == 3) {
                $new_perf = { label => $self->{perfdatas}->[$i]->{label}, value => '', unit => '', warning => '', critical => '', min => '', max => '' };
            }

            if (defined($extend->{pfdata_substitute})) {
                eval "\$new_perf->{label} =~ s{$extend->{pfdata_match}}{$extend->{pfdata_substitute}}";
            }

            if (defined($extend->{method_name})) {
                my $func = $self->can('apply_pfdata_' . $extend->{method_name});
                $func->($self, perf => \$new_perf, args => $extend->{method_args});
            }

            $new_perf->{unit} = $extend->{uom_sub} if (defined($extend->{uom_sub}));
            $new_perf->{min} = $extend->{min_sub} if (defined($extend->{min_sub}));
            $new_perf->{max} = $extend->{max_sub} if (defined($extend->{max_sub}));
            $self->apply_perfdata_thresholds(
                perf => \$new_perf,
                warning => $extend->{warn_sub},
                critical => $extend->{crit_sub},
                max => $new_perf->{max}
            );

            if ($extend->{type} == 1) {
                $self->{perfdatas}->[$i] = $new_perf;
            } else {
                push @$new_pfdata, $new_perf if (length($new_perf->{value}));
            }
        }

        push @{$self->{perfdatas}}, @$new_pfdata;
    }
}

sub change_perfdata {
    my ($self, %options) = @_;

    $self->apply_perfdata_extend();
    $self->apply_perfdata_explode();
}

1;

__END__

=head1 NAME

Output class

=head1 SYNOPSIS

-

=head1 OUTPUT OPTIONS

=over 8

=item B<--verbose>

Display extended status information (long output).

=item B<--debug>

Display debug messages.

=item B<--filter-perfdata>

Filter perfdata that match the regexp.
Example: adding --filter-perfdata='avg' will remove all metrics that do not contain
'avg' from performance data.

=item B<--filter-perfdata-adv>

Filter perfdata based on a "if" condition using the following variables:
label, value, unit, warning, critical, min, max.
Variables must be written either %{variable} or %(variable).
Example: adding --filter-perfdata-adv='not (%(value) == 0 and %(max) eq "")' will
remove all metrics whose value equals 0 and that don't have a maximum value.

=item B<--explode-perfdata-max>

Create a new metric for each metric that comes with a maximum limit. The new
metric will be named identically with a '_max' suffix.
Example: it will split 'used_prct'=26.93%;0:80;0:90;0;100
into 'used_prct'=26.93%;0:80;0:90;0;100 'used_prct_max'=100%;;;;

=item B<--change-perfdata> B<--extend-perfdata>

Change or extend perfdata.
Syntax: --extend-perfdata=searchlabel,newlabel,target[,[<new-unit-of-mesure>],[min],[max]]

Common examples:

=over 4

Convert storage free perfdata into used: --change-perfdata='free,used,invert()'

Convert storage free perfdata into used: --change-perfdata='used,free,invert()'

Scale traffic values automatically: --change-perfdata='traffic,,scale(auto)'

Scale traffic values in Mbps: --change-perfdata='traffic_in,,scale(Mbps),mbps'

Change traffic values in percent: --change-perfdata='traffic_in,,percent()'

=back

=item B<--extend-perfdata-group>

Add new aggregated metrics (min, max, average or sum) for groups of metrics defined by a regex match on the metrics' names.
Syntax: --extend-perfdata-group=regex,<names-of-new-metrics>,calculation[,[<new-unit-of-mesure>],[min],[max]]
regex: regular expression
<names-of-new-metrics>: how the new metrics' names are composed (can use $1, $2... for groups defined by () in regex).
calculation: how the values of the new metrics should be calculated
<new-unit-of-mesure> (optional): unit of measure for the new metrics
min (optional): lowest value the metrics can reach
max (optional): highest value the metrics can reach

Common examples:

=over 4

Sum wrong packets from all interfaces (with interface need  --units-errors=absolute): --extend-perfdata-group=',packets_wrong,sum(packets_(discard|error)_(in|out))'

Sum traffic by interface: --extend-perfdata-group='traffic_in_(.*),traffic_$1,sum(traffic_(in|out)_$1)'

=back

=item B<--change-short-output> B<--change-long-output>

Modify the short/long output that is returned by the plugin.
Syntax: --change-short-output=pattern~replacement~modifier
Most commonly used modifiers are i (case insensitive) and g (replace all occurrences).
Example: adding --change-short-output='OK~Up~gi' will replace all occurrences of 'OK', 'ok', 'Ok' or 'oK' with 'Up'

=item B<--change-exit>

Replace an exit code with one of your choice.
Example: adding --change-exit=unknown=critical will result in a CRITICAL state
instead of an UNKNOWN state.

=item B<--change-output-adv>

Replace short output and exit code based on a "if" condition using the following variables:
short_output, exit_code.
Variables must be written either %{variable} or %(variable).
Example: adding --change-output-adv='%(short_ouput) =~ /UNKNOWN: No daemon/,OK: No daemon,OK' will 
change the following specific UNKNOWN result to an OK result.

=item B<--range-perfdata>

Rewrite the ranges displayed in the perfdata. Accepted values:
0: nothing is changed.
1: if the lower value of the range is equal to 0, it is removed.
2: remove the thresholds from the perfdata.

=item B<--filter-uom>

Mask the units when they don't match the given regular expression.

=item B<--opt-exit>

Replace the exit code in case of an execution error (i.e. wrong option provided,
SSH connection refused, timeout, etc). Default: unknown.

=item B<--output-ignore-perfdata>

Remove all the metrics from the service. The service will still have a status
and an output.

=item B<--output-ignore-label>

Remove the status label ("OK:", "WARNING:", "UNKNOWN:", CRITICAL:") from the
beginning of the output.
Example: 'OK: Ram Total:...' will become 'Ram Total:...'

=item B<--output-xml>

Return the output in XML format (to send to an XML API).

=item B<--output-json>

Return the output in JSON format (to send to a JSON API).

=item B<--output-openmetrics>

Return the output in OpenMetrics format (to send to a tool expecting this
format).

=item B<--output-file>

Write output in file (can be combined with JSON, XML and OpenMetrics options).
Example: --output-file=/tmp/output.txt will write the output in /tmp/output.txt.

=item B<--disco-format>

Applies only to modes beginning with 'list-'.
Returns the list of available macros to configure a service discovery rule
(formatted in XML).

=item B<--disco-show>

Applies only to modes beginning with 'list-'.
Returns the list of discovered objects (formatted in XML) for service discovery.

=item B<--float-precision>

Define the float precision for thresholds (default: 8).

=item B<--source-encoding>

Define the character encoding of the response sent by the monitored resource
Default: 'UTF-8'.

=head1 DESCRIPTION

B<output>.

=cut
