#!/usr/bin/perl -w
# -*- perl -*-
use P4CGI ;
use strict ;
#
#################################################################
#  CONFIGURATION INFORMATION 
#  All config info should be in P4CGI.pm
#
#################################################################
#
#  P4 file diff viewer
#  View diff between two files or two revisions
#
#################################################################

# Get file spec argument
my @files = split / /,P4CGI::cgi()->param("FSPC") ;
&P4CGI::bail("No file specified") unless @files > 0 ;

my @revs = split / /,P4CGI::cgi()->param("REV") ;
$files[0] =~ s/^([^\#]+)\#(\d+)/$1/ and do { $revs[0] = $2 ; } ;
&P4CGI::bail("No revision specified") unless @revs > 0 ;

my @modes = split / /,P4CGI::cgi()->param("ACT") ;
&P4CGI::bail("No mode specified") unless @modes > 0 ;

my @files2 = split / /,P4CGI::cgi()->param("FSPC2") ;
my @revs2 = split / /,P4CGI::cgi()->param("REV2") ;
if(defined $files2[0]) {
    $files2[0] =~ s/^([^\#]+)\#(\d+)/$1/ and do { $revs2[0] = $2 ; } ;
} ;

my $change = P4CGI::cgi()->param("CH") ;

my $n ;
for($n = 0; $n < @files ; $n++) {
    $files2[$n] = $files[$n] unless defined $files2[$n] ;
    $revs2[$n] = $revs[$n]-1 unless defined $revs2[$n] ;
}

if((@files != @revs) ||
   (@files != @files2) ||
   (@files != @revs2)) {
    &P4CGI::bail("Argument counts not correct") ;    
} ;

my $title ;

if(@files == 1) {
    if($files[0] eq $files2[0]) {
	if($revs[0] < $revs2[0]) {
	    my $r = $revs2[0] ;
	    $revs2[0] = $revs[0] ;
	    $revs[0] = $r ;
	}
	$title = "Diff<br><code>$files[0]</code><br>\#$revs2[0] -&gt; \#$revs[0] " ;
    }
    else {
	$title = "Diff between<br><code>$files[0]\#$revs[0]</code><br>and<br><code>$files2[0]\#$revs2[0]" ;
    }
}
else {
    $title = "Diffs for change $change" ;
}

print
    "",
    &P4CGI::start_page($title,"") ;

# Constants for the file diff display
my $MAXCONTEXT = 30;
my $NCONTEXT   = 10;


while(@files>0) {

    my $f1start= "<font color=blue>" ;
    my $f1end="</font>" ;
    my $f2start = "<font color=red><strike>" ;
    my $f2end = "</strike></font>" ;
    
    my $file   = shift @files ;
    my $file2  = shift @files2 ;
    my $rev    = shift @revs ;
    my $rev2   = shift @revs2 ;
    my $mode   = shift @modes ;

    if($file eq $file2) {
	if($rev < $rev2) {
	    my $r = $rev2 ;
	    $rev2 = $rev ;
	    $rev = $r ;
	}
    }
    else {
	$f2start = "<font color=green>" ;
	$f2end = "</font>" ;
    }

    print
	&P4CGI::start_table("width=100% align=center bgcolor=white"),
	&P4CGI::table_row({-align=>"center",
			   -text =>"<BIG>$f1start$file\#$rev$f1end<br>$f2start$file2\#$rev2$f2end</BIG>"}),
	&P4CGI::end_table(),
	"<pre>" ;
	
    local *P4 ;
    
    
    my $f1 = "$file#$rev";
    my $f2 = "$file2#$rev2";
    
    my $nchunk = 0;
    my @start ;
    my @dels ;
    my @adds ;
    my @lines ;    
    
    if ($mode ne 'add' && $mode ne 'delete' && $mode ne 'branch') {
	
	&P4CGI::p4call(*P4, "diff2 $f2 $f1");
	$_ = <P4>; 
	while (<P4>) {
	    
	    my ( $dels, $adds );
	    
	    /(\d+),?(\d*)([acd])(\d+),?(\d*)/;
	    my ( $la, $lb, $op, $ra, $rb ) = ($1,$2,$3,$4,$5) ;
	    
	    next unless $ra;
	    
	    if( !$lb ) { $lb = $op ne 'a' ? $la : $la - 1; }
	    if( !$rb ) { $rb = $op ne 'd' ? $ra : $ra - 1; }
	    
	    $start[ $nchunk ] = $op ne 'd' ? $ra : $ra + 1;
	    $dels[ $nchunk ] = $dels = $lb - $la + 1;
	    $adds[ $nchunk ] = $adds = $rb - $ra + 1;
	    $lines[ $nchunk ] = "";
	    
	    # deletes
	    
	    while( $dels-- ) {
		$_ = <P4>;
		s/^. //;
		$_ = &P4CGI::fixSpecChar($_) ;
		$lines[ $nchunk ] .= $_;
	    }
	    
	    # separator
	    
	    if ($op eq 'c') {	
		$_ = <P4>; 
	    }
	    
	    # adds
	    
	    while( $adds-- ) {
		$_ = <P4>;
	    }
	    
	    $nchunk++;
	}
	
	close P4;
    }
    
    # Now walk through the diff chunks, reading the current rev and
    # displaying it as necessary.
    
    my $curlin = 1;
    
    &P4CGI::p4call(*P4, "print -q $f1");
    
    my $n ;
    for( $n = 0; $n < $nchunk; $n++ )
    {
	# print up to this chunk.
	
	&catchup( *P4, $start[ $n ] - $curlin );
	
	# display deleted lines -- we saved these from the diff
	
	if( $dels[ $n ] )
	{
	    print "$f2start";
	    print $lines[ $n ];
	    print "$f2end";
	}
	
	# display added lines -- these are in the file stream.
	
	if( $adds[ $n ] )
	{
	    print "$f1start";
	    &display( *P4, $adds[ $n ] );
	    print "$f1end";
	}
	
	$curlin = $start[ $n ] + $adds[ $n ];
    }
    
    &catchup( *P4, 999999999 );
    
    close P4;

    print "</pre>" ;
}

print &P4CGI::end_page() ;


# Support for processing diff chunks.
#
# skip: skip lines in source file
# display: display lines in source file, handling funny chars 
# catchup: display & skip as necessary
#

sub skip {
	my ( $handle, $to ) = @_;

	while( $to > 0 && ( $_ = <$handle> ) ) {
	    $to--;
	}

	return $to;
}

sub display {
	my ( $handle, $to ) = @_;

	while( $to-- > 0 && ( $_ = <$handle> ) ) {
	    $_ = &P4CGI::fixSpecChar($_) ;
	    print $_;
	}
}

sub catchup {

	my ( $handle, $to ) = @_;

	if( $to > $MAXCONTEXT )
	{
	    my $skipped = $to - $NCONTEXT * 2;

	    &display( $handle, $NCONTEXT );
	    
	    $skipped -= &skip( $handle, $skipped );

	    print 
		"<hr><center><strong>",
		"$skipped lines skipped",
		"</strong></center><hr>\n" if( $skipped );

	    &display( $handle, $NCONTEXT );
	}
	else
	{
	    &display;
	}
}

#
# That's it folks
#