#!/usr/bin/perl -w

use strict;

my ( @TEMPLATES );
my ( @FILE_LIST );
my ( %DESC_TABLE );
my ( $filename, $rec, $cnt, $idx );
my ( $hdr, $answer );
my ( $B, $N, $clear, $home, $cols );
my ( $nEntries, $curidx, $fShowCounter, $nLine );

my $descfile = "desc.txt";

#
# Some screen attibutes, so we can make the screen look a bit better
#
$B     = `echo -e "smul\nbold" | tput -S`;    # Bold
$N     = `tput sgr0`;                         # Normal
$clear = "${N}" . `tput clear`;               # Clear Screen
$home  = "${N}" . `tput home`;                # Move cursor to top left corner
$cols  = `tput cols`;

$nEntries     = 0;
$fShowCounter = 0;

$hdr = "The Linux Terminal Server Project (http://www.LTSP.org)";


###############################################################################
#
# Function Declarations
#

sub clear {
    print "$clear";
    $nLine = 1;
}

sub home {
    print "$home";
    $nLine = 1;
}

sub newline {
    my $cnt = 1;
    if( ($#_ + 1 )> 0 ){
        $cnt = int($_[0]);
    }
    while( $cnt > 0 ){
        print "\n";
        $nLine++;
        $cnt--;
    }
}

sub header {
    my( $counter, $lenhdr, $lenctr, $gap );
   
    if( $fShowCounter ){ 

        $counter = sprintf("screen %d of %d",$curidx+1,$nEntries);

        $lenhdr = length($hdr);
        $lenctr = length($counter);
        $gap    = $cols - ( $lenhdr + $lenctr );

        printf( "%s%${gap}s%s", $hdr, "", $counter );
        newline();
    }
    else{
        printf "%s", $hdr;
        newline();
    }
}

# Function to wait for pressing any key by the user to go on
# (ok... were waiting for any keys and then pressing enter...
# but its enough if the user know that he should press enter...)
#
sub any_key {

    newline( 23 - $nLine );

    print "Press <${B}ENTER${N}> to go on, or '${B}C${N}' to cancel ";
    $answer = <STDIN>;
    chomp $answer;
    $answer =~ tr/[a-z]/[A-Z]/;
    newline();
    return $answer;
}

#
# Function to ask if the user is ready to perform the updates
#
sub ready_to_update {

    while( 1 ){

        home();
    
        newline( 23 - $nLine );

        print "Ready to apply the changes? ( ";
        print "${B}R${N}-Review, ${B}A${N}-Apply, ${B}C${N}-Cancel ) ";

        $answer = <STDIN>;
        chomp $answer;
        $answer =~ tr/[a-z]/[A-Z]/;

        if( $answer =~ /^A/ ){
            return "A";
        }
        elsif( $answer =~ /^R/ ){
            return "R";
        }
        elsif( $answer =~ /^C/ ){
            return "C";
        }
        else{
            print("Invalid answer!");
        }
    }
}

#
# Function for checking user input [yes|no]...
#
sub yes_no_cancel {
	
    my ($default, $done, $answer);
    $default=$_[0];
    $done="N";

    while ( $done eq "N" ) {

        if (!$default) { $default = "Y" }

        print "(${B}Y${N}es/";
        print "${B}N${N}o/";
        print "${B}B${N}ack/";
        print "${B}C${N}ancel) ";
        print "[$default] ";

        $answer = <STDIN>;
        chomp $answer;

        if (!$answer) { $answer = $default }

        $answer =~ tr/[a-z]/[A-Z]/; 

        if ($answer =~ /^Y/) {
            $done="Y";
            return "Y";
        } elsif ($answer =~ /^N/) { 
            $done="Y";
            return "N";
        } elsif ($answer =~ /^C/) { 
            return "C";
            $done="Y";
        } elsif ($answer =~ /^B/ ) { 
            return "B";
            $done="Y";
        } else {
            print ("Invalid answer!");
            newline();
        }
    }
}


#
# Function to display the splash screen at startup
#
sub display_splash {

    clear();

    print "$hdr";
    newline(3);

    print "    About to update important system files.  If you would like";
    newline();
    print "    to stop and review the changes that are about to be made,";
    newline();
    print "    you can cancel now and look at the replacement files that";
    newline();
    print "    are about to be installed.";
    newline();

}

#
# Function to display a list of the targets
#
sub display_list {
    #
    # Print a list of the configuration files/scripts with the info we found
    #
    clear();
    header();
    newline();
    print "The following files will be created/modified:";
    newline(2);

    for $idx ( 0 .. @TEMPLATES-1 ){
          my($currec);
          $currec = $TEMPLATES[$idx];
          printf " %-34.34s %-39.39s [%s]", $currec->{target},
                                            $currec->{description},
                                            $currec->{answer};
          newline();
    }
}

#
# Read in the template descriptions
#
sub load_descriptions {

    my( $cur_topic );

    open( DESC_FILE, "<$descfile" )
        or die ("Couldn't open description file $descfile\n$!\n\n" );

    $cur_topic = "";

    while( <DESC_FILE> ){
        chomp;
        $rec = $_;

        if( $rec =~ /^#/ ){       # Skip comment lines
            next;
        }

        if( $rec =~ /^\[[^\]]*\]/ ){    # Look for '[/etc/exports]' entries
            if( $cur_topic ){
                #
                # Remove trailing blank lines
                #
                $DESC_TABLE{$cur_topic} =~ s/\n*$//;
            }
            $cur_topic = $rec;
            $cur_topic =~ s/^.*\[//;
            $cur_topic =~ s/\].*$//;
            $DESC_TABLE{$cur_topic} = "";
            next;
        }
        else{
            if( $cur_topic ){
                $rec =~ s/\${B}/${B}/g;
                $rec =~ s/\${N}/${N}/g;
                $rec =~ s/ *$//g;
                $DESC_TABLE{$cur_topic} = $DESC_TABLE{$cur_topic} . $rec . "\n";
            }
        }
    }
    if( $cur_topic ){
        #
        # Remove trailing blank lines from the last entry
        #
        $DESC_TABLE{$cur_topic} =~ s/\n*$//;
    }
    close( DESC_FILE );
}

#
# Load the target information
#
sub load_target_info {

    #
    # Open the current directory, so we can get a list of
    # all the files in the directory.
    #
    opendir(TEMPLATE_DIR, ".")
        or die( "\n$!\n" );

    #
    # Get the list of template files in the directory
    #
    @FILE_LIST = grep( /.*\.tmpl$/, readdir TEMPLATE_DIR );

    foreach $filename ( sort(@FILE_LIST) ){

        open( CONF_FILE, "<$filename" )
            or die ("Couldn't open template file $filename\n$!\n\n" );

        my( $currec );
        $currec                 = {};
        $currec->{filename}     = $filename;
        $currec->{answer}       = "";
        $currec->{description}  = "";
        $currec->{target}       = "";
        $currec->{mode}         = "";
        $currec->{action}       = "";
        $currec->{desc_key}     = "";
        $currec->{interpreter}  = "";

        while( <CONF_FILE> ){

            chomp;             # Remove trailing newline
                if( s/\\$//) {
                $_ .= "^" . <CONF_FILE>;    # We use a carat '^' to separate
                                        # the records.
                redo;
            }

            $rec = $_;

            $rec =~ s/"//g;    # Remove quotes

            if( $rec eq "" ){
                next;          # Skip blank lines
            };

            if( $rec =~ /^:/ ){
                $currec->{interpreter} = "/bin/sh";
            }

            if( $rec =~ /^#!/ ){
                $currec->{interpreter} = substr($rec,2);
            }

            if( $rec =~ /^#*\s*DEFAULT\s*=\s*([YN])/ ) {
                if($currec->{answer} eq ""){
                    $currec->{answer} = $1;
                }
            }

            if( $rec =~ /^#?\s*DESCRIPTION\s*=\s*(.*)/ ) {
                if($currec->{description} eq ""){
                    $currec->{description} = $1;
                }
            }

            if( $rec =~ /^#?\s*DESC_KEY\s*=\s*(.*)/ ) {
                if($currec->{desc_key} eq ""){
                    $currec->{desc_key} = $1;
                }
            }

            if( $rec =~ /^#?\s*TARGET\s*=\s*(.*)/ ) {
                if( $currec->{target} eq "" ) {
                    $currec->{target} = $1
                }
            }

            if( $rec =~ /^#?\s*ACTION\s*=\s*((COPY,\d{4},\w+,\w+)|(BACKUP_EXECUTE)|(EXECUTE))/ ) {
                if( $currec->{action} eq "" ) {
                    $currec->{action} = $1
                }
            }
        }

        close( CONF_FILE );

        $currec->{mode} = ( -f $currec->{target} ) ? "update" : "create" ;

        if( $currec->{action}      eq "" ){
            $currec->{action}      = "EXECUTE";
        };

        if( $currec->{description} eq "" ){
            $currec->{description} = "No description available!";
        };

        if( $currec->{answer}      eq "" ){
            $currec->{answer}      = "Y";
        };

        if( $currec->{interpreter} eq "" ){
            $currec->{interpreter}  = "/bin/sh";
        }

        push @TEMPLATES, $currec;
    }
}

#
# Function to backup the new file...
#
sub backup_file {
    my( $filenum, $filename, $target, $source );
    $target   = $_[0];
    $source   = $_[1];
    $filename = $target;
    $filenum  = 0;

    #
    # Figure out the new name for the backup file
    #
    while( -f $filename ){
        $filename = $target . "." . ++$filenum;
    }

    #
    # Make the copy
    #
    if( -f $target ){
        print( "Backup of old file $target as $filename\n" );
        system( "cp $target $filename" ) == 0
            or die ( "backup of $target failed, Error: $!\n" );
    }
    return "done";
}

#
# Function to copy the new file...
#
sub copy_file {
    my( $target, $source );
    my( @FILE_PERM );

    $target = $_[0];
    $source = $_[1];

    @FILE_PERM = split( /,/, $_[2] );
    if( @FILE_PERM ne "4" ){
        die("Missing file permissions for target: $target\n\n" );
    }

    #
    # Copy the config file to the $target
    #
    open( SOURCE, "<$source" ) or die ( "\nCouldn't open source: $source: $!\n" );
    open( TARGET, ">$target" ) or die ( "\nCouldn't open target: $target: $!\n" );
    while( <SOURCE> ){
        chomp;
        if( $_ !~/^#?\s*(DEFAULT|DESCRIPTION|TARGET|ACTION|DESC_KEY).*/ ) {
            print TARGET "$_\n";
        }
    }
    close(SOURCE);
    close(TARGET);

    #
    # Set the user, group and permissions of the file.
    #
    system( "chmod $FILE_PERM[1] $target" ) == 0
        or die ("Could not change file permissions $FILE_PERM[1] for: $target, Error: $?\n" );

    system( "chown $FILE_PERM[2]:$FILE_PERM[3] $target" ) == 0
        or die ("Could not chagne owner:group $FILE_PERM[2]:$FILE_PERM[3] for: $target, Error: $?\n");
}

###############################################################################
#
# End of function declarations
#
###############################################################################


###############################################################################
#
# MAIN - Program starts here
#

#
# Clear the screen before we start
#

display_splash();

load_descriptions();

load_target_info();

$answer = any_key();
if( $answer eq "C" ){
    print("Cancelling...\n\n");
    exit;
}

my $finished        = 0;
my $first_time      = 1;
my $ready_to_update = 0;

while ( ! $ready_to_update ){

    display_list();

    $answer = ready_to_update();
    if( $answer eq "C" ){
        print("Cancelling...\n\n");
        exit;
    }
    if( $answer eq "A" ){
        $ready_to_update = "Y";
        next;
    }
    $first_time = 0;

    my( $beg, $end, $cur, $done );

    $beg    = 0;
    $end    = @TEMPLATES-1;
    $curidx = $beg;

    $nEntries = @TEMPLATES;

    $done = 0;

    $fShowCounter = 1;

    while( ! $done ){

        my( $currec, $line );
        $currec = $TEMPLATES[$curidx];

        clear();
        header();
        newline();

        print "  $currec->{target}  -  $currec->{description}";
        newline(2);

        if( exists $DESC_TABLE{$currec->{desc_key}} ){
            $line = $DESC_TABLE{$currec->{desc_key}};
            $line =~ s/^/  /gm;
            print "$line";
            my $newlines = $line;
            $newlines =~ s/[^\n]//g;
            $nLine += length($newlines);
            newline();
        }

        newline( 23 - $nLine );

        print "$currec->{mode} $currec->{target}? ";

        $answer = yes_no_cancel($currec->{answer});
        if( $answer eq "C" ){
            print "Cancelling...\n\n";
            $done = 1
        }
        elsif( $answer eq "B" ){
            $curidx = $curidx - 1;
        }
        else{
            $currec->{answer} = $answer;
            $curidx = $curidx + 1;
        }

        if( $curidx < 0 )   { $curidx  = 0; }
        if( $curidx > $end ){ $done = 1; }
    }

    $fShowCounter = 0;
}

if( $ready_to_update eq "Y" ){

    clear();
    $fShowCounter = 0;
    header();
    newline();

    print "Doing the update\n";

    for $idx ( 0 .. @TEMPLATES-1 ){
        my($currec);
        $currec = $TEMPLATES[$idx];
        if( $currec->{answer} eq "Y" ){
            print( "$currec->{filename}\n" );
            system("chmod +x $currec->{filename}") == 0
                or die ( "\nCould not chmod +x $currec->{filename}, Error: $?\n" );

            if( $currec->{action} eq "EXECUTE" ){
                system("$currec->{interpreter} ./$currec->{filename}") == 0
                    or die ( "\nCould not execute ./$currec->{filename}, Error: $?\n" );
            }
            elsif ($currec->{action} =~ /^BACKUP_EXECUTE/ ){
                #
                # Make a backup and execute the file
                #
                backup_file( "$currec->{target}", "$currec->{filename}" );
                system("$currec->{interpreter} ./$currec->{filename}") == 0
                    or die ( "\nCould not execute ./$currec->{filename}, Error: $?\n" );
            }
            elsif ( $currec->{action} =~ /^COPY/ ){
                #
                # Make a backup and execute the file
                #
                backup_file( "$currec->{target}", "$currec->{filename}" );

                copy_file( "$currec->{target}",
                           "$currec->{filename}",
                           "$currec->{action}" );
            }
        }
    }
}

newline();
