#!/usr/bin/perl -w
# $Id$
# This script (sophomorix-teach-in) is maintained by Rüdiger Beck
# It is Free Software (License GPLv3)
# If you find errors, contact the author
# jeffbeck@web.de  or  jeffbeck@gmx.de


# ===========================================================================
# Bibliotheken
# ===========================================================================
use strict;
use Getopt::Long;
Getopt::Long::Configure ("bundling");
use Sophomorix::SophomorixConfig;
use Sophomorix::SophomorixBase;
# Für das Fuzzy-matching
use String::Approx 'amatch';
use Term::ANSIColor qw(:constants); # farbiger Text RED, BLUE, ...
# nach jedem Printbefehl wieder auf Standardfarbe zurücksetzen
$Term::ANSIColor::AUTORESET = 1;
use DBI;
use Net::LDAP;
use Sophomorix::SophomorixPgLdap qw(show_modulename
                                    check_connections
                                    get_teach_in_sys_users
                                    update_user_db_entry
                                    get_sys_users
                                   );

my @arguments = @ARGV;



# Erlaubte Werte in einen Hash schreiben
my %match_fehlertoleranz_erlaubt = qw(
       5     0           10     0
      15     0           20     0
      25     0           30     0
      35     0           40     0
      45     0           50     0
      55     0           60     0
);


# fill this hash when a login is stated more than once
my %ambiguous_logins=();
my %show_ambiguous_logins=();
# fill this hash with identifiers, when one is stated more than once 
my %ambiguous_identifiers=();
my %identifiers_seen=();



# ===========================================================================
# Variablen, ...
# ===========================================================================

my $identifier;
my $hinzu_klasse;

# Hash in die Hinzukommende Schüler eingelesen werden
my %schueler_hinzu_hash;
# Hash for iignored logins
my %ignored_users;
my $ignore_file="sophomorix.teach-in.ignored";

# Hash für das Auswahlmenü
my %auswahl_hash;
# Variablen für das Auswahlmenü
my $schueler_weg="";
my $schueler_hinzu="";
my $entsprechungs_nummer=0;
my $teach_in_nummer=0;
my $user_antwort=0;
my $gecos;

# Wiederverwendbare Variablen zur Hashausgabe
my $k;
my $v;


# ===========================================================================
# Optionen verarbeiten
# ==========================================================================

# Variablen für Optionen
my $testen=0;
$Conf::log_level=1;
my $help=0;
my $ignored_users=0;
my $teach_in="";
my $ignore="";
my @ignore=();
#my %ignore=();

my $info=0;
my $next=0;
my $all=0;
my $undo_login="";
# Parameter gibt an wie verschieden ähnliche Schüler finden soll (in Prozent)
# 10% nur sehr änliche Namen werden gefunden
# 25% sinnvoller Wert
# 45% auch Schüler mit vertauschtem Vor- und Nachname werden gefunden
my $match_fehlertoleranz="";;

# Parsen der Optionen
my $testopt=GetOptions(
           "undo=s" => \$undo_login,
           "approx|a=s" => \$match_fehlertoleranz,
#           "test|t" => \$testen,
           "ignored-users" => \$ignored_users,
           "teach-in=s" => \$teach_in,
           "ignore=s" => \$ignore,
           "next=n" => \$next,
           "all" => \$all,
           "verbose|v+" => \$Conf::log_level,
           "help|h" => \$help,
           "info|i" => \$info
          );

# Prüfen, ob Optionen erkannt wurden
&check_options($testopt);
&check_connections();



# --help
if ($help==1) {
   # Scriptname ermitteln
   my @list = split(/\//,$0);
   my $scriptname = pop @list;
   # Befehlbeschreibung
   print "\nUpdates userdata (Lastname, Firstname, Birthday) in an existing\n",
         "account of the sophomorix database in an interactive way.\n";
   print('
Options
  -h  / --help
  -v  / --verbose
  -vv / --verbose --verbose
  -i / --info
  --next number
  -a value / --approx value
  --ignored-users
  --ignore user1,user2,...
  --teach-in \'user1::identifier1,user2,identifier2,...\'
  --undo login

Please see the sophomorix-teach-in(8) man pages for full documentation
');
   exit;
   #&log_script_exit("",1,1,0,@arguments);
}

# --info
if ($info==1){
    my $count=0;
    print "Undoable changes:\n";
    open(HISTORY,
     "${DevelConf::log_files}/user-modify.log") 
     || die "Fehler: $!";

    while (<HISTORY>){
      chomp();
      my @line=split(/::/);
      # show only the following:
      #   man-taech-in
      #   auto-teach-in
      if (not ( $line[0] eq "auto-teach-in"
                or $line[0] eq "man-teach-in" )){
	  next;
      }
      if (not defined $line[6]){$line[6]=""}
      my $data="(".$line[0]." ".$line[1]." Unid: $line[6])";
      if (($undo_login ne "" and $line[2] eq $undo_login) or
          ($undo_login eq "") ){
        $count++;
        print "$count) $line[2] $data:\n";
        printf "   %-7s %-65s\n","Old:",$line[3];
        printf "   %-7s %-65s\n","System:",$line[5];
      }
    }
    if ($undo_login ne ""){
        print "Action ($count) can be undone.\n";
    }
    close(HISTORY);
    exit;
    #&log_script_exit("",1,1,0,@arguments);
}


&log_script_start(@arguments);


# --teach-in in hash
my @teach_in_list = ();
my %teach_in_hash =();
if ($teach_in ne ""){
    @teach_in_list = split(/,/,$teach_in);
    foreach my $item (@teach_in_list){
        my ($login,$ident)=split(/::/,$item);
        $teach_in_hash{$login}="$ident";
        if (exists $identifiers_seen{$ident}){
            $ambiguous_identifiers{$ident}="more than once";  
            print "WARNING: Identifier $ident is ambiguous. Skipping ...\n";
        } else {
            $identifiers_seen{$ident}="seen";
        }
    }
    
}



# open the ignored file for appending
open(SOPHOMORIXIGNORE,
     ">>${DevelConf::ergebnis_pfad}/$ignore_file") 
     || die "Fehler: $!";


# --ignore
my %ignore_hash=();
if ($ignore ne ""){
    @ignore = split(/::/,$ignore);
    foreach my $user (@ignore){
        $ignore_hash{$user}="ignored";
        if (exists $teach_in_hash{$user}){
            print "WARNING: $user is given in --ignore and ",
                  "--teach-in. Skipping ...\n";
            $show_ambiguous_logins{$user}="show";
	} else {
            print SOPHOMORIXIGNORE "${user}::\n";
        }
    }
#    exit;
}


# --approx
if ($match_fehlertoleranz eq ""){
# Standardwert angeben für das Argument
$match_fehlertoleranz="25";
} else {
   if (exists($match_fehlertoleranz_erlaubt{$match_fehlertoleranz})){
           # ist im Hash zu finden
           # nix tun
       } else {
           # Abbruch
           print "  Fehlertoleranz $match_fehlertoleranz ist nicht zulässig! \n";
           print "  Zulässige Argumente sind 5 bis 60 \n";
           print "    (in 5-er Schritten) \n";
           &log_script_exit("",1,1,0,@arguments);
       }
}



# --undo login
if ($undo_login ne ""){
    print "Undoing changes to account $undo_login:\n\n";
    open(LOG,
     "${DevelConf::log_files}/user-modify.log") 
     || die "Fehler: $!";
    my $count=0;
    my ($last,$first,$birth);
    my ($kind,$date,$login,$identifier_old,$a,$identifier_sys);
    while (<LOG>){
        ($kind,$date,$login,$identifier_old,$a,$identifier_sys)=split(/::/);
        if ($login eq $undo_login){
	  $count++;
	  ($last,$first,$birth)=split(/;/,$identifier_old);
          printf " %-14s %-64s \n",
                 "($count) $login","System:           $identifier_sys";
          printf " %-14s %-64s \n\n",
                 "","Can be undone to: $identifier_old";
        }
    }
    close(LOG);

    if ($count>=1){
       &update_user_db_entry($undo_login, 
                             "Name=$first",
                             "LastName=$last",
                             "Birthday=$birth");
       # update the system
       $gecos="$first"." "."$last";
       &Sophomorix::SophomorixPgLdap::update_user_db_entry($undo_login,
                      "Gecos=$gecos");

       # removing last entry in log-lines
       open(LOG,
       "${DevelConf::log_files}/user-modify.log") 
       || die "Fehler: $!";
       my @log_lines = reverse <LOG>;
       close(LOG);

       my @log_lines_tmp;
       my $hit=0;
       foreach my $line (@log_lines){
          ($kind,$date,$login,$identifier_old,
           $a,$identifier_sys)=split(/::/,$line);
	  if ($login eq $undo_login and $hit==0){
              # dont print line
              $hit=1;
          } else {
              unshift(@log_lines_tmp,$line),
          }
       }

       # write the tmp-file
       open(LOGTMP,
       ">${DevelConf::log_files}/user-modify.log-tmp") 
       || die "Fehler: $!";
       foreach my $line (@log_lines_tmp){
	   print LOGTMP $line;
       }
       close(LOGTMP);

       # cp the tmp-file
       system("mv ${DevelConf::log_files}/user-modify.log-tmp ${DevelConf::log_files}/user-modify.log");
       print "Action ($count) was undone!\n";
    } else {
       print "Sorry: Could not find loginname $undo_login\n";
    }
    &log_script_exit("",1,1,0,@arguments);
}


# ===========================================================================
# Programmbeginn
# ===========================================================================

&titel("sophomorix-teach-in starts with $match_fehlertoleranz% amatch");

my $today=`date +%d.%m.%Y`;
my $today_pg=`date +%Y-%m-%d`;
chomp($today);
chomp($today_pg);


# ===========================================================================
# Ignored users hash erstellen
# ===========================================================================
if (-e "${DevelConf::ergebnis_pfad}/$ignore_file"){
    open(SOPHOMORIXIGNORD,
         "${DevelConf::ergebnis_pfad}/$ignore_file") 
         || die "Fehler: $!";
    if($Conf::log_level>=3){
        &titel("Users that are ignored ($ignore_file):");
    }
    while(<SOPHOMORIXIGNORD>){
        chomp();
        my $a;
        my ($login,$ident)=split(/::/);
        $ignored_users{$login}="$ident";
        if($Conf::log_level>=3){
            print "Reading:    $_\n";
            print "Login:      $login\n";
            print "Identifier: $ident\n";
        }
    }
    close(SOPHOMORIXIGNORD);
}


# --ignored-users
if ($ignored_users==1){
    my @list=();
    while (my ($login,$ident) = each %ignored_users) {
	push @list, $login;
    }
    @list = sort @list;
    &titel("Users that are ignored ($ignore_file):");
    foreach my $item (@list){
   printf "%-12s: %-64s\n",$item,$ignored_users{$item};
    }
    &log_script_exit("",1,1,0,@arguments);
}



# ===========================================================================
# Schüler-Hinzu-Hash erstellen
# ===========================================================================
open(SOPHOMORIXADD,
     "${DevelConf::ergebnis_pfad}/sophomorix.add") 
     || die "Fehler: $!";
if($Conf::log_level>=3){
   &titel("Users that would be added (sophomorix.add):");
 }
while(<SOPHOMORIXADD>){
   chomp();
   my $a;
   ($hinzu_klasse,$identifier,$a)=split(/::/);
   $schueler_hinzu_hash{$identifier}="$hinzu_klasse";
   if($Conf::log_level>=3){
      print "Reading:    $_\n";
      print "Identifier: $identifier\n";
    }
}
close(SOPHOMORIXADD);



# ===========================================================================
# Alle Schüler, die entfernt werden sollen einzeln abfragen
# ===========================================================================

&titel("Asking the system for users ...");

my ($ref_login, 
    $ref_adminclass,
    $ref_status,
    $ref_subclass,
    $ref_toleration_date,
    $ref_deactivation_date,
    $ref_unid_identifier,
    $ref_exit_adminclass,
    $ref_account_type,
   ) = &get_sys_users();

# identifier - klasse (ohne k = sophomorixAdminClass)
my %schueler_im_system_hash = %$ref_adminclass;

# identifier - loginname (uid in ldap)
my %schueler_im_system_loginname = %$ref_login;


# identifier - ExitAdminClass
my %schueler_im_system_exit_admin_class = %$ref_exit_adminclass;


&titel("Extracting teach-in users from the system ...");

my @toleration=&get_teach_in_sys_users();
&print_list("Teach-in users from the system",@toleration);

my $number=1;

foreach $identifier (@toleration){
   my $login=$schueler_im_system_loginname{$identifier};
   # skip ignored users
   if (exists $ignored_users{$login}){
       print "   $login is ignored\n";
       next;
   }

   # Gesucht wird Entsprechung zu diesem Schüler
   if ($next!=0 or $all==1){
       # noninteractive
       # if specified by option do not print
       if ((not exists $teach_in_hash{$login}
            and not exists $ignore_hash{$login}) 
            or exists $ambiguous_identifiers{$teach_in_hash{$login}}
            or exists $show_ambiguous_logins{$login}){
              print "next::${schueler_im_system_hash{$identifier}}::".
                    "${login}::${identifier}";
       }
   } else {
       # interactive
       print "----------------------------------------",
             "----------------------------------------\n";
       printf "%-25s: %-40s\n" , 
              "    Login ($number)", 
              "$login";
       printf "%-25s: %-40s\n" , 
              "    User in the system", 
              "$identifier";
       printf "%-25s: %-40s\n" , 
              "    AdminClass", 
              "$schueler_im_system_hash{$identifier}";
       if (exists $schueler_im_system_exit_admin_class{$identifier}){
          printf "%-25s: %-40s\n" , 
                 "ExitAdminClass", 
                 "$schueler_im_system_exit_admin_class{$identifier}";
       }
       print "----------------------------------------",
             "----------------------------------------\n";
   }

   # Auswahlnummer auf eins setzen
   $entsprechungs_nummer=0;
   $teach_in_nummer=0;
   # Bisherige Werte löschen
   $user_antwort="";
   %auswahl_hash=();
   # ======================================================================
   # Auswahlmenü erzeugen
   # ======================================================================
   # Hash mit allen hinzukommenden Schülern durchgehen
   while (($_,$v) = each %schueler_hinzu_hash) {
      chomp();
      $schueler_hinzu=$_;
      # Approx-Match ausgeben
      # 25% ist realistisch für Approx-Matching
      # so tuts:#  if (amatch("$schueler_weg" , ["i 25%"])){
      if (amatch("$identifier" , ["i   ${match_fehlertoleranz}%"])){
         # counter for decision number
         $entsprechungs_nummer++;
         # counter for --next
         $number++;
         # printout
         if ($next!=0 or $all==1){
            # noninteracive
            # check if option fits
	    if (exists $teach_in_hash{$login}
                and not exists $ambiguous_identifiers{$teach_in_hash{$login}}
                and not exists $show_ambiguous_logins{$login}){
                 if ($teach_in_hash{$login} eq "$schueler_hinzu"){
                        $teach_in_nummer=$entsprechungs_nummer;
                 }
	    } elsif ((not exists $teach_in_hash{$login}
                      and not exists $ignore_hash{$login})
                       or exists $ambiguous_identifiers{$teach_in_hash{$login}}
                       or exists $show_ambiguous_logins{$login}){
        	   print "::$schueler_hinzu_hash{$schueler_hinzu}::$schueler_hinzu";
	    }
         } else {
            # interactive
            printf "%-25s: %-40s\n" , 
                   "($entsprechungs_nummer) Approx match",
                   "$schueler_hinzu";
            printf "%-25s: %-40s\n" , 
                   "    AdminClass",
                   "$schueler_hinzu_hash{$schueler_hinzu}";
         }
         # Approx-Match ablegen im Auswahl-Hash
         $auswahl_hash{$entsprechungs_nummer}="$schueler_hinzu";
      }
   }# Alle Schüler durchgegangen


   # exit if it is enough
   if ($all==0 and $next!=0 and $number-1==$next and $teach_in_nummer==0){
       if ((not exists $teach_in_hash{$login}
            and not exists $ignore_hash{$login}   )
            or exists $ambiguous_identifiers{$teach_in_hash{$login}}
            or exists $show_ambiguous_logins{$login}){
              print "::\n";
       }
       &log_script_exit("",1,1,0,@arguments);
#   } elsif ( ( $all==1 or $next!=0 ) and $teach_in_nummer==0){
    } elsif ( ( $all==1 or $next!=0 ) and $teach_in_nummer==0){
       if ((not exists $teach_in_hash{$login}
            and not exists $ignore_hash{$login})
            or exists $ambiguous_identifiers{$teach_in_hash{$login}}
            or exists $show_ambiguous_logins{$login}){
              print "::\n";
       }
       next;
   }

   if ($all==1 or $next!=0) {
       # no printout
   } else {
       printf "%-25s: %-40s\n" , 
              "(0)",
              "Do nothing";
       print "----------------------------------------",
             "----------------------------------------\n";
   }


    # (0) = Nichts machen hinzufügen zum Auswahl-Hash
    $auswahl_hash{0}="Nichts tun!";



   # ======================================================================
   # Useraktion einlesen
   # ======================================================================
   if($entsprechungs_nummer==0){
   # Es gibt nur 0=Nichts tun
   $user_antwort="0";
   } elsif ($teach_in_nummer!=0) {
       print "Selected by option: $teach_in_nummer \n";
       $user_antwort=$teach_in_nummer;
   } else { # Admin nach rat fragen
      while(){# Endlosschleife für die Eingabe
         $user_antwort= <STDIN>; # Lesen von Tastatur
         chomp($user_antwort); # Newline abschneiden
         if (exists($auswahl_hash{$user_antwort})){
            # Wenn Eingabewert im Auswahl-Hash vorkommt, aussteigen
            last; # Ausstieg aus Endlosschleife
         } else {
            # Endlosschleife, Eingabe wiederholen
            print "Fehlerhafte Eingabe. Bitte wiederholen!",
                  " Zuerst die Ziffer, dann <RETURN>\n";
         }
      }
    }
   # ======================================================================
   # Resultat der Useraktion verarbeiten
   # ======================================================================
   # Ausgabe in die tech-in-datei
   if ($user_antwort eq "0"){ # nur, wenn 1,2,3,... gewählt etwas tun
      # remember this user
      print SOPHOMORIXIGNORE "$login"."::"."$identifier"."\n";
      print "\n\n\n";
    } else {
      print "========================================",
            "========================================\n";
      print "sophomorix db:  ", 
            "$identifier -> $auswahl_hash{$user_antwort}\n";
      # removing new user from sophomorix.add
      print "sophomorix.add: remove ", 
            "$auswahl_hash{$user_antwort} in ",
            "$schueler_hinzu_hash{$schueler_hinzu}\n";
      &remove_line_from_file(
          "$schueler_hinzu_hash{$schueler_hinzu}::$auswahl_hash{$user_antwort}",
          "${DevelConf::ergebnis_pfad}/sophomorix.add");
      # removing new user from sophomorix.move
      if (-e "${DevelConf::ergebnis_pfad}/sophomorix.move"){
          print "sophomorix.move: remove ", 
                "${login} in ",
                "$schueler_hinzu_hash{$schueler_hinzu}\n";
          &remove_line_from_file(
              "${login}::$schueler_hinzu_hash{$schueler_hinzu}",
              "${DevelConf::ergebnis_pfad}/sophomorix.move");
      }
      # removing new user from sophomorix.kill
#      print "sophomorix.kill: remove ", 
#            "${login} with identifier $auswahl_hash{$user_antwort}",
#            "\n";
#      &remove_line_from_file(
#          "$auswahl_hash{$user_antwort}::${login}",
#          "${DevelConf::ergebnis_pfad}/sophomorix.kill");
      print "========================================",
            "========================================\n\n";

      my $identifier_sys=$identifier;
      my $identifier_file=$auswahl_hash{$user_antwort},
      my $login=$schueler_im_system_loginname{$identifier_sys};
      my ($last,$first,$birth)=split(/;/,$identifier_file);
#      print "\nlogin: $login";
#      print "\nsys: $identifier_sys";
#      print "\nfile: $identifier_file";
#      print "\nlast: $last";
#      print "\nfirst: $first";
#      print "\nbirth: $birth\n\n";

      # updating the database
      &update_user_db_entry($login, 
                            "Name=$first",
                            "LastName=$last",
                            "Birthday=$birth",
                            "Status=E",
                            "TolerationDate=");
      # log
      &append_teach_in_log("man-teach-in",
                                $login,
                                $identifier_sys,
                                $identifier_file,
                               );
      # update the system
      $gecos="$first"." "."$last";
      &Sophomorix::SophomorixPgLdap::update_user_db_entry($login,
                      "Gecos=$gecos");

      # update the hashes
      if (exists $schueler_im_system_hash{$identifier_sys}){
        my $save_class=$schueler_im_system_hash{$identifier_sys};
        delete($schueler_im_system_hash{$identifier_sys});
        $schueler_im_system_hash{$identifier_file}="$save_class";
      }

      if (exists $schueler_im_system_loginname{$identifier_sys}){
        my $save_login=$schueler_im_system_loginname{$identifier_sys};
        delete($schueler_im_system_loginname{$identifier_sys});
        $schueler_im_system_loginname{$identifier_file}="$save_login";
      }
      if (exists $schueler_im_system_exit_admin_class{$identifier_sys}){
        my $save_exit=$schueler_im_system_exit_admin_class{$identifier_sys};
        delete($schueler_im_system_exit_admin_class{$identifier_sys});
        $schueler_im_system_exit_admin_class{$identifier_file}="$save_exit";
      }
   }
}


close(SOPHOMORIXIGNORE);



&log_script_end(@arguments);
