#!/usr/bin/perl

##############################################################################################################
#                                                HEADER                                                      #
##############################################################################################################
#--------------------------------------#
## Bibliotheken                       ##
#--------------------------------------#
BEGIN {
    unshift( @INC, ( $ENV{POPHOME} || '@POPHOME@' ) . '/lib' )
      unless $ENV{'KUNDE_NO_PERLPATH'};
}

use utf8;
use strict;
use warnings;

use Net::SNMP;
use Term::ANSIColor;

use Dbase::Getopt qw/GetOptions/;
use Dbase::Help qw/Do DoFn DoSelect DoTrans qquote/;
use Umlaut qw/textmodus/;
use UTFkram qw/decode_anything/;

our $TIMESTAMP;
our $PRINT = 0;
our $DEBUG = 0;

##############################################################################################################
#                                                TRAILER                                                     #
##############################################################################################################
#------------------------------------------------------------------------------------------------------------#
# --> FUNKTIONEN                                                                                             #
#                                                                                                            #

#--------------------------------------#
## Parsen der Option(en)              ##
#--------------------------------------#
@ARGV = GetOptions(
    'help|?' => sub {
        exec perldoc => -F => $0 or die "Kann perldoc nicht ausführen: $!\n";
    },
    'print'    => sub { $PRINT = 1; },
    'debug'    => sub { $DEBUG = 1; },
    'rz=s'     => \our $_RZ_,
    'host=s'   => \our $_Host_,
    'stelle=s' => \our $_Stelle_,
    'time=i'   => \our $_Time_,
);

( !defined $_Time_ ) ? ( $TIMESTAMP = time() ) : ( $TIMESTAMP = $_Time_ );

#-------------------------------------#
## Gibt FEHLER aus                   ##
#-------------------------------------#
sub print_error($) {
    print color("red"), "FEHLER! ", color("reset"), "$_[0]";
}

#-------------------------------------#
## Gibt Datenbank-FEHLER aus         ##
#-------------------------------------#
sub print_db_error($) {
    print color("red"), "DATENBANKFEHLER! ", color("reset"), "$_[0]";
}

#-------------------------------------#
## Gibt INTERNER FEHLER aus          ##
#-------------------------------------#
sub print_int_error($) {
    print color("red"), "INTERNER FEHLER! ", color("reset"), "$_[0]";
}

#-------------------------------------#
## Gibt WARNUNG aus                  ##
#-------------------------------------#
sub print_warning($) {
    print color("yellow"), "WARNUNG! ", color("reset"), "$_[0]";
}

#-------------------------------------#
## Hinweis wenn Zähler übersprungen  ##
#-------------------------------------#
sub print_skip() {
    print_warning("Messstelle wurde übersprungen.\n");
}

#-------------------------------------#
## Gibt aus, was auf einen zu kommt  ##
#-------------------------------------#
sub print_status($$$) {
    my ( $_result, $_ms_name, $_typ ) = @_;
    print "Anzahl der bevorstehenden Eingaben: $_result\n";    # Infoausgabe, was so auf einen nun zu kommt
    print "$_typ ", color("green"), "$_ms_name", color("reset"), ": ";
}

#-------------------------------------#
## Wenn was nicht gefunden wurde     ##
#-------------------------------------#
sub print_not_found($) {
    print color("yellow"), $_[0], color("reset"), "\n";
}

#-------------------------------------#
## Hinweis auf Fehleingabe           ##
#-------------------------------------#
sub print_error_input($) {
    my ($_input) = @_;
    chomp $_input;
    print_error("Kann mit '$_input' nix anfangen! Das ganze nochmal bitte.\n");
}

#-------------------------------------#
## Hinweis auf defekten Zähler       ##
#-------------------------------------#
sub print_error_diff($) {
    my ($_offset_diff) = @_;

    print "Abweichung: $_offset_diff Wh [",
      color("red"),    "failure",
      color("reset"),  "] - Der Zähler scheint wohl defekt zu sein. Sind Sie sich sicher?\nSie können mit \"Ja\" Ihre ",
      color("red"),    "Eingabe bestätigen",
      color("reset"),  ", mit keiner Eingabe den ",
      color("yellow"), "Zähler überspringen",
      color("reset"),  ", oder erneut einen ",
      color("green"),  "Zählerstand eingeben",
      color("reset"),  "\n> ";
}

#-------------------------------------#
## Formatierte Ausgabe nach STDOUT   ##
#-------------------------------------#
sub print_stdout($$$) {
    printf "%15s%11s%8s\n", $_[0], $_[1], $_[2];
}

#-------------------------------------#
## Formatierter Protokoll-Eintrag    ##
#-------------------------------------#
sub print_file_offset($$$$$;$) {

    # Falls eine Differnz
    if ( defined $_[5] ) {
        printf FILE (
            "%3i: %s |\tZaehler: %10i |\teZaehler: %10i |\tOffset: %11i |\tDiff: %11i\t|",
            $_[0], $_[1], $_[2], $_[3], $_[4], $_[5]
        );

        printf FILE "*" if ( ( $_[5] >= 100 ) || ( $_[5] <= -100 ) );
        printf FILE "\n";
    }

    else {
        printf FILE (
            "%3i: %s |\tZaehler: %10i |\teZaehler: %10i |\tOffset: %11i |\n",
            $_[0], $_[1], $_[2], $_[3], $_[4]
        );
    }
}

sub print_file_manuell($$$) {
    printf FILE ( "%3i: %s |\tMesswert: %10i |\n", $_[0], $_[1], $_[2] );
}

#-------------------------------------#
## Datei löschen?                    ##
#-------------------------------------#
sub do_file($) {

    my ($file) = @_;

    print_warning("Datei '$file' existiert bereits.\nMöchten Sie die Datei löschen, bevor Sie sie beschreiben?\n");

    print "[Ja?]> ";
    my $answer = <STDIN>;

    if ( $answer =~ /^Ja$/ix ) {
        system("rm $file");
        print "Datei wurde gelöscht\n";
    }
    else {
        print "Datei wurde nicht gelöscht\n";
    }
}

#-------------------------------------#
## Checkt den Host                   ##
#-------------------------------------#
sub check_host($) {

    my ($chk_host) = @_;

    if ( defined $chk_host ) {

        my ($_chk_host) = DoFn( "
SELECT name
  FROM ipkunde
 WHERE name  = " . qquote($chk_host) );

        return undef if !defined $_chk_host;

        if ( $chk_host =~ $_chk_host ) {
            return $_chk_host;
        }
        else { return undef; }
    }
}

#-------------------------------------#
## Checkt das RZ anhand des Hosts    ##
#-------------------------------------#
sub get_rz_by_host($) {

    my ($chk_rz) = @_;

    if ( defined $chk_rz ) {

        my ($_chk_rz) = DoFn( "
SELECT rz.name
  FROM rz
  JOIN rack            ON rz.id            = rack.rz
  JOIN hardware AS hw2 ON hw2.rack         = rack.id
  JOIN hardware AS hw1 ON hw1.enthalten_in = hw2.id
  JOIN ipkunde  AS ip  ON ip.id            = hw1.ip
 WHERE ip.name  = " . qquote($chk_rz) );

        return undef if !defined $_chk_rz;

        if ( $chk_rz =~ $_chk_rz ) {
            return $_chk_rz;
        }
        else { return undef; }
    }
}

#--------------------------------------#
## Checkt das RZ anhand der Eingabe   ##
#--------------------------------------#
sub check_rz($) {

    my ($chk_rz) = @_;

    if ( defined $chk_rz ) {

        my ($_chk_rz) = DoFn( "
SELECT name
  FROM rz
 WHERE name = " . qquote($chk_rz) );

        return undef if !defined $_chk_rz;

        if ( $chk_rz =~ $_chk_rz ) {
            return $_chk_rz;
        }
        else { return undef; }
    }
}

#--------------------------------------#
## Checkt die Messstelle              ##
#--------------------------------------#
sub check_stelle($$) {

    my ( $chk_stelle, $chk_rz ) = @_;
    chomp $chk_stelle;
    my $_chk_stelle;

    if ( $chk_stelle =~ /^[-A-Za-z0-9_]+$/ix ) {

        my $_result = DoSelect {
            ($_chk_stelle) = @_;
        }"
SELECT ms.name
  FROM mess_stelle      AS ms
  JOIN mess_verbraucher AS mv ON ms.id = mv.mess_stelle
  JOIN hardware         AS hw ON hw.id = mv.hardware
  JOIN rack                   ON hw.rack = rack.id
  JOIN rz                     ON rack.rz = rz.id
   AND ms.name = "    . qquote($chk_stelle) . "
   AND rz.name = "    . qquote($chk_rz)     . "
   AND (  mv.ende > " . qquote($TIMESTAMP)  . "
       OR mv.ende IS NULL
       )";

        if ( $_result == 1 ) {
            return $_chk_stelle;
        }
        elsif ( $_result > 1 ) {
            print_db_error("Die angegebene Messstelle ist nicht eindeutig und im RZ $_result Mal vorhanden: ");
            print_not_found("$chk_stelle @ $chk_rz");
            return undef;
        }
        else {
            print_error("Konnte Messstelle und seinen Verbraucher nicht finden: ");
            print_not_found("$chk_stelle @ $chk_rz");
            return undef;
        }
    }
    else {
        print_error("Dies ist kein üblicher Name einer Messstelle: ");
        print_not_found($chk_stelle);
        return undef;
    }
}

#-------------------------------------#
## Checkt die Zählerstandeingabe     ##
#-------------------------------------#
sub check_input($) {
    my ($_zaehler) = @_;
    chomp $_zaehler;

    $_zaehler =~ tr/,/./;

    if ( $_zaehler =~ /^(|\d+\.?\d*)$/ ) {
        return $_zaehler;
    }
    else {
        return undef;
    }
}

#-------------------------------------#
## Werte berechnen                   ##
#-------------------------------------#
sub calc_value($$$) {
    return ( $_[0] * 1000, $_[1] * $_[2] );
}

#-------------------------------------#
## Offset berechnen                  ##
#-------------------------------------#
sub calc_offset($$) {
    return ( $_[0] - $_[1] );
}

#-------------------------------------#
## neuen Offset in DB eintragen      ##
#-------------------------------------#
sub set_offset($$) {
    Do( "
UPDATE mess_geraet
   SET offset = " . qquote( $_[0] ) . "
 WHERE mess_stelle = " . qquote( $_[1] ) );
}

#-------------------------------------#
## neuen Messwert in DB eintragen    ##
#-------------------------------------#
sub update_messwertablesung($$) {
    my ( $_mv_id, $_wert ) = @_;

    my $_result = DoSelect {} ( "
SELECT id 
  FROM mess_ablesung
 WHERE mess_verbraucher = " . qquote($_mv_id) . "
   AND ablesezeitpunkt  = " . qquote($TIMESTAMP) );

    if ( $_result == 0 ) {
        Do( "
INSERT INTO mess_ablesung (
       person,
       ablesezeitpunkt,
       wert,
       mess_verbraucher)
VALUES ("
              . qquote($<) . ", "
              . qquote($TIMESTAMP) . ", "
              . qquote($_wert) . ", "
              . qquote($_mv_id)
              . ")" );

    }
    elsif ( $_result == 1 ) {
        Do( "
UPDATE mess_ablesung
           SET wert   = " . qquote($_wert) . ",
               person = " . qquote($<) . "
         WHERE mess_verbraucher = " . qquote($_mv_id) . "
           AND ablesezeitpunkt  = " . qquote($TIMESTAMP) );
    }
    else {
        print_error("Diesen Eintrag gibt es bereits $_result Mal!\n");
    }
}

#------------------------------------------------------------------------------------------------------------#
# --> CHECKS                                                                                                 #
#------------------------------------------------------------------------------------------------------------#
#--------------------------------------#
## Checken der Haupt-Option(en)       ##
#--------------------------------------#
if ( !defined $_RZ_ && !defined $_Host_ ) {
    print_error("Sie müssen entweder ein RZ oder ein Host angeben. Woher soll ich denn wissen WO Sie ablesen wollen?\n");
    exit;
}

#--------------------------------------#
## Start der Checks                   ##
#--------------------------------------#

my $rz     = check_rz($_RZ_)         if defined $_RZ_;
my $host   = check_host($_Host_)     if defined $_Host_;
my $rzhost = get_rz_by_host($_Host_) if defined $_Host_;
my $stelle = undef;

# Wenn Host nicht gefunden wird
if ( defined $_Host_ && !defined $host ) {
    print_error("Der angegebene Host konnte nicht gefunden werden!\n");
    exit;
}

# Wenn Host in keinem RZ gefunden wird
if ( defined $_Host_ && !defined $rzhost ) {
    print_error("Der angegebene Host konnte in keinem RZ gefunden werden!\n");
    exit;
}

# Wenn RZ nicht gefunden wird
if ( defined $_RZ_ && !defined $rz ) {
    print_error("Das angegebene RZ konnte nicht gefunden werden!\n");
    exit;
}

$stelle = check_stelle( $_Stelle_, $rzhost )
  if ( defined $_Stelle_ && defined $rzhost );
$stelle = check_stelle( $_Stelle_, $rz )
  if ( defined $_Stelle_ && defined $rz && !defined $rzhost );

# Wenn die Messstelle nicht gefunden wird (Info wird davor schon ausgegeben)
exit if ( defined $_Stelle_ && !defined $stelle );

#--------------------------------------#
## Plausibilitätskontrolle wenn mehr  ##
## Optionen als nötig angegeben wurde ##
#--------------------------------------#

# Wenn RZ und Host angegeben wurde
if ( defined $_RZ_ && defined $_Host_ ) {

    # ... (und mindestens eine der beiden Angaben nicht OK) |oder| (OK, aber widersprüchlich) sind
    if ( !defined $rz || !defined $rzhost || ( $rz !~ $rzhost ) ) {
        print_error("Jetzt haben wir den Salat! Ihre Angaben sind widersprüchlich!\n");
        exit;
    }
    print_warning("Bitte geben Sie in Zukunft entweder einen Host ODER ein RZ an - beides ist unnötig und kann ggf. zu Unstimmigkeiten führen!\n");
}

#------------------------------------------------------------------------------------------------------------#
# --> Fallabhängige SQL-Statements für manuelle und direkte Messwertablesungen/Kontrollablesung              #
#------------------------------------------------------------------------------------------------------------#

my $CASE;
my $SQL_STATEMENT;

#--------------------------------------#
## ... ALLE automatische Messstellen  ##
## an einen Host                      ##
#--------------------------------------#
if ( defined $host && !defined $stelle && ( $PRINT == 0 ) ) {

    $CASE = 10;

    $SQL_STATEMENT = "
  SELECT ms.id,
         ms.name,
         mt.oid_praefix,
         ms.oid_suffix,
         mt.snmp_faktor,
         mg.offset,
         ip.snmp_community
    FROM mess_stelle AS ms
    JOIN mess_typ    AS mt ON mt.id = ms.typ
    JOIN mess_geraet AS mg ON ms.id = mg.mess_stelle
    JOIN ipkunde     AS ip ON ip.id = ms.snmp_host
   WHERE ip.name = " . qquote($host) . "
ORDER BY ABS(ms.oid_suffix)";
}

#--------------------------------------#
## ... EINE automatische Messstelle   ##
## an einem Host                      ##
#--------------------------------------#
elsif ( defined $host && defined $stelle && ( $PRINT == 0 ) ) {

    $CASE = 11;

    $SQL_STATEMENT = "
  SELECT ms.id,
         ms.name,
         mt.oid_praefix,
         ms.oid_suffix,
         mt.snmp_faktor,
         mg.offset,
         ip.snmp_community
    FROM mess_stelle AS ms
    JOIN mess_typ    AS mt ON mt.id = ms.typ
    JOIN mess_geraet AS mg ON ms.id = mg.mess_stelle
    JOIN ipkunde     AS ip ON ip.id = ms.snmp_host
   WHERE ip.name = " . qquote($host) . "
     AND ms.name = " . qquote($stelle);
}

#--------------------------------------#
## ... ALLE manuelle Messstellen      ##
## in einem RZ                        ##
#--------------------------------------#
elsif ( defined $rz && !defined $stelle && ( $PRINT == 0 ) ) {

    $CASE = 12;

    $SQL_STATEMENT = "
  SELECT ms.name, mv.id
    FROM mess_stelle      AS ms
    JOIN mess_verbraucher AS mv ON ms.id   = mv.mess_stelle
    JOIN hardware         AS hw ON hw.id   = mv.hardware
    JOIN rack                   ON rack.id = hw.rack
    JOIN rz                     ON rz.id   = rack.rz
   WHERE ms.snmp_host IS NULL
     AND rz.name = " . qquote($rz) . "
     AND ( mv.ende > " . qquote($TIMESTAMP) . "
           OR mv.ende IS NULL )
   ORDER BY ABS(ms.oid_suffix)";
}

#--------------------------------------#
## ... EINE manuelle Messstelle       ##
## in einem RZ                        ##
#--------------------------------------#
elsif ( defined $rz && defined $stelle && ( $PRINT == 0 ) ) {

    $CASE = 13;

    $SQL_STATEMENT = "
  SELECT ms.name, mv.id
    FROM mess_stelle      AS ms
    JOIN mess_verbraucher AS mv ON ms.id   = mv.mess_stelle
    JOIN hardware         AS hw ON hw.id   = mv.hardware
    JOIN rack                   ON rack.id = hw.rack
    JOIN rz                     ON rz.id   = rack.rz
   WHERE rz.name = " . qquote($rz) . "
     AND ms.name = " . qquote($stelle) . "
     AND ( mv.ende > " . qquote($TIMESTAMP) . "
           OR mv.ende IS NULL )";
}

#------------------------------------------------------------------------------------------------------------#
# --> Fallabhängige SQL-Statements für die manuelle Messwertablesung via Script                              #
#------------------------------------------------------------------------------------------------------------#

#--------------------------------------#
## ... ALLE automatische Messstellen  ##
## an einen Host                      ##
#--------------------------------------#
elsif ( defined $host && !defined $stelle && ( $PRINT == 1 ) ) {

    $CASE = 20;

    $SQL_STATEMENT = "
  SELECT ms.name,
         ms.oid_suffix,
         mt.oid_praefix,
         mt.snmp_faktor,
         mg.offset,
         ip.snmp_community,
         rack.name
    FROM mess_stelle      AS ms
    JOIN mess_typ         AS mt      ON mt.id      = ms.typ
    JOIN mess_geraet      AS mg      ON ms.id      = mg.mess_stelle
    JOIN mess_verbraucher AS mv      ON ms.id      = mv.mess_stelle 
    JOIN hardware         AS hw      ON hw.id      = mv.hardware
    JOIN rack                        ON rack.id    = hw.rack
    JOIN ipkunde          AS ip      ON ip.id      = ms.snmp_host
    JOIN hardware         AS hw_1    ON ip.id      = hw_1.ip
    JOIN hardware         AS hw_2    ON hw_2.id    = hw_1.enthalten_in
    JOIN rack             AS rack_ip ON rack_ip.id = hw_2.rack
    JOIN rz                          ON rz.id      = rack_ip.rz
   WHERE ip.name = " . qquote($host) . "
ORDER BY ABS(ms.oid_suffix)";
}

#--------------------------------------#
## ... EINE automatische Messstelle   ##
## an einem Host                      ##
#--------------------------------------#
elsif ( defined $host && defined $stelle && ( $PRINT == 1 ) ) {

    $CASE = 21;

    $SQL_STATEMENT = "
  SELECT ms.name,
         ms.oid_suffix,
         mt.oid_praefix,
         mt.snmp_faktor,
         mg.offset,
         ip.snmp_community,
         rack.name
    FROM mess_stelle      AS ms
    JOIN mess_typ         AS mt      ON mt.id      = ms.typ
    JOIN mess_geraet      AS mg      ON ms.id      = mg.mess_stelle
    JOIN mess_verbraucher AS mv      ON ms.id      = mv.mess_stelle 
    JOIN hardware         AS hw      ON hw.id      = mv.hardware
    JOIN rack                        ON rack.id    = hw.rack
    JOIN ipkunde          AS ip      ON ip.id      = ms.snmp_host
    JOIN hardware         AS hw_1    ON ip.id      = hw_1.ip
    JOIN hardware         AS hw_2    ON hw_2.id    = hw_1.enthalten_in
    JOIN rack             AS rack_ip ON rack_ip.id = hw_2.rack
    JOIN rz                          ON rz.id      = rack_ip.rz
   WHERE ip.name = " . qquote($host) . "
     AND ms.name = " . qquote($stelle);
}

#--------------------------------------#
## ... ALLE automatische Messstellen  ##
## in einem RZ                        ##
#--------------------------------------#
elsif ( defined $rz && !defined $stelle && ( $PRINT == 1 ) ) {

    $CASE = 22;

    $SQL_STATEMENT = "
  SELECT ms.name,
         ms.oid_suffix,
         mt.oid_praefix,
         mt.snmp_faktor,
         mg.offset,
         ip.snmp_community,
         rack.name,
         ip.name
    FROM mess_stelle      AS ms
    JOIN mess_typ         AS mt      ON mt.id      = ms.typ
    JOIN mess_geraet      AS mg      ON ms.id      = mg.mess_stelle
    JOIN mess_verbraucher AS mv      ON ms.id      = mv.mess_stelle 
    JOIN hardware         AS hw      ON hw.id      = mv.hardware
    JOIN rack                        ON rack.id    = hw.rack
    JOIN ipkunde          AS ip      ON ip.id      = ms.snmp_host
    JOIN hardware         AS hw_1    ON ip.id      = hw_1.ip
    JOIN hardware         AS hw_2    ON hw_2.id    = hw_1.enthalten_in
    JOIN rack             AS rack_ip ON rack_ip.id = hw_2.rack
    JOIN rz                          ON rz.id      = rack_ip.rz
   WHERE rz.name = " . qquote($rz) . "
ORDER BY ip.name, ABS(ms.oid_suffix)";
}

#--------------------------------------#
## ... EINE automatische Messstelle   ##
## in einem RZ                        ##
#--------------------------------------#
elsif ( defined $rz && defined $stelle && ( $PRINT == 1 ) ) {

    $CASE = 23;

    $SQL_STATEMENT = "
  SELECT ms.name,
         ms.oid_suffix,
         mt.oid_praefix,
         mt.snmp_faktor,
         mg.offset,
         ip.snmp_community,
         rack.name,
         ip.name
    FROM mess_stelle      AS ms
    JOIN mess_typ         AS mt      ON mt.id      = ms.typ
    JOIN mess_geraet      AS mg      ON ms.id      = mg.mess_stelle
    JOIN mess_verbraucher AS mv      ON ms.id      = mv.mess_stelle 
    JOIN hardware         AS hw      ON hw.id      = mv.hardware
    JOIN rack                        ON rack.id    = hw.rack
    JOIN ipkunde          AS ip      ON ip.id      = ms.snmp_host
    JOIN hardware         AS hw_1    ON ip.id      = hw_1.ip
    JOIN hardware         AS hw_2    ON hw_2.id    = hw_1.enthalten_in
    JOIN rack             AS rack_ip ON rack_ip.id = hw_2.rack
    JOIN rz                          ON rz.id      = rack_ip.rz
   WHERE rz.name = " . qquote($rz) . "
     AND ms.name = " . qquote($stelle);
}

#--------------------------------------#
## ... das, was ich vergessen habe    ##
#--------------------------------------#
else {
    $CASE          = 0;
    $SQL_STATEMENT = "empty";

    print_int_error("CASE = $CASE - Da hab ich wohl einen 'Stoni' gebaut! :-(\n");
    exit 10;
}

#--------------------------------------#
## Anzahl der Messstellen ermitteln   ##
#--------------------------------------#
my $result = DoSelect {} ($SQL_STATEMENT);
print "$SQL_STATEMENT\n\n" if ( $DEBUG == 1 );

#--------------------------------------#
## Überprüft den Aufruf EINER Stelle  ##
#--------------------------------------#
if ( defined $host && defined $stelle && $result > 1 ) {
    print_db_error(
        "Die angegebene Messstelle ist auf diesem Host nicht eindeutig! ");
    print_not_found("$stelle @ $host");
    exit 11;
}

elsif ( !defined $host && defined $rz && defined $stelle && $result > 1 ) {
    print_db_error(
        "Die angegebene Messstelle ist in diesem RZ nicht eindeutig! ");
    print_not_found("$stelle @ $rz");
    exit 12;
}

elsif ( defined $host && defined $stelle && $result == 0 ) {
    print_error("Die angegebene Messstelle ist diesem Host nicht zugeordnet! ");
    print_not_found("$stelle @ $host");
    exit 13;
}

elsif ( defined $rz && defined $stelle && $result == 0 ) {
    print_error("Die angegebene manuelle Messstelle konnte in diesem RZ nicht gefunden werden oder der Verbraucher ist beendet! ");
    print_not_found("$stelle @ $rz");
    exit 14;
}

elsif ( defined $rz && !defined $stelle && $result == 0 && $PRINT == 0 ) {
    print <<_;
In diesem RZ gibt es keine Messstellen, die manuell abgelesen werden müssen/können.
Jedenfalls existieren keine Verbraucher dazu (oder die Verbraucher sind beendet).
_
    exit 15;
}

elsif ( defined $rz && !defined $stelle && $result == 0 && $PRINT == 1 ) {
    print "In diesem RZ gibt es keine Messstelle, die vom System abgerufen werden können.\n";
    exit 16;
}

#--------------------------------------#
## Protokoll-File überschreiben?      ##
#--------------------------------------#

# Nur ausführen, wenn manuell ablesen!
if ( $PRINT == 0 ) {

    # Wenn ein $host engegeben wurde ...
    if ( defined $host ) {

        # ... und dazu keine Messstelle
        if ( -e $host && !defined $_Stelle_ ) {

            do_file($host);

        }
    }

    # Wenn ein $rz UND KEIN $host angegeben wurde
    if ( defined $rz && !defined $host ) {

        # ... und dazu keine Messstelle
        if ( -e "messstellen_" . $rz && !defined $_Stelle_ ) {

            do_file( "messstellen_" . $rz );

        }
    }
}

#--------------------------------------#
## SNMP-Funktion                      ##
#--------------------------------------#
sub get_value($$$) {
    my ( $OID, $community, $_host ) = @_;

    # Socket vorbereiten
    my ( $session, $error ) = Net::SNMP->session(
        -hostname  => $_host,
        -community => $community
    );

    # Schauen, ob die Socken sitzen ;)
    if ( !defined $session ) {
        print_error("$error\n");
        exit 1;
    }

    # Socket öffnen und Abfrage starten
    my $value = $session->get_request( -varbindlist => [$OID], );

    # Schauen, ob die Abfrage erfolgreich war
    if ( !defined $value ) {
        print_error( $session->error() . "\n" );
        $session->close();
        exit 1;
    }

    # Socket schließen und Werte zurückgeben
    $session->close();
    return $value->{$OID};
}

##############################################################################################################
#                                                MAIN                                                        #
##############################################################################################################

DoSelect {

#------------------------------------------------------------------------------------------------------------#
# --> CASE 1x                                                                                                #
#------------------------------------------------------------------------------------------------------------#
    if ( $CASE == 10 || $CASE == 11 ) {
        my ( $ms_id, $ms_name, $mt_oid, $ms_oid, $snmp_faktor, $mg_offset,
            $community )
          = @_;

        # Das SNMP-Plugin kann nur Zahlen und Punkte
        $mt_oid =~ s/^SNMPv2-SMI::enterprises/1.3.6.1.4.1/;

        my $OID = $mt_oid . $ms_oid; # OIDs zusammenfügen
        my $new_offset;              # Der neue Offset
        my $offset_diff;             # Differenz zwischen alten und neuen Offset
        my $Input;                   # Eingabe
        my $zaehler;                 # bearbeiteter Zähler (done)

        open( FILE, ">>$host" );

        while ( !defined $zaehler ) {    # solange Eingabe ungültig

            print_status( $result, $ms_name, "Zählerstand" ); # gebe Status aus
            $Input   = <STDIN>;                # Eingabeaufforderung
            $zaehler = check_input($Input);    # Checke die Eingabe

            if ( !defined $zaehler ) {    # Wenn keine Zahl und kein Leerstring

                # Schau ob es eine Messstelle auf dem Host ist
                my $stelle = check_stelle( $Input, $rzhost );

                if ( !defined $stelle ) {    # Wenn keine Messstelle gefunden
                    print_error_input($Input);    # FEHLER
                }
                else {                            # Ansonsten nochmal mit

                    # dem gleichen Host und Messstelle
                    system("$0 -host $host -stelle $stelle");
                }
            }
            elsif ( $zaehler =~ /^$/ ) {    # wenn Leerstring
                print_skip();               # dann Info ausgeben
            }

        }    # "solange-die-Eingabe-ungültig-ist-WHILE

        # Prüfung bestanden UND kein Leerstring
        if ( defined $zaehler && ( $zaehler !~ /^$/ ) ) {

            # elektronischen Zählerstand ermitteln
            my $e_zaehler = get_value( $OID, $community, $host );

            # Werte in die Grundeinheit umwandeln
            ( $zaehler, $e_zaehler ) =
              calc_value( $zaehler, $e_zaehler, $snmp_faktor );

            # Offset berechnen
            $new_offset = calc_offset( $zaehler, $e_zaehler );

            if ( defined($mg_offset) )
            {    # Falls schon mal ein Offset ermittelt wurde

                # Abweichung berechnen
                $offset_diff = $mg_offset - $new_offset;

                # Falls Differenz >=100 oder <=-100 und Differenz nicht bestätigt oder Zähler übersprungen
                while (( ( $offset_diff >= 100 ) || ( $offset_diff <= -100 ) )
                    && ( $Input !~ /^$|^Ja$/ix ) )
                {
                    print_error_diff($offset_diff);
                    $Input = <STDIN>;

                    # nur zuweisen, wenn Eingabe korrekt
                    $zaehler = check_input($Input)
                      if defined check_input($Input);

                    if (   defined check_input($Input)
                        && check_input($zaehler) !~ /^$/
                        && $Input !~ /^Ja$/ )
                    {

                        # elektronischen Zählerstand ermitteln
                        $e_zaehler = get_value( $OID, $community, $host );

                        # Werte in die Grundeinheit umwandeln
                        ( $zaehler, $e_zaehler ) =
                          calc_value( $zaehler, $e_zaehler, $snmp_faktor );

                        # Offset berechnen
                        $new_offset = calc_offset( $zaehler, $e_zaehler );

                        # Abweichung berechnen
                        $offset_diff = $mg_offset - $new_offset;

                    }
                    elsif ( !defined check_input($Input) && $Input !~ /^Ja$/ ) {
                        print_error_input($Input);
                    }
                }

                # Falls im Rahmen der Toleranz (innerhalb weniger 100 Wh)
                if ( ( $offset_diff < 100 ) && ( $offset_diff > -100 ) ) {
                    print "Abweichung: $offset_diff Wh [", color("green"), "OK",
                      color("reset"), "]\n";
                }

                if ( check_input($zaehler) =~ /^$/ )
                {    # wenn keine Eingabe erfolgte
                    print_skip();    # Zähler überspringen
                }
                else {               # In DB und File schreiben und
                    print_file_offset( $ms_id, $ms_name, $zaehler, $e_zaehler,
                        $new_offset, $offset_diff );
                    set_offset( $new_offset, $ms_id );
                }
            }

            else {                   # Falls das die erste Offsetermittlung ist
                print "Vorhergehender Offset = NULL (das ist wohl Ihr Ersteintrag).\n";
                print_file_offset( $ms_id, $ms_name, $zaehler, $e_zaehler, $new_offset );

                # Neuen Offset in die Datenbank eintragen
                set_offset( $new_offset, $ms_id );
            }

        }
        $Input   = undef;
        $zaehler = undef;
        close(FILE);
        $result--;

    }    # Das war der Fall: "automatisierte Messstellen"

#------------------------------------------------------------------------------------------------------------#
# --> CASE 12 und 13                                                                                         #
#------------------------------------------------------------------------------------------------------------#
    elsif ( $CASE == 12 || $CASE == 13 ) {
        my ( $ms_name, $mv_id ) = @_;

        open( FILE, ">>messstellen_$rz" );

        my $Input;
        my $wert;

        while ( ( $result > 0 ) && !defined $wert ) {

            print_status( $result, $ms_name, "Messwert" );
            $Input = <STDIN>;
            $wert  = check_input($Input);

            if ( !defined $wert ) {
                my $stelle = check_stelle( $Input, $rz );

                if ( !defined $stelle ) {
                    print_error_input($Input);
                }
                else {

                    # Programm nochmals aufrufen
                    system("$0 -rz $rz -stelle $stelle -time $TIMESTAMP");
                }

            }
            elsif ( $wert =~ /^$/ ) {    # Enter ohne Eingabe
                print_skip();
            }
            else {
                update_messwertablesung( $mv_id, $wert );
                print_file_manuell( $mv_id, $ms_name, $wert );
            }

        }    # "solange-Messstellen-noch-da-sind-While
        $Input = undef;
        $wert  = undef;
        close(FILE);
        $result--;

    }    # Das war der Fall: "manuelle Messstellen"

#------------------------------------------------------------------------------------------------------------#
# --> CASE 20 und 21                                                                                         #
#------------------------------------------------------------------------------------------------------------#
    elsif ( $CASE == 20 || $CASE == 21 ) {
        my ( $ms_name, $ms_oid, $mt_oid, $snmp_faktor, $mg_offset, $community,
            $rack )
          = @_;

        # Das SNMP-Plugin kann nur Zahlen und Punkte
        $mt_oid =~ s/^SNMPv2-SMI::enterprises/1.3.6.1.4.1/;

        my $OID = $mt_oid . $ms_oid;    # OIDs zusammenfügen

        # elektronischen Zählerstand ermitteln
        my $wert = get_value( $OID, $community, $host );

        if ( defined $wert ) {
            $wert *= $snmp_faktor if defined $snmp_faktor;
            $wert += $mg_offset if defined $mg_offset;
            print_stdout( $ms_name, $wert, $rack );
        }
        else {
            print_warning("Fuer die Messstelle '$ms_name' (Rack $rack) existiert kein Wert.\n");
        }
        print_warning("Kein SNMP-Faktor hinterlegt") if !defined $snmp_faktor;
        print_warning("Kein Offset hinterlegt")      if !defined $mg_offset;

    }    # Das war der Fall: "Messstellen an einem Host"

#------------------------------------------------------------------------------------------------------------#
# --> CASE 22 und 23                                                                                         #
#------------------------------------------------------------------------------------------------------------#
    elsif ( $CASE == 22 || $CASE == 23 ) {
        my ( $ms_name, $ms_oid, $mt_oid, $snmp_faktor, $mg_offset, $community,
            $rack, $host )
          = @_;

        # Das SNMP-Plugin kann nur Zahlen und Punkte
        $mt_oid =~ s/^SNMPv2-SMI::enterprises/1.3.6.1.4.1/;

        my $OID = $mt_oid . $ms_oid;                      # OIDs zusammenfügen
        my $wert = get_value( $OID, $community, $host );  # elektronischen Zählerstand ermitteln

        if ( defined $wert ) {
            $wert *= $snmp_faktor if defined $snmp_faktor;
            $wert += $mg_offset if defined $mg_offset;
            print_stdout( $ms_name, $wert, $rack );
        }
        else {
            print_warning("Fuer die Messstelle '$ms_name' (Rack $rack) existiert kein Wert.\n");
        }
        print_warning("Kein SNMP-Faktor hinterlegt") if !defined $snmp_faktor;
        print_warning("Kein Offset hinterlegt")      if !defined $mg_offset;

    }    # Das war der Fall: "viele Messstellen in einem RZ"

#------------------------------------------------------------------------------------------------------------#
# --> CASE x für alles andere                                                                                #
#------------------------------------------------------------------------------------------------------------#
    else {

        ( defined $CASE )
          ? ( print_int_error( "CASE = $CASE - Da hab ich wohl einen 'Stoni' gebaut! :-(") )
          : ( print_int_error("Da hab ich wohl einen 'Stoni' gebaut! :-(") );
        exit;

    }

}
"$SQL_STATEMENT";

__END__

=encoding utf8

=head1 NAME

mess_ablesung-manuell - Ein Tool ...

- ... zum ermitteln von Offsets zwischen elektronische und mechanische Zählerstände von Wirkarbeitszählern.

- ... das die Wirkarbeitszähler auf Ihre korrekte Funktion überprüft.

- ... für die manuelle Messwertablesung von Messstellen jeglicher Art (bisher jedoch nur PDU's und RMS's).

- ... das sämtliche Zählerstände abruft und ausgibt.

=head1 SYNOPSE

mess_ablesung-manuell -host $HOSTNAME || -rz $RECHENZENTRUM [-stelle $MESSSTELLENNAME] [-print] [-help]

=head1 OPTIONEN

Mit [-host] wird die Angabe des Hosts eingeleitet.

Mit [-rz] wird die Angabe des RZs eingeleitet.

Mit [-stelle] wird die Angabe einer Messstelle eingeleitet (optional).

Mit [-help] wird diese Hilfe ausgegeben.

=over 4

=item B<-host>
Hier wird die FQDN eines SNMP-Gerätes angegeben. Beispiel für B<$HOSTNAME>:
rms1-feld4-nbg3.hs.noris.de

Wenn die Option B<-host> verwendet wird, wird die Option B<-rz> nicht mehr benötigt.
Mit der Option B<-host> erfolgt die Verarbeitung der Messstellen, die diesem B<$HOST> zugeordnet sind.


=item B<-rz>
Hier wird der Name eines Rechenzentrums angegeben. Beispiel für B<$RECHENZENTRUM>:
nbg3

Wenn die Option B<-rz> verwendet wird, sollte die Option B<-host> nicht verwendet werden, das diese Option vorrang hat und somit die Messstellen an einem Host verarbeitet werden.
Mit der Option B<-rz> erfolgt prinzipiell die Verarbeitung von Messstellen, die sich im 'ganzen' RZ befinden.


=item B<-stelle>
Hier wird der Name einer Messstelle angegeben, die am Host $HOSTNAME angeschlossen ist ODER sich im RZ $RECHENZENTRUM befindet.
Es ist möglich WAEHREND der Eingabe von Zählerständen eine Messstelle einzugeben, die sich innerhalb des eingegebenen RZs befindet, bzw. einem Hosts zugeordnet ist.
Wird die Messstelle gefunden, kann der Zählerstand dafür eingegeben werden.
Falls nicht, erscheint eine Fehlermeldung.
Messstellen die mit B<-stelle> angegeben werden, können nur gefunden werden, wenn diese auch einem Verbraucher zugeordnet ist.
In beiden Fällen wird dort fortgesetzt, wo unterbrochen wurde - ist das nicht voll toll - es IST voll toll! :) ?

=item B<-help> | B<-?>

um (nur) diese Dokumentation anzeigen zu lassen.

=back

=head1 BESCHREIBUNG

In jedem Stromverteilerschrank mit Wirkarbeitszähler befindet sich (meist) ein Gerät, welches für die Zählung der Impulse dieser Wirkarbeitszähler zuständig ist.
Dieses Programm ermittelt die Differenz in Wattstunden (Wh) zwischen dem Impulszählerstand und dem mechanischen Zählerstand.
Durch die Angabe von B<-host> wird der $HOSTNAME angegeben und die zugehörigen Messstellen ermittelt.
Für jede Messstelle erfolgt eine Eingabeaufforderung, bei der nun der Zählerstand des Zählers eingegeben werden kann.
Wenn NUR EINE Messstelle abgelesen werden soll, kann auch mit B<-stelle> ein $MESSSTELLENNAME für die Ablesung angeben.

B<WICHTIG>: Die Eingabe erfolgt in B<Kilowattstunden (kWh)>, das heisst, es wird I<genau> die Zahl eingegeben, die auf dem mechanischen Zähler zu sehen ist (optional ist die Nachkommastelle).
Hier ein paar Beispiele, wie die Eingabe auszusehen hat:

 +----------+
 |  1234,5  |
 |   999.1  |
 |    987.  |
 |          |
 |  129834, |
 |    09876 |
 +----------+

Wie in diesen Beispielen zu sehen ist, kann die Dezimaltrennung mit einem Punkt '.' oder mit einem Komma ',' versehen werden.
Beide Varianten sind möglich.
Außerdem muss nicht zwangsweise nach einer Dezimaltrennung eine Ziffer folgen.
Beim Weglassen der Ziffer (und ggf. auch der Dezimaltrennung), wird automatisch mit '0' aufgefüllt.
Die oben gezeigten Beispielwerte werden vom Programm folgendermaßen weiterverarbeitet:

 +---------------------------------------+
 |   1234,5   -->  1234.5 -->   1234500  |
 |    999.1               -->    999100  |
 |     987.               -->    987000  |
 |  129834,   --> 129834. --> 129834000  |
 |    09876               -->   9876000  |
 +---------------------------------------+

Nach der Eingabe des Zählerstandes und dem Drücken der ENTER-Taste wird im Hintergrund eine SNMP-Abfrage auf dem jeweiligen Host für den zugehörigen Wirkarbeitszähler ausgeführt.
Der Rückgabewert der SNMP-Abfrage wird automatisch mit dem richtigen Impulsratenfaktor multipliziert und die Differenz des eingegebenen Zählerstand berechnet.
Diese Differenz wird ebenso automatisch in der Grundeinheit Wattstunden (Wh) gespeichert, bzw. in einer Datenbank-Tabelle eingetragen.
Vor dem Eintragen in die Datenbank-Tabelle erfolg jedoch noch eine interne Überprüfung.
Falls in der Vergangenheit schon einmal eine Differenzermittlung für diesen Zähler stattgefunden hat, wird dieser "alte" Wert mit dem aktuellen neuen Wert verglichen (Plausibilitätskontrolle).
Abhängig zu welchem Zeitpunkt die Abfrage stattgefunden hat, können neuer und alter Wert lediglich voneinnander um maximal +/- 100 Wh (+/- 0,1 kWh) schwanken (Toleranzgrenze).
Die alte und die neue Differenz dürfen sich nicht mehr als um die Toleranz unterscheiden - egal zu welchem Abfragezeitpunkt (auch Jahre später).

Ist bei einem Zähler die Differenz doch mal größer als 100 oder kleiner als -100 Wh, spricht dies für einen Defekt am Zähler. 
Wurde an diesem Zähler bisher nur EIN MAL eine Offsetermittlung durchgeführt, so könnte es auch sein, dass bei der ERSTEN Ablesung der Zähler falsch aufgenommen wurde.
Demnach muss der Zähler nicht zwagsläufig defekt sein.
Wenn das Programm einen Fehler zeigt und der Zähler bereits B<MEHR ALS EIN MAL> abgelsen wurde, dann ist er definitiv defekt. 
Sind bei B<ALLEN> Zähler eines Hosts die Differenz außerhalb der Toleranzgrenze, so deutet das eher auf eine Fehlfunktion der Steuerung hin.

In beiden Fällen bitte eine eMail an die Admins, oder besser gleich anrufen (Durchwahl s. LDAP)s. LDAP.
- Am Besten, während man noch vor Ort ist, falls die Steuerung einen reboot benötigt -
(Grund für die Toleranzgrenze ist übrigens ein ganz einfacher: Man kann die Wirkarbeitszähler nur bis zu einer Nachkommastelle genau ablesen).

Falls die Zählerstände für andere Anwendungen benötigt werden, müssen die Zähler nicht erneut abgelesen werden.
Zusätzlich werden sämtliche Zählerstände und Differenzen/Offsets in die Datei "HOSTNAME" geschrieben.
(Beispielsweise für die Weiterverwendung in der Excel-Tabelle für die Stromablesung für Kontrollzwecke)


Dieses Skript ist auch für die manuelle Ablesung von Messstellen vorgesehen, die keine Schnittstelle für die automatische Ablesung besitzen.
Sprich: für Messgeräte, die man 'nur' anschauen kann :-/
Da bei der manuellen Ablesung kein Gerät für die automatisierte Auslesung angeschlossen ist, kann demnach auch kein $HOSTNAME angegeben werden.
Aus diesem Grund wird mit B<-rz> das $RECHENZENTRUM angegeben.
Dabei werden alle manuellen Messstellen im angegebenen RZ ermittelt und nacheinander für die Eingabe ausgegeben.
Bei der Eingabe von manuell ermittelte Messwerte werden stets Werte in der B<GRUNDEINHEIT> eingegeben.
Hier ein paar Beispiele, wie die Eingabe auszusehen hat:

 +-----------------------------------+
 |  Display     -->     Eingabe      |
 +-----------------------------------+
 |   1234,5 A   -->        1234,5    |
 |    999.1 mA  -->           0,9991 |
 |     21,1 °C  -->          21,1    |
 |    432.9 kV  -->      432900      |
 |     95,5 %   -->           0,955  |
 |   9876   MW  -->  9876000000      |
 +-----------------------------------+

So... das wars. Jetzt könnt Ihr loslegen. Viel Spaß!


=head1 AUTHOR

Stefan Steiner <stoni@noris.net>

Für die noris network AG
