#
# 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 snmp_standard::mode::stringvalue;

use base qw(centreon::plugins::mode);

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

sub new {
    my ($class, %options) = @_;
    my $self = $class->SUPER::new(package => __PACKAGE__, %options);
    bless $self, $class;
    
    $options{options}->add_options(arguments => { 
        'oid:s'                   => { name => 'oid' },
        'oid-leef:s'              => { name => 'oid_leef' },
        'oid-table:s'             => { name => 'oid_table' },
        'oid-instance:s'          => { name => 'oid_instance' },
        'filter-table-value:s'    => { name => 'filter_table_value' },
        'filter-table-instance:s' => { name => 'filter_table_instance' },

        'warning-regexp:s'        => { name => 'warning_regexp' },
        'critical-regexp:s'       => { name => 'critical_regexp' },
        'unknown-regexp:s'        => { name => 'unknown_regexp' },
        'regexp-isensitive'       => { name => 'use_iregexp' }, # compatibility
        'regexp-insensitive'      => { name => 'use_iregexp' },

        'warning-absent:s@'       => { name => 'warning_absent' },
        'critical-absent:s@'      => { name => 'critical_absent' },
        'unknown-absent:s@'       => { name => 'unknown_absent' },
        'warning-present:s@'      => { name => 'warning_present' },
        'critical-present:s@'     => { name => 'critical_present' },
        'unknown-present:s@'      => { name => 'unknown_present' },

        'format-ok:s'             => { name => 'format_ok', default => '%{filter_rows} value(s)' },
        'format-warning:s'        => { name => 'format_warning', default => 'value(s): %{details_warning}' },
        'format-critical:s'       => { name => 'format_critical', default => 'value(s): %{details_critical}' },
        'format-unknown:s'        => { name => 'format_unknown', default => 'value(s): %{details_unknown}' },

        'format-details-ok:s'         => { name => 'format_details_ok', default => '%{value}' },
        'format-details-warning:s'    => { name => 'format_details_warning', default => '%{value}' },
        'format-details-critical:s'   => { name => 'format_details_critical', default => '%{value}' },
        'format-details-unknown:s'    => { name => 'format_details_unknown', default => '%{value}' },

        'format-details-separator-ok:s'       => { name => 'format_details_separator_ok', default => ', ' },
        'format-details-separator-warning:s'  => { name => 'format_details_separator_warning', default => ', ' },
        'format-details-separator-critical:s' => { name => 'format_details_separator_critical', default => ', ' },
        'format-details-separator-unknown:s'  => { name => 'format_details_separator_unknown', default => ', ' },

        'map-values:s'            => { name => 'map_values' },
        'map-value-other:s'       => { name => 'map_value_other' },
        'map-values-separator:s'  => { name => 'map_values_separator', default => ',' },
        'convert-custom-values:s' => { name => 'convert_custom_values' },

        'use-perl-mod:s@'         => { name => 'use_perl_mod' },
    });

    $self->{macros} = { ok => {}, warning => {}, critical => {}, unknown => {} };
    return $self;
}

sub check_options {
    my ($self, %options) = @_;
    $self->SUPER::init(%options);

    foreach my $mod (@{$self->{option_results}->{use_perl_mod}}) {
        centreon::plugins::misc::mymodule_load(output => $self->{output}, module => $mod,
                                               error_msg => "Cannot load module '" . $mod . "'.");
    }

    $self->{option_results}->{oid_leef} = $self->{option_results}->{oid} if (defined($self->{option_results}->{oid}) && $self->{option_results}->{oid} ne '');
    if ((!defined($self->{option_results}->{oid_leef}) || $self->{option_results}->{oid_leef} eq '') &&
        (!defined($self->{option_results}->{oid_table}) || $self->{option_results}->{oid_table} eq '')) {
       $self->{output}->add_option_msg(short_msg => 'Need to specify an OID with option --oid-leef or --oid-table.');
       $self->{output}->option_exit(); 
    }
    foreach (('oid_leef', 'oid_table', 'oid_instance')) {
        $self->{option_results}->{$_} = '.' . $self->{option_results}->{$_} if (defined($self->{option_results}->{$_}) && $self->{option_results}->{$_} ne '' && $self->{option_results}->{$_} !~ /^\./);
    }   
    
    $self->{map_values} = {};
    if (defined($self->{option_results}->{map_values})) {
        foreach (split /$self->{option_results}->{map_values_separator}/, $self->{option_results}->{map_values}) {
            my ($name, $map) = split /=>/;
            $self->{map_values}->{centreon::plugins::misc::trim($name)} = centreon::plugins::misc::trim($map);
        }
    }
}

sub get_instance_value {
    my ($self, %options) = @_;
    
    if (!defined($self->{option_results}->{oid_instance}) || $self->{option_results}->{oid_instance} eq ''
        || !defined($self->{results}->{$self->{option_results}->{oid_instance} . '.' . $options{instance}})) {
        return $options{instance};
    }
    
    return $self->{results}->{$self->{option_results}->{oid_instance} . '.' . $options{instance}};
}

sub get_change_value {
    my ($self, %options) = @_;
    
    my $value = $options{value};
    return '' if (!defined($options{value}));
    
    if (defined($self->{option_results}->{convert_custom_values}) && $self->{option_results}->{convert_custom_values} ne '') {
        eval "\$value = $self->{option_results}->{convert_custom_values}";
    }

    if (defined($value) && defined($self->{map_values}->{$value})) {
        $value = $self->{map_values}->{$value}
    } elsif (defined($self->{map_values}->{$options{value}})) {
        $value = $self->{map_values}->{$options{value}};
    } elsif (defined($self->{option_results}->{map_value_other}) && $self->{option_results}->{map_value_other} ne '') {
        $value = $self->{option_results}->{map_value_other};
    }

    return $value;
}

sub get_snmp_values {
    my ($self, %options) = @_;
    
    $self->{instances} = {};
    if (defined($self->{option_results}->{oid_leef}) && $self->{option_results}->{oid_leef} ne '') {
        $self->{results} = $self->{snmp}->get_leef(oids => [$self->{option_results}->{oid_leef}], nothing_quit => 1);
        $self->{macros}->{rows} = 1;
        $self->{macros}->{filter_rows} = 1;
        $self->{instances}->{0} = $self->get_change_value(value => $self->{results}->{$self->{option_results}->{oid_leef}});
        return 0;
    }
    
    my $tables = [ { oid => $self->{option_results}->{oid_table}} ];
    push @$tables, { oid => $self->{option_results}->{oid_instance} } if (defined($self->{option_results}->{oid_instance}) && $self->{option_results}->{oid_instance} ne '');
    $self->{results} = $self->{snmp}->get_multiple_table(oids => $tables, nothing_quit => 1, return_type => 1);
    my ($row, $filter_row) = (0, 0);
    foreach (keys %{$self->{results}}) {
        next if ($_ !~ /^$self->{option_results}->{oid_table}\.(.*)$/);
        
        $row++;
        my $instance = $self->get_instance_value(instance => $1);
        my $value = $self->get_change_value(value => $self->{results}->{$_});
        $self->{output}->output_add(long_msg => sprintf('[instance: %s][value: %s]', $_, $value), debug => 1);
        if (defined($self->{option_results}->{filter_table_value}) && $self->{option_results}->{filter_table_value} ne '' && 
            $value !~ /$self->{option_results}->{filter_table_value}/) {
            $self->{output}->output_add(long_msg => sprintf("skipping oid '%s' value '%s': not matching the filter", $_, $value), debug => 1);
            next;
        }
        if (defined($self->{option_results}->{filter_table_instance}) && $self->{option_results}->{filter_table_instance} ne '' && 
            $instance !~ /$self->{option_results}->{filter_table_instance}/) {
            $self->{output}->output_add(long_msg => sprintf("skipping oid '%s' instance '%s': not matching the filter", $_, $instance), debug => 1);
            next;
        }
        
        $self->{instances}->{$instance} = $value;
        $filter_row++;
    }
    $self->{macros}->{rows} = $row;
    $self->{macros}->{filter_rows} = $filter_row;
    
    return 0;
}

sub checking_regexp {
    my ($self, %options) = @_;
    
    return 0 if (!defined($self->{option_results}->{$options{severity} . '_regexp'}));
    my $regexp = $self->{option_results}->{$options{severity} . '_regexp'};
    
    if (defined($self->{option_results}->{use_iregexp}) && $options{value} =~ /$regexp/i) {
        $self->{instances}->{$options{severity}}->{$options{instance}} = $options{value};
        return 1;
    } elsif (!defined($self->{option_results}->{use_iregexp}) && $options{value} =~ /$regexp/) {
        $self->{instances}->{$options{severity}}->{$options{instance}} = $options{value};
        return 1;
    }    
    
    return 0;
}

sub store_ok {
    my ($self, %options) = @_;
    
    foreach my $severity (('critical', 'warning', 'unknown')) {
        foreach my $type (('absent', 'present')) {
            if (defined($self->{option_results}->{$severity . '_' . $type}) && scalar(@{$self->{option_results}->{$severity . '_' . $type}}) > 0) {
                return 0;
            }   
        }
    }

    $self->{instances}->{ok}->{$options{instance}} = $options{value};
}

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

    foreach my $severity (('critical', 'warning', 'unknown')) {
        foreach my $absent (@{$self->{option_results}->{$severity . '_absent'}}) {
            my $match = 0;
            foreach (keys %{$self->{instances}}) {
                if ($self->{instances}->{$_} eq $absent) {
                    $match = 1;
                    last;
                }
            }
            
            if ($match == 0) {
                $self->{instances}->{$severity}->{$absent} = $absent;
            }
        }

        foreach my $present (@{$self->{option_results}->{$severity . '_present'}}) {
            my $match = 0;
            foreach (keys %{$self->{instances}}) {
                if ($self->{instances}->{$_} eq $present) {
                    $self->{instances}->{$severity}->{$_} = $self->{instances}->{$_};
                }
            }
        }
    }
}

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

    my $value = $self->{option_results}->{'format_' . $options{severity}};
    $value =~ s/%\{(.*?)\}/$self->{macros}->{$1}/g;

    return $value;
}

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

    foreach my $severity (('ok', 'critical', 'warning', 'unknown')) {
        $self->{macros}->{'details_' . $severity} = '';
        my $append = '';
        foreach my $instance (sort keys %{$self->{instances}->{$severity}}) {
            my $details = $self->{option_results}->{'format_details_' . $severity};
            $details =~ s/%\{rows\}/$self->{macros}->{rows}/g;
            $details =~ s/%\{filter_rows\}/$self->{macros}->{filter_rows}/g;
            $details =~ s/%\{instance\}/$instance/g;
            $details =~ s/%\{value\}/$self->{instances}->{$severity}->{$instance}/g;

            $self->{macros}->{'details_' . $severity} .= $append . $details;
            $append = $self->{option_results}->{'format_details_separator_' . $severity};
        }
    }
}

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

    if (!(defined($options{force}) && $options{force} == 1) && scalar(keys %{$self->{instances}->{$options{severity}}}) == 0) {
        return 0;
    }
    
    my $display = $self->change_macros(severity => $options{severity});
    $self->{output}->output_add(
        severity => $options{severity},
        short_msg => $display
    );
}

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

    $self->build_format_details();
    $self->display_severity(severity => 'ok', force => 1);
    foreach my $severity (('critical', 'warning', 'unknown')) {
        $self->display_severity(severity => $severity);
    }
}

sub run {
    my ($self, %options) = @_;
    $self->{snmp} = $options{snmp};    

    $self->get_snmp_values();

    foreach (keys %{$self->{instances}}) {
        $self->checking_regexp(severity => 'critical', instance => $_, value => $self->{instances}->{$_}) || 
            $self->checking_regexp(severity => 'warning', instance => $_, value => $self->{instances}->{$_}) || 
            $self->checking_regexp(severity => 'unknown', instance => $_, value => $self->{instances}->{$_}) || 
            $self->store_ok(instance => $_, value => $self->{instances}->{$_});
    }
    $self->checking_exist();
    $self->display_result();

    $self->{output}->display();
    $self->{output}->exit();
}

1;

__END__

=head1 MODE

Check SNMP string values (can be a string or an integer).

Check absent values:
centreon_plugins.pl --plugin=snmp_standard::plugin --mode=string-value --hostname=127.0.0.1 --snmp-version=2c --snmp-community=public 
    --oid-table='.1.3.6.1.2.1.25.4.2.1.2' --format-ok='%{filter_rows} processes' --format-critical='processes are absent: %{details_critical}' --critical-absent='centengine' --critical-absent='crond' --filter-table-value='centengine|crond'

Check table status:
centreon_plugins.pl --plugin=snmp_standard::plugin --mode=string-value --hostname=127.0.0.1 --snmp-version=2c --snmp-community=akcp 
    --oid-table='.1.3.6.1.4.1.3854.1.2.2.1.16.1.4' --oid-instance='.1.3.6.1.4.1.3854.1.2.2.1.16.1.1' --map-values='1=>noStatus,2=>normal,3=>highWarning,4=>highCritical,5=>lowWarning,6=>lowCritical,7=>sensorError' --map-value-other='unknown' --format-ok='All %{rows} entries [%{filter_rows}/%{rows} Temperatures] are ok.' --format-critical='%{details_critical}' --format-details-critical='%{instance} status is %{value}' --critical-regexp='highCritical|lowCritical|sensorError'

Check like the old plugin:
centreon_plugins.pl --plugin=snmp_standard::plugin --mode=string-value --hostname=127.0.0.1 --snmp-version=2c --snmp-community=public 
    --oid='.1.3.6.1.2.1.1.1.0' --format-ok='current value is: %{details_ok}' --format-details-warning='current value is: %{details_warning}'  --format-details-critical='current value is: %{details_critical}'    
 
=over 8

=item B<--oid> or <--oid-leef>

OID value to check (numeric format only).

=item B<--oid-table>

OID table value to check (numeric format only).

=item B<--oid-instance>

OID table value for the instance (numeric format only).
Can be used to have human readable instance name.

=item B<--filter-table-value>

Filter value from --oid-table option (can be a regexp).

=item B<--filter-table-instance>

Filter instance from --oid-table option (can be a regexp).

=item B<--warning-regexp>

Return Warning if an oid value match the regexp.

=item B<--critical-regexp>

Return Critical if an oid value match the regexp.

=item B<--regexp-insensitive>

Allows to use regexp non case-sensitive.

=item B<--format-*>

Output format according to the threshold.
Can be: 
'ok' (default: '%{filter_rows} value(s)'), 
'warning' (default: 'value(s): %{details_warning}'), 
'critical' (default: 'value(s): %{details_critical}'), 
'unknown' (default: 'value(s): %{details_unknown}').
You can use the following variables:
%{rows}, %{filter_rows}, %{details_warning}, %{details_ok}, %{details_critical}, %{details_unknown}

=item B<--map-values>

Use to transform an integer value in most common case.
Example: --map-values='1=>ok,10=>fan failed,11=>psu recovery'

=item B<--map-value-other>

Use to transform an integer value not defined in --map-values option.

=item B<--map-values-separator>

Separator uses between values (default: coma).

=item B<--convert-custom-values>

Custom code to convert values.
Example to convert octet string to MAC address: --convert-custom-values='join(":", unpack("(H2)*", $value))'

=item B<--use-perl-mod>

Load additional Perl module (can be defined multiple times)
Example : --use-perl-mod='Date::Parse'

=back

=cut
