#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------

package Common::SVN;

#
# Common::SVN
#
# This class provides basic SVN support via the command line
# tool.  Currently this only works on a sign repository, but
# it can be extended to support multiple repositories later.
#

use strict;

use XML::Simple;

use lib '/app/tools/common/lib';
use Common::Assert;
use Common::Log;
use Common::Parser;

sub SVN     { 'svn' }
sub SVNREPO { 'http://svn.royaltyshare.com/repos/code' }

sub _requiredSVNVersion { '1.5.6' }

sub new {
    my $class = shift;

    my $self = {};
    bless $self, $class;

    return $self->_init(@_);
}

sub _init {
    my ( $self, %args ) = @_;

    return $self;
}

sub login {
    my $self = shift;

    Common::Log::Debug("Attempting to login.");
    my $cmd = " info " . $self->SVNREPO;
    return $self->run($cmd);
}

sub run {
    my $self = shift;
    my $cmd  = shift;
    my %args = @_;
    my $result;
    my $success;

    $self->_versionCheck();

    $cmd = $self->SVN . " $cmd";

    $cmd .= " 2>/dev/null" if ( $args{hide_errors} );

    Common::Log::Debug("CMD: $cmd");

    if ( open( OUT, "$cmd |" ) ) {
        while ( my $line = <OUT> ) {
            $result .= $line;
            print $line if ( $args{show_output} );
        }

        $success = 1;
    }

    $self->{_stdout} = $result;
    return $success;
}

# We want to make sure everyone is using the same version of subversion
sub _versionCheck {
    my $self = shift;

    return if ( $self->{_versionOK} );

    my ( $rMajor, $rMinor, $rRelease ) = split( /\./, $self->_requiredSVNVersion() );
    my ( $major, $minor, $release ) = $self->version();

    $self->{_versionOK} = ( $rMajor >= $major && $rMinor >= $minor && $rRelease >= $release );

    unless ( $self->{_versionOK} ) {
        print STDERR "ERROR: SVN Version Mismatch Required: "
          . join( '.', ( $rMajor, $rMinor, $rRelease ) )
          . " Client: "
          . join( '.', ( $major, $minor, $release ) ) . "\n";

        exit 1;
    }

    return $self->{_versionOK};
}

sub exists {
    my $self   = shift;
    my $source = shift || die "source required";
    my $repo   = $self->SVNREPO;
    my $url    = $source =~ /^$repo/ ? $source : $self->SVNREPO . "/$source";

    Common::Log::Debug("Exists: $url");
    $self->run( " info $url", hide_errors => 1 );

    return $self->stdout() && $self->stdout =~ /URL/ ? 1 : undef;
}

sub stdout {
    return shift->{_stdout};
}

sub comment {
    my $self = shift;
    my $comment = shift || die "comment required";
    return "Common::SVN  --  $comment";
}

sub cp {
    my $self      = shift;
    my $source    = shift || die "source required";
    my $dest      = shift || die "destination required";
    my $comment   = shift || die "comment required";
    my $sourceurl = $self->SVNREPO . "/$source";
    my $desturl   = $self->SVNREPO . "/$dest";

    Common::Log::Debug("SVN Copy: $source $dest");
    if ( $self->exists($dest) ) {
        print STDERR "ERROR: $dest already exists in the repository\n";
        return;
    }

    unless ( $self->exists($source) ) {
        print STDERR "ERROR: $source does not exist in the repository\n";
        return;
    }

    return $self->run( " cp -m '" . $self->comment($comment) . "' $sourceurl $desturl" );
}

sub version {
    my $self = shift;
    my $cmd  = $self->SVN;

    open( IN, "$cmd --version |" ) || die "Failed to launch SVN: $!";

    while ( my $line = <IN> ) {
        if ( $line =~ /version (\d+)\.(\d+)\.(\d+)/ ) {
            return ( $1, $2, $3 );
        }
    }

    return ( 0, 0, 0 );
}

sub info {
    my $self = shift;
    my $path = shift || "path required";

    return unless ( $self->run( "info --xml $path", hide_errors => 1 ) );

    my $xs  = XML::Simple->new();
    my $ref = $xs->XMLin( $self->stdout );

    return $ref;
}

sub rm {
    my $self    = shift;
    my $source  = shift || die "source required";
    my $comment = shift || die "comment required";
    my $url     = $self->SVNREPO . "/$source";

    Common::Log::Debug("SVN Remove: $source");
    unless ( $self->exists($url) ) {
        print STDERR "ERROR: $source does not exist in the repository\n";
        return;
    }

    return $self->run( " rm -m '" . $self->comment($comment) . "' $url" );
}

sub ls {
    my $self   = shift;
    my $source = shift || die "source required";
    my $url    = $self->SVNREPO . "/$source";

    Common::Log::Debug("SVN LS: $source");
    unless ( $self->exists($url) ) {
        print STDERR "ERROR: $source does not exist in the repository\n";
        return;
    }

    $self->run(" ls $url");

    my @lines = split( /\n/, $self->stdout );

    return @lines;
}

sub mkdir {
    my $self    = shift;
    my $source  = shift || die "source required";
    my $comment = shift || die "comment required";
    my $url     = $self->SVNREPO . "/$source";

    Common::Log::Debug("SVN mkdir: $source");
    return if ( $self->exists($url) );

    return $self->run( " mkdir -m '" . $self->comment($comment) . "' $url" );
}

sub merge {
    my $self        = shift;
    my $source      = shift || die "source required";
    my $destination = shift || die "destination required";
    my $url         = $self->SVNREPO . "/$source";

    my $info        = $self->info($destination);
    my $localBranch = $info->{entry}->{url};
    my $root        = $info->{entry}->{repository}->{root};
    $localBranch =~ s/$root\///;

    my $merge_options = "";

    assert( $destination !~ /^http/ );

    die "$url does not exist" unless ( $self->exists($url) );

    chdir($destination) || die "Could not change to '$destination'";

    die "'$destination' is not up to date.  please run 'svn update'" unless ( $self->up2date('.') );

    $merge_options = " --reintegrate " if ( $localBranch eq 'trunk' );

    return $self->run( " merge $url", show_output => 1 );
}

sub checkout {
    my $self        = shift;
    my $source      = shift || die "source required";
    my $destination = shift || "";
    my $url         = $self->SVNREPO . "/$source";

    assert( $destination !~ /^http/ );
    die "$url does not exist" unless ( $self->exists($url) );

    return $self->run(" co $url $destination");
}

sub up2date {
    my $self = shift;
    my $path = shift || die "path required";

    return unless ( $self->run("status $path") );

    return $self->stdout() ? undef : 1;
}

###
1;    # Play nicely.
###
