# This file is part of "KarmaLab.net Perl Library".
#    Copyright 2005, Yoran Brault
#    eMail: yoran.brault@bad_karma-lab.net (remove "bad_" and spaces
#    Site : http://artisan.karma-lab.net
#
# "KarmaLab.net Perl Library" is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of
# the License, or (at your option) any later version.
#
# "KarmaLab.net Perl Library" is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with "KarmaLab.net Perl Library"; if not, write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA, or see the FSF site: http://www.fsf.org.

package Systems::AbstractSystem;

use strict;
use warnings;

use Logging::LoggerManager;
use Term::ANSIColor qw(:constants);
use File::Basename;
use constant RUN_DONT_TRACE_ERROR   => 1;  # don't show any message when something go wrong
use constant RUN_VERBOSE_OUTPUT     => 2;  # show everything in console (bu also log)
use constant RUN_DONT_STOP_ON_ERROR => 4;  # stop execution of caller programm (exit) when an error occur
use constant RUN_PROGRESS_OUTPUT    => 8;  # use a one line progress output (need ANSO term)
use constant RUN_FREE_CONSOLE       => 16; # don't redirect any stream, let the child process output as it need (usefull for very verbose processes that you don't want to log)
use IO::Handle;
use Cwd;

use base "Exporter";
our @EXPORT = qw(RUN_DONT_TRACE_ERROR RUN_VERBOSE_OUTPUT RUN_PROGESS_OUTPUT RUN_DONT_STOP_ON_ERROR );

sub new {
    my $self = bless {
                       userExistsCommand       => "id %s",
                       isRootCommand           => "id -u",
                       createUserCommand       => "sudo useradd --create-home --shell /bin/bash --password toto %s",
                       checkBinaryCommand      => "/usr/bin/which %s",
                       checkConnectionCommand  => "wget http://www.google.fr -O /dev/null",
                       listAlternativesCommand => "sudo update-alternatives --list %s",
                       setAlternativeCommand   => "sudo update-alternatives --set %s %s",
                       nextRunOptions          => 0,
                       logFile                 => dirname($0) . ".log"
      },
      shift;

    return $self;
}

sub logger {
    my $self = shift;
    if ( !defined $self->{logger} ) {
        $self->{logger} = Logging::LoggerManager::Instance->logger(__PACKAGE__);
    }
    return $self->{logger};
}

sub logFile {
    my $self = shift;
    if (@_) {
        $self->{logFile} = shift;
    }
    return $self->{logFile};
}

sub nextRunOptions {
    my $self = shift;
    if (@_) {
        $self->{nextRunOptions} = $self->{nextRunOptions} | shift;
    }
    return $self->{nextRunOptions};
}

sub isRoot {
    my ($self) = @_;
    my $id = `$self->{isRootCommand}`;
    chomp($id);
    if ($id) {
        return 1;
    }
    else {
        return 0;
    }
}

sub userExists {
    my ( $self, $user ) = @_;
    $self->nextRunOptions(RUN_DONT_TRACE_ERROR);
    my $res = $self->run( sprintf( $self->{userExistsCommand}, $user ) );
    return $res;
}

sub createUser {
    my ( $self, $user ) = @_;
    return $self->run( sprintf( $self->{createUserCommand}, $user ) );
}

sub checkConnection {
    my ($self) = @_;
    my $res = $self->run( $self->{checkConnectionCommand} );
    return $res;
}

sub run {
    my ( $self, $command ) = @_;

    $self->logger->debug("Running command : $command");

    pipe( IN_STD, OUT_STD );
    pipe( IN_ERR, OUT_ERR );

    if ( my $pid = fork ) {
        close(OUT_STD);
        close(OUT_ERR);
        my $std;
        my $err;
        open( LOG_COMMAND, ">" . $self->{logFile} . "-last.log" );
        open( LOG,         ">>" . $self->{logFile} . ".log" );
        while ( defined( $std = <IN_STD> ) || defined( $err = <IN_ERR> ) ) {
            my $message;
            if ($std) { $message = $std; }
            else { $message = $err; }

            if ( ( $self->nextRunOptions & RUN_VERBOSE_OUTPUT ) == RUN_VERBOSE_OUTPUT ) {
                if ($std) {
                    $self->logger->trace( "--" . $std );
                }
                else {
                    $self->logger->severe( "--" . $err );
                }
            }
            else {
                if ( ( $self->nextRunOptions & RUN_PROGRESS_OUTPUT ) == RUN_PROGRESS_OUTPUT ) {
                    $self->logger->progress($message);
                }
            }
            print LOG $message;
            print LOG_COMMAND $message;
        }
        if ( ( $self->nextRunOptions & RUN_PROGRESS_OUTPUT ) == RUN_PROGRESS_OUTPUT ) {
            $self->logger->progressStop;
        }
        close(IN_STD);
        close(IN_ERR);
        close(LOG);
        close(LOG_COMMAND);
        waitpid( $pid, 0 );

        # We treate the exit code the perl way, low byte is the signal received to
        # end the process (0 most of the time). High byte is the actual command
        # exit code.
        my $exitCode = $?;

        # If we are in RUN_NO_TRACES mode, we don't want any output if command failed
        if ( ( ( $self->nextRunOptions & RUN_DONT_TRACE_ERROR ) != RUN_DONT_TRACE_ERROR ) && $exitCode != 0 ) {
            $self->logger->severe(
                "Error while running : $command
PWD:" . getcwd() . "
See " . $self->{logFile} . " for details."
            );
            if ( ( $self->nextRunOptions & RUN_DONT_STOP_ON_ERROR ) != RUN_DONT_STOP_ON_ERROR ) { exit($exitCode); }
        }

        # Reset running options for the next tme
        $self->{nextRunOptions} = 0;

        # That's all folks
        return $exitCode;
    }
    else {
        if ( !defined $pid ) {
            $self->logger->severe( "Unable to execute '%s'", $command );
            if ( ( $self->nextRunOptions & RUN_DONT_STOP_ON_ERROR ) != RUN_DONT_STOP_ON_ERROR ) { exit(1); }
            return -1;
        }
        close(IN_STD);
        close(IN_ERR);
        if ( ( $self->nextRunOptions & RUN_FREE_CONSOLE ) != RUN_FREE_CONSOLE ) {
            open( STDOUT, ">&OUT_STD" );
            open( STDERR, ">&OUT_ERR" );
        }    
        system($command);
        my $res = $?;
        close(OUT_STD);
        close(OUT_ERR);
        my $signal = $res & 0xff;
        exit( $res >> 8 );
    }
}

sub runProgress {
    my ($self) = shift;
    $self->nextRunOptions(RUN_PROGRESS_OUTPUT);
    return $self->run(@_);
}

sub runVerbose {
    my ($self) = shift;
    $self->nextRunOptions(RUN_VERBOSE_OUTPUT);
    return $self->run(@_);
}

sub runFree {
    my ($self) = shift;
    $self->nextRunOptions(RUN_FREE_CONSOLE);
    return $self->run(@_);
}


sub commandExists {
    my ( $self, $binary ) = @_;
    $self->nextRunOptions(RUN_DONT_TRACE_ERROR);
    return $self->run( sprintf( $self->{checkBinaryCommand}, $binary ) );
}

sub packageExists {
    my ( $self, $package ) = @_;
    $self->nextRunOptions(RUN_DONT_TRACE_ERROR);
    return $self->run( sprintf( $self->{checkPackageCommand}, $package ) );
}

sub hasAlternative {
    my ( $self, $group, $version ) = @_;
    my $command = sprintf( $self->{listAlternativesCommand}, $group );
    my @alternatives = `$command`;
    for my $alternative (@alternatives) {
        chop($alternative);
        if ( $alternative =~ /$version/ ) {
            return 0;
        }
    }
    return 1;
}

sub getAlternative {
    my ( $self, $group, $version ) = @_;
    my $command = sprintf( $self->{listAlternativesCommand}, $group );
    my @alternatives = `$command`;
    for my $alternative (@alternatives) {
        chop($alternative);
        if ( $alternative =~ /$version/ ) {
            return $alternative;
        }
    }
    return undef;
}

sub updateAlternative {
    my ( $self, $group, $version ) = @_;
    my $command = sprintf( $self->{listAlternativesCommand}, $group );
    my @alternatives = `$command`;
    for my $alternative (@alternatives) {
        chop($alternative);
        if ( $alternative =~ /$version/ ) {
            my $result = $self->run( sprintf( $self->{setAlternativeCommand}, $group, $alternative ) );
            return $result;
        }
    }
    return 1;
}

sub installPackage {
    my ( $self, $package ) = @_;
    return $self->run( sprintf( $self->{installPackageCommand}, $package ) );
}

sub createDir {
    my $self   = shift;
    my $folder = shift;
    $self->nextRunOptions(RUN_DONT_TRACE_ERROR);
    $self->run("mkdir -p $folder");
}
1;
