#!/usr/bin/perl
#

use strict;

use FindBin qw($RealBin);

use lib "$RealBin";
use util;

use POSIX;
use Data::Dumper;
use File::stat;
use Carp;

my $SVN_REPO="file:///home/svn/samba";
my $SVN_BRANCH="branches/SAMBA_4_0/";
my $SVN_PATH="$SVN_REPO/$SVN_BRANCH";
my $PATCH_PATH="$ENV{HOME}/svnmirror/samba4-sync.patches";
my $AUTHORS_PATH="$ENV{HOME}/svnmirror/samba4-sync.scripts/svn-authors";
my $GIT_PATH="$ENV{HOME}/svnmirror/samba4-sync.git";

my $LAST_SVN_REV_PATH = "$PATCH_PATH/latest.rev";

$ENV{LANG} = "en_US.UTF-8";

sub get_last_svn_rev()
{
	my $v = util::FileLoad($LAST_SVN_REV_PATH);
	my $def = 25600;

	$v = $def if $v eq "";
	$v *= 1;
	$v = $def if $v == 0;

	print "get_last_svn_rev: $v\n";
	return $v;
}

sub set_last_svn_rev($)
{
	my ($r) = @_;
	my $v = $r->{svnrev};
	util::FileSave($LAST_SVN_REV_PATH, $v);
	print "set_last_svn_rev: $v\n";
}

sub get_cur_svn_rev()
{
	my $def = 25650;
	my $infocmd = "svn info --non-interactive $SVN_PATH";
	my $info = `$infocmd` || confess "$infocmd: failed";

	my $v = $info;
	if ($v =~ /\nRevision: (\d+)/) {
		$v = $1;
	}
	$v = $def if $v eq $info;
	$v *= 1;
	$v = $def if $v == 0;

	print "get_cur_svn_rev: $v\n";
	return $v;
}

sub load_authors()
{
	my $f = util::FileLoad($AUTHORS_PATH);
	my @lines = split("\n", $f);
	my $authors = undef;

	foreach my $l (@lines) {
		if ($l =~ /^([\w\-]+) = (.*)$/) {
			$authors->{$1} = $2;
			next;
		}

		confess "line: $l: invalid";
	}

	return $authors;
}

sub fix_author($)
{
	my ($in) = @_;
	my $authors = undef;

	$authors = load_authors() unless defined($authors);

	my $out = $authors->{$in};

	confess "author: $in:not found" unless defined($out);

	return $out;
}

sub fix_log($)
{
	my ($in) = @_;
	my $delim = "------------------------------------------------------------------------";
	my @tmp1 = split($delim, $in);
	my @tmp2 = split("\n", $tmp1[0]);

	while (1){
		my $l = $tmp2[0];
		last unless $l =~ /^[ \t]*$/;
		shift(@tmp2);
	}

	my $out = join("\n", @tmp2);

	return $out;
}

sub fix_date($)
{
	my ($in) = @_;

	return $in;
}

sub is_dir($)
{
	my ($in) = @_;
	return 1 if $in eq ".";
	return 1 if $in =~ /\/$/;
	return 0;
}

sub construct_chunk($)
{
	my ($chunk) = @_;
	my @content =  @{$chunk->{content}->{lines}};

	my @hdr = splice(@content, 0, 5);

	confess "hdr[0]" unless $hdr[0] =~ /^Index: /;
	confess "hdr[1]" unless $hdr[1] =~ /^[=]+$/;
	confess "hdr[2]" unless $hdr[2] =~ /^--- /;
	confess "hdr[3]" unless $hdr[3] =~ /^\+\+\+ /;
	confess "hdr[4]" unless $hdr[4] =~ /^\@@ /;

	$hdr[0] =~ s/^Index: ([^\n]+)$/diff --git a\/$1 b\/$1/g;
	$hdr[2] =~ s/--- ([^\(]+)\(revision 0\)/--- \/dev\/null/g;
	$hdr[2] =~ s/--- ([^\(]+)\([^\n]+/--- a\/$1/g;
	$hdr[3] =~ s/\+\+\+ ([^\(]+)\(revision 0\)/+++ \/dev\/null/g;
	$hdr[3] =~ s/\+\+\+ ([^\(]+)\([^\n]+/+++ b\/$1/g;

	if ($hdr[4] =~ /\@\@ -\d+,\d+ \+0,0 \@\@/) {
		$hdr[3] = "+++ /dev/null";
	}

	my $oldsha1 = "1234567";
	my $newsha1 = "7654321";
	my $mode = "";

	if (grep(/svn:executable/, @{$chunk->{properties}->{lines}})) {
		print join("\n", @{$chunk->{properties}->{lines}});
		$mode = "new file mode 100755\n";
	}

	if ($hdr[2] =~ /\/dev\/null/) {
		$oldsha1 = "0000000";
		$mode = "new file mode 100644\n" if $mode eq "";
	}

	if ($hdr[3] =~ /\/dev\/null/) {
		$newsha1 = "0000000";
		$mode = "deleted file mode 100644\n";
	}

	$hdr[1] = $mode."index $oldsha1..$newsha1";

	splice(@content, 0, 0, @hdr);

	my $out = join("\n", @content);

	return $out;
}

sub strip_svn_properties($)
{
	my ($in) = @_;
	my @in = split("\n", $in);

	my $chunk = undef;
	my $chunks = undef;
	foreach my $l (@in) {
		if ($l =~ /^Index: ([\w\.\_\-\/]+)/) {
			#print "content: $1\n";

			$chunk = undef;
			$chunk->{type} = "content";
			$chunk->{file} = $1;
			confess "$1 content exists" if defined($chunks->{$1}) and defined($chunks->{$1}->{content});
			$chunks->{$1}->{content} = $chunk;
		} elsif ($l =~ /^Property changes on: ([\w\.\_\-\/]+)/) {
			#print "properties(".is_dir($1)."): $1\n";

			$chunk = undef;
			$chunk->{type} = "property";
			$chunk->{path} = $1;
			confess "$1 properties exists" if defined($chunks->{$1}) and defined($chunks->{$1}->{properties});
			$chunks->{$1}->{properties} = $chunk unless is_dir($1);
		}

		push(@{$chunk->{lines}}, $l);
	}

	#print Data::Dumper::Dumper($chunks);

	my @out = ();
	foreach $chunk (values %{$chunks}) {
		#print Data::Dumper::Dumper($chunk);
		my $v = construct_chunk($chunk);
		push(@out, $v);
	}

	my $out = join("\n", @out);
	return undef if $out eq "";

	return $out;
}

sub fix_diff($)
{
	my ($in) = @_;
	my $out = $in;

	confess("binary diff") if ($in =~ /Cannot display: file marked as a binary type/);
	return undef unless ($in =~ /^Index: [\w\.\_\-\/]+/);

	$out = strip_svn_properties($in);

	return $out;
}

sub get_new_svn_revs($$)
{
	my ($lastrev, $currev) = @_;
	my $nextrev = $lastrev + 1;
	return undef if $nextrev > $currev;
	my $logcmd = "svn log --non-interactive -r $nextrev:$currev $SVN_PATH";
	my $log = `$logcmd` || confess "$logcmd: failed";
	my $revs = undef;

	while($log =~ /\nr(\d+) \| (\w+) \| ([^\|]+) \|.*?line(s?)\n(.*)$/s) {
		$log = $5;
		$revs->{$1}->{svnrev} = $1;
		$revs->{$1}->{author} = fix_author($2);
		$revs->{$1}->{log}    = fix_log($5);
		$revs->{$1}->{date}   = fix_date($3);
	}

	return $revs;
}

sub get_svn_diff($)
{
	my ($rev) = @_;
	my $orev = $rev - 1;
	my $diffcmd = "svn diff --non-interactive -r $orev:$rev $SVN_PATH";
	my $diff = `$diffcmd` || confess "$diffcmd: failed";

	return fix_diff($diff);	
}

sub generate_patch($$)
{
	my ($r, $diff) = @_;
	my @log = split("\n", $r->{log});
	my $subject = shift @log;
	my $body = join("\n", @log);
	my $p = "";

	$p .= "From 123456780abcdef\n";
	$p .= "From: $r->{author}\n";
	$p .= "Date: $r->{date}\n";
	$p .= "Subject: [PATCH] r$r->{svnrev}: $subject\n";
	$p .= "$body\n";
	$p .= "\n";
	$p .= $diff;
	$p .= "\n---\nsvn-sync script\n\n";

	return $p;
}

sub apply_patch($)
{
	my ($r) = @_;
	my $applycmd = "cd $GIT_PATH && git am --whitespace=nowarn --binary $r->{patch_path}";

	my $apply = `$applycmd` || confess "$applycmd: failed";
}

sub store_patches($)
{
	my ($revs) = @_;

	foreach my $rev (sort keys %{$revs}) {
		my $r = $revs->{$rev};
		$r->{patch_path} = "$PATCH_PATH/$r->{svnrev}.patch";
		my $diff = get_svn_diff($r->{svnrev});
		next unless defined($diff);
		my $patch = generate_patch($r, $diff);
		#print $patch;
		util::FileSave($r->{patch_path}, $patch);
		apply_patch($r);
		set_last_svn_rev($r);
	}
}

sub apply_patches($)
{
	my ($revs) = @_;

	
}

my $lastrev = get_last_svn_rev();
my $currev = get_cur_svn_rev();

my $revs = get_new_svn_revs($lastrev, $currev);
store_patches($revs);
#print Data::Dumper::Dumper($revs);

