#!/usr/bin/perl -w

# $Id: dasscm 205 2007-07-02 15:15:47Z joergs $

use strict;

use Env qw($DASSCM_PROD $DASSCM_REPO $USER $DASSCM_USERNAME $DASSCM_USER $DASSCM_PASSWORD);
use Cwd;
use Term::ReadKey;
use File::Basename;
use File::stat;
use File::Path;
use File::Copy;
use Getopt::Long;

#
# used ConfigFile instead of SmartClient::Config,
# because the huge amount of SmartClient dependencies
#use SmartClient::Config;
use ConfigFile;

#####################################################################
#
# global
#

my $config_file = "/etc/dasscm.conf";

# my $config = SmartClient::Config->( $config_file );
my $config = ConfigFile::read_config_file($config_file);
my $DASSCM_LOCAL_REPOSITORY_BASE;
my $DASSCM_REPOSITORY_NAME;
my $DASSCM_SVN_REPOSITORY;

my $SVN                    = "svn ";
my $svnOptions             = "";
my $svnCheckoutCredentials = "";
my $svnPasswordCredentials = "";

# command line options get stored in options hash
my %options = ();

# subcommand, that gets executed (add, commit, ...)
my $command;

my $verbose = 0;

#####################################################################
#
# util functions
#
sub usage()
{
    print "usage: dasscm <subcommand> [options] [args]\n";
    print "\n";
    print "dasscm is intended to help versioning configuration files\n";
    print "\n";
    print "Available subcommands:\n";
    print "   init\n";
    print "   login\n";
    print "   add <filename>\n";
    print "   commit <filename>\n";
    print "   diff <filename>\n";
    print "   help <subcommand>\n";
    print "\n";
    print "preperation:\n";
    print "check out the configuration repository, e.g.\n";
    print
      "svn checkout --no-auth-cache --username USERNAME https://dass-it.de/svn/dasscm/HOSTNAME\n";
    print
      "environment variables\n",
      "    DASSCM_REPO\n", 
      "    DASSCM_PROD¸n",
      "    DASSCM_USERNAME\n",
      "    DASSCM_PASSWORD\n",
      "are evaluated.\n";
    print "\n";
}

sub check_env()
{

    # DASSCM_PROD
    if ( !$DASSCM_PROD ) {
        $DASSCM_PROD = "/";
    }

    if ( !-d $DASSCM_PROD ) {
        die "DASSCM_PROD ($DASSCM_PROD) is not set to a directory.\n";
    }
    if ($verbose) { print "DASSCM_PROD: " . $DASSCM_PROD . "\n"; }


    # DASSCM_REPOSITORY_NAME
        if ( !$DASSCM_REPOSITORY_NAME ) {
            die
              "Variable DASSCM_REPOSITORY_NAME is not defined.\nIt needs to be a unique name.\nNormally the full qualified host name is used.\nUse file $config_file to configure it.\n";
        }


    # DASSCM_REPO
    if ( !$DASSCM_REPO ) {
        if ( $DASSCM_LOCAL_REPOSITORY_BASE && $DASSCM_REPOSITORY_NAME ) {
            $DASSCM_REPO =
              $DASSCM_LOCAL_REPOSITORY_BASE . "/" . $DASSCM_REPOSITORY_NAME;
        } else {
            die
              "Envirnonment variable DASSCM_REPO not set.\nSet DASSCM_REPO to the directory of the versioning system checkout for this machine.\n";
        }
    }
    print "DASSCM_REPO: " . $DASSCM_REPO . "\n";

    #
    # check if local repository directory exist (if not creating by init)
    #
    if ( $command ne "init" ) {
        if ( not -d $DASSCM_REPO ) {
            die
            "Can't access local repository DASSCM_REPO\n($DASSCM_REPO)\nCheck configuration and execute\n    dasscm init\n";
        }
    
        #
        # user settings
        #
        
        # DASSCM_USER is legacy. Use DASSCM_USERNAME instead
        if( !$DASSCM_USERNAME ) {
            $DASSCM_USERNAME=$DASSCM_USER;
        }

        # user root is not allowed for checkins.
        # if user is root, DASSCM_USER has to be set,
        # otherwise USER can be used
        if ( "$USER" eq "root" ) {
            if (( not $DASSCM_USERNAME ) and ( $command ne "login" )) {
                die
                "Envirnonment variable DASSCM_USERNAME not set.\nSet DASSCM_USERNAME to your subversion user account.\n";
            }
            $svnOptions .= " --no-auth-cache ";
        } elsif ( !$DASSCM_USERNAME ) {
            $DASSCM_USERNAME = $USER;
        }

        #
        # password
        #
        if( $DASSCM_PASSWORD ) {
            $svnPasswordCredentials = " --password $DASSCM_PASSWORD ";
        }
    }

    #$svnOptions .= " --username $DASSCM_USERNAME "
}

sub check_parameter(@)
{
}

sub get_filenames(@)
{
    my $filename_prod = $_[0];
    if ( !( $filename_prod =~ m/^\// ) ) {
        $filename_prod = cwd() . "/" . $filename_prod;
    }

    -r $filename_prod or die "$filename_prod is not accessable";

    # TODO: dirname buggy: eg. "/etc/" is reduced to "/",
    #	"/etc" is used as filename
    my $dirname_prod = dirname($filename_prod);
    chdir $dirname_prod or die $!;
    $dirname_prod = cwd();
    my $basename = basename($filename_prod);

    print "dir: " . $dirname_prod . "\n";
    print "fn: " . $basename . "\n";

    my $dirname_repo  = $DASSCM_REPO . "/" . $dirname_prod;
    my $filename_repo = "$dirname_repo/$basename";

    return (
        $basename,      $dirname_prod, $dirname_repo,
        $filename_prod, $filename_repo
    );
}

sub run_command
{
    my $command = shift;

    #print "executing command: " . $command . "\n";

    open( RESULT, $command . ' 2>&1 |' );
    my @result = <RESULT>;
    close(RESULT);
    my $retcode = $? >> 8;

    #print @result;
    #if( $retcode ) { print "return code: " . $retcode . "\n"; }

    return ( $retcode, @result );
}

sub run_interactive
{

    if( $verbose ) { 
        print "run_interactive:" . join( " ", @_ ) . "\n";
    }

    system(@_);
    if ( $? == -1 ) {
        printf "failed to execute: $!\n";
    } elsif ( $? & 127 ) {
        printf "child died with signal %d, %s coredump\n", ( $? & 127 ),
          ( $? & 128 ) ? 'with' : 'without';
    } elsif ( $? >> 8 != 0 ) {
        printf "child exited with value %d\n", $? >> 8;
    }
    return ( $? >> 8 );
}

sub svn_check_credentials( $$ )
{
    my $username = shift;
    my $password = shift;

    ( my $rc_update, my @result ) =
      run_command(
        "$SVN info --non-interactive --no-auth-cache --username $username --password $password $DASSCM_SVN_REPOSITORY"
      );

    print @result;

    if ( $rc_update != 0 ) {
        print @result;
        die;
    }

}

sub svn_update( ;$ )
{
    my $update_path = shift || $DASSCM_REPO;
    ( my $rc_update, my @result ) =
      run_command("$SVN update $svnCheckoutCredentials $update_path");
    if ( $rc_update != 0 ) {
        print @result;
        die;
    }

    print @result;

}

#####################################################################
#
# functions

sub help(;@)
{
    if ( @_ == 0 ) {
        usage();
    } else {
        print "help for @_: ...\n";
    }
}

sub login(@)
{
    check_parameter( @_, 1 );
    check_env();

    my $output_username = "";
    if ($DASSCM_USERNAME) {
        $output_username = " ($DASSCM_USERNAME)";
    }

    print "Enter DASSCM user name", $output_username, ": ";
    my $input_username = <STDIN>;
    chomp($input_username);

    # hidden password input
    print "Enter DASSCM user password: ";
    ReadMode('noecho');
    my $input_password = <STDIN>;
    ReadMode('normal');
    chomp($input_password);

    svn_check_credentials( $input_username, $input_password );

    #
    # set environment variables
    #
    $ENV{'DASSCM_USERNAME'} = $input_username;
    $ENV{'DASSCM_PASSWORD'} = $input_password;

    # TODO: print environment

    exec("bash") or die "can't login";
}



sub init(@)
{
    check_parameter( @_, 1 );
    check_env();

    # update complete repository
    my $retcode = run_interactive("cd $DASSCM_LOCAL_REPOSITORY_BASE; $SVN checkout $svnCheckoutCredentials $svnOptions $DASSCM_SVN_REPOSITORY");
}


#
# add (is used for command add and commit)
#
sub add(@)
{
    check_parameter( @_, 1 );
    check_env();

    (
        my $basename,
        my $dirname_prod,
        my $dirname_repo,
        my $filename_prod,
        my $filename_repo
      )
      = get_filenames( $_[0] );

    if ( $command eq "add" ) {
        mkpath($dirname_repo);
    }

    # update complete repository
    my $retcode = run_interactive("$SVN update $svnOptions $DASSCM_REPO");

    copy( $filename_prod, $filename_repo ) or die $!;

    if ( $command eq "add" ) {

        # already checked in?
        chdir($DASSCM_REPO);

        # also add the path to filename.
        for my $dir ( split( '/', $dirname_prod ) ) {
            if ($dir) {
                run_command("$SVN add --non-recursive $dir");
                chdir $dir;
            }
        }
        run_command("$SVN add $basename");
    }

    if ( $options{'message'} ) {
        $svnOptions .= " --message \"$options{'message'}\" ";
    }

    # commit calls $EDITOR. uses "interactive" here, to display output
    $retcode =
      run_interactive(
        "$SVN commit $svnOptions --username $DASSCM_USERNAME $svnPasswordCredentials $DASSCM_REPO");

    print $filename_prod. "\n";
    print $dirname_repo. "\n";
}

sub blame(@)
{
    check_parameter( @_, 1 );
    check_env();

    (
        my $basename,
        my $dirname_prod,
        my $dirname_repo,
        my $filename_prod,
        my $filename_repo
      )
      = get_filenames( $_[0] );

    my $retcode = run_interactive("$SVN blame $svnOptions $filename_repo");
}

sub diff(@)
{
    check_parameter( @_, 1 );
    check_env();

    (
        my $basename,
        my $dirname_prod,
        my $dirname_repo,
        my $filename_prod,
        my $filename_repo
      )
      = get_filenames( $_[0] );

    #print "$basename,$dirname_prod,$dirname_repo\n";

    ( my $rc_update, my @result ) = run_command("$SVN update $filename_repo");
    if ( $rc_update != 0 ) {
        print @result;
        die;
    }

    ( my $rc_diff, my @diff ) =
      run_command("diff $filename_repo $filename_prod");
    print @diff;
}

#####################################################################
#
# main
#

my $number_arguments = @ARGV;

if ( $number_arguments > 0 ) {

    # get subcommand and remove it from @ARGV
    $command = $ARGV[0];
    shift @ARGV;

    $DASSCM_LOCAL_REPOSITORY_BASE = $config->{'DASSCM_LOCAL_REPOSITORY_BASE'};
    $DASSCM_REPOSITORY_NAME       = $config->{'DASSCM_REPOSITORY_NAME'};

    # TODO: check variables
    $DASSCM_SVN_REPOSITORY =
      $config->{'DASSCM_SVN_REPOSITORY_BASE'} . "/" . $DASSCM_REPOSITORY_NAME;

    my $DASSCM_CHECKOUT_USERNAME = $config->{'DASSCM_CHECKOUT_USERNAME'};
    my $DASSCM_CHECKOUT_PASSWORD = $config->{'DASSCM_CHECKOUT_PASSWORD'};

    #
    # if a user is given by dasscm configuration file, we use it.
    # Otherwise we expect that read-only account is configured
    # as local subversion configuration.
    # If this is also not the case,
    # user is required to type username and password.
    # This will be stored as local subversion configuration thereafter.
    #
    if ( $DASSCM_CHECKOUT_USERNAME && $DASSCM_CHECKOUT_PASSWORD ) {
        $svnCheckoutCredentials =
          " --username $DASSCM_CHECKOUT_USERNAME --password $DASSCM_CHECKOUT_PASSWORD ";
    }

    # get command line options and store them in options hash
    my $result = GetOptions( \%options, 'message=s' );

    # print options
    foreach my $option ( keys %options ) {
        print $option. ": " . $options{$option} . "\n";
    }

    $_ = $command;
    if (m/help/i) {
        help(@ARGV);
    } elsif (m/login/i) {
        $command ="login";
        login(@ARGV);
    } elsif (m/init/i) {
        $command ="init";
        init(@ARGV);
    } elsif (m/add/i) {
        ## rewrite command
        $command = "add";
        add(@ARGV);
    } elsif (m/commit/i) {
        $command = "commit";
        add(@ARGV);
    } elsif (m/blame/i) {
        $command ="blame";
        blame(@ARGV);
    } elsif (m/diff/i) {
        $command ="diff";
        diff(@ARGV);
    } elsif (m/activate/i) {
        ## TODO
        activate(@ARGV);
    } else {
        usage();
        check_env();
    }

    # login
    # up
    # commitall
    # revert
    # status (chkconf)
}
