#!/usr/bin/perl -w

use 5.006;
use strict;
use warnings;

use Pod::Usage;
use Getopt::Long;
use Gear::Rules 0.06;
use RPM::Source::Editor;
use String::ShellQuote;

my $verbose_default=1;
my $verbose=$verbose_default;
my $opt_sourcenum=0;
my ($opt_version, $opt_tarball, $help, @opt_hook);
my $script_prefix='__~.uupdate-step-';
my $opt_destdir='.git/uupdate';
my ($opt_unpack,$opt_merge,$opt_spec, $opt_force_unpack);
# not yet opt
my $opt_git_interactive_merge=0;

my $options_result = 
    GetOptions (
	"h|help"   => \$help,
	"destdir=s" => \$opt_destdir,
	"force-unpack" => \$opt_force_unpack,
	"hook=s" => \@opt_hook,
	"source=i" => \$opt_sourcenum,
	"upstream-version=s" => \$opt_version,
	"merge" => \$opt_merge,
	"prefix" => \$script_prefix,
	"update-spec" => \$opt_spec,
	"unpack" => \$opt_unpack,
	"verbose+"  => \$verbose,
	"quiet"  => sub {$verbose=0},
    );

sub exit_usage {
    #exec "pod2usage --exit=0 $0";
    pod2usage({ #-message => "the options below are package-specific:" ,
	-exitval => 0  ,
	-verbose => $verbose ? $verbose - $verbose_default : 0,
	#-output  => $filehandle
	      } );
}

my $partial_mode= $opt_merge || $opt_unpack || $opt_spec;
my ($do_unpack,$do_merge,$do_spec)=(1,1,1);
($do_unpack,$do_merge,$do_spec)=(0,0,0) if $partial_mode;
$do_unpack =1 if $opt_unpack;
$do_merge =1 if $opt_merge;
$do_spec =1 if $opt_spec;

if ($help or not @ARGV or @ARGV>2 or not defined $opt_version and @ARGV==1) {
    &exit_usage();
}

$opt_tarball = shift @ARGV;
$opt_version = shift @ARGV if not defined $opt_version;

my $rules=Gear::Rules->new();
my $specfile=$rules->get_spec();
my $spec=RPM::Source::Editor->new(
    SPECFILE=>$specfile,
    );

# TODO: using private internals !!! :(
# like get_tag
my $source0=$spec->macros->macro_subst($spec->raw_sourcelist_ref->[$opt_sourcenum]);
die "can't find Source$opt_sourcenum\n" unless $source0;

my $source0rule=$rules->match_srpm_file($source0,$specfile);
die "can't find a matching rule for Source$opt_sourcenum: $source0\n" unless $source0rule;
my ($pkgname,$pkgversion,$pkgrelease)=$rules->get_nvr();

if ($rules->__has_external_commits()) {
    die "Commits from external repository found.
The tarball update seems to be not the right method of repository update.
If you insist, use --force-unpack option\n" unless $opt_force_unpack;
    warn "Commits from external repository found, Tarball update will mess with them.\n";
}

my $layout_analysis=$rules->__is_gear_layout_not_robot_recognizeable($source0rule);
die "Gear layout is not ready for automatic update: $layout_analysis\n" if ($layout_analysis);

unlink glob ($script_prefix."*");

my @diffs=$rules->sorted_diff_rules();
my $current_git_branch=&Gear::Git::Helper::get_current_git_branch();
$current_git_branch||=&Gear::Git::Helper::get_git_commit('HEAD');
my $default_upstream_branch_name='upstream';

my %TAG=map{$_=>1} &Gear::Git::Helper::ls_git_tags();
my $BRANCH=&Gear::Git::Helper::get_git_branch_commit_hash();
my $fh;
my @NEW_NVR=($pkgname, $opt_version,'alt1');
my $script_count=0;

my @MASTER_MERGE_DEFAULT;
my @MASTER_MERGE_OURS;
my ($source0_git_path) = $source0rule->git_paths();
my $source0tree=$source0rule->{-tree};

&print_upstream_update() if $do_unpack;

if ($do_merge and @diffs) {
    foreach my $diff (@diffs) {
	my @git_paths=$diff->git_paths();
	if ($diff->{-tree2} eq '.') {
	    # master branch; we will checkout it later...
	    push @MASTER_MERGE_DEFAULT, &get_tag_new_nvr($git_paths[0]);
	} else {
	    &new_uupdate_script('merge2diff');
	    print $fh "#!/bin/sh -ve\n";
	    print '# diff rule at line ', $diff->linenum, ': ',$diff->rulestring(),"\n";
	    &git_checkout_old_nvr($fh, $git_paths[1], 'diff_rule_line_'.$diff->linenum);
	    print $fh 'git pull . ',&get_tag_new_nvr($git_paths[0]),"\n";
	    &git_create_tag($fh, $git_paths[1]);
	    push @MASTER_MERGE_DEFAULT, &get_tag_new_nvr($git_paths[1]);
	}
    }
}

if ($source0tree ne '.') {
    my $merge_tag=&get_tag_new_nvr($source0_git_path);
    if (not grep {$_ eq $merge_tag} @MASTER_MERGE_DEFAULT,@MASTER_MERGE_OURS) {
	my $merge_strategy=$rules->__guess_merge_strategy($source0_git_path->{'commit'},'HEAD');
	if (not $merge_strategy) {
	    push @MASTER_MERGE_DEFAULT, $merge_tag;
	} elsif ($merge_strategy eq 'ours') {
	    push @MASTER_MERGE_OURS, $merge_tag;
	} else {
	    die "Oops: merge strategy $merge_strategy is not implemented. Please, report.";
	}
    }
}

&print_master_merge() if $do_merge and (@MASTER_MERGE_DEFAULT or @MASTER_MERGE_OURS);
&print_update_spec() if $do_spec;

sub print_upstream_update {
    &new_uupdate_script('update-upstream');
    print $fh '#!/bin/sh -ve',"\n";
    print $fh qq!# upstream tree: !.$source0tree,"\n";
    if ($source0tree ne '.') {
	&git_checkout_old_nvr($fh, $source0_git_path, $default_upstream_branch_name);
    }
    my $upstream_subdir=$source0_git_path->{'path'};
    my $new_tarball_path=$opt_tarball;
    if ($upstream_subdir eq '.') {
	my $new_tarball_path=($opt_destdir ? $opt_destdir.'/' : '').$opt_tarball;
	print $fh "# move away $opt_tarball or it will appear as an untracked file to gear-update\n";
	print $fh qq{[ "$new_tarball_path" != "$opt_tarball" ] && mv -f "$opt_tarball" "$new_tarball_path"},"\n";
	print $fh qq!gear-update -f "$new_tarball_path" !.$upstream_subdir,"\n";
	print $fh qq!rm -f "$new_tarball_path" !,"\n";
    } else {
	print $fh qq!gear-update "$new_tarball_path" !.$upstream_subdir,"\n";
    }
    print $fh "git commit -m $opt_version\n";
    if ($source0tree ne '.') {
	&git_create_tag_new_nvr($fh, $source0_git_path);
	print $fh "git checkout -q $current_git_branch\n";
    }
}

sub print_master_merge {
    &new_uupdate_script('merge2master');
    print $fh '#!/bin/sh -ve',"\n";
    print $fh "git checkout -q $current_git_branch\n";

    my $nointeractive='--no-edit ';
    $nointeractive='' if $opt_git_interactive_merge;

    print $fh "git merge ", $nointeractive, join(' ',@MASTER_MERGE_DEFAULT), "\n" if @MASTER_MERGE_DEFAULT;
    print $fh "git merge -s ours ", $nointeractive, join(' ',@MASTER_MERGE_OURS), "\n" if @MASTER_MERGE_OURS;
    print $fh "gear-update-tag -ac\n";
}

sub print_update_spec {
    &new_uupdate_script('update-spec');
    my (@script, @line);
    push @line, qw/srpmnmu -i --version/, $opt_version, '--spec', $specfile, '--changelog', "- new version $opt_version";
    foreach my $hook (@opt_hook) {
	push @line, " --hook ", $hook;
    }
    push @script, \@line;
    push @script, [qw/git add/, $specfile];

    print $fh &runarray2sh(\@script);
    print $fh "rmdir $opt_destdir 2>/dev/null ||:\n";
}

sub git_checkout_old_nvr {
    my ($fh, $git_paths,$fallback_branch_name)=@_;
    my $branch_name=$git_paths->{'tag'};
    my $commit=$git_paths->{'commit'};

    if (&Gear::Rules::template_is_likely_a_tag($git_paths->{'tag_template'})) {
	print $fh 'git checkout '.&_tag2git($git_paths),"\n";
	my @find_branch=&Gear::Git::Helper::ls_git_branches($commit);
	$branch_name=$find_branch[0];
	$branch_name=$fallback_branch_name unless defined $branch_name;
    }
    if ($BRANCH->{$branch_name}) {
	if ($BRANCH->{$branch_name} eq $commit) {
	    print $fh "git checkout $branch_name\n" if $current_git_branch ne $branch_name;
	    return;
	} else {
	    print $fh "git branch -D $branch_name\n";
	}
    }
    print $fh "git checkout -q -b $branch_name ".$git_paths->{'commit'}."\n";
    return;
}

sub new_uupdate_script {
    my ($script_name)=@_;
    $script_count++;
    close ($fh) if $fh;
    `mkdir -p "$opt_destdir"` if ($opt_destdir && ! -d $opt_destdir);
    my $filename= ($opt_destdir ? $opt_destdir.'/' : '') . 
	$script_prefix.('0' x (3-length($script_count)))."$script_count-$script_name.sh";
    print STDERR "Writing: $filename\n" if $verbose;
    open ($fh, '>', $filename) or die "can't create $filename: $!";
    chmod(0755, $fh);
}

sub git_create_tag_new_nvr {
    my ($fh, $git_paths)=@_;
    my $tag_name=&get_tag_new_nvr($git_paths);
    if (&Gear::Rules::template_is_likely_a_tag($git_paths->{'tag_template'})) {
	print $fh "git tag -s -m $tag_name $tag_name\n";
    }
}

sub get_tag_new_nvr {
    my ($git_paths)=@_;
    return &Gear::Rules::subst_gear_nvr($git_paths->{'tag_template'},@NEW_NVR);
}

sub _tag2git {
    my ($git_paths)=@_;
    my $tag=$git_paths->{'tag'};
    return $tag if $TAG{$tag};
    return $git_paths->{'commit'};
}

sub runarray2sh {
    my ($scriptptr)=@_;
    print $fh '#!/bin/sh -xe',"\n";
    foreach my $lineptr (@$scriptptr) {
	print $fh join (' ', shell_quote_best_effort @$lineptr), "\n";
    }
}

sub runarray {
    my ($scriptptr)=@_;
    foreach my $lineptr (@$scriptptr) {
	&run(@$lineptr);
    }
}

sub run {
    print STDERR "running: ", join(' ',@_),"\n";
    system(@_)==0 or die "command failed: ".join(' ',@_);
}



=head1	NAME

gear-uupdate - generate NMU packages according to their upload method.

=head1	SYNOPSIS

B<gear-uupdate,gear-uupdate-prepare>
[B<-h,--help>] 
[B<-v,--verbose>]
[B<-q,--quiet>]
[B<--force-unpack>]
[B<--destdir> I<dir>]
[B<--source> I<sourcenum>]
[B<--prefix> I<script prefix>]
[B<--hook> I<path/to/hook>]
[B<--unpack>]
[B<--merge>]
[B<--update-spec>]
[B<--upstream-version> I<new version>]
I<tarball> [ I<new version> ]

=head1	DESCRIPTION

B<gear-uupdate-prepare,gear-uupdate>
Update gear repository together with rpm-uscan. gear-uupdate is a wrapper 
for gear-uupdate-prepare and gear-uupdate-execute.

=head1	OPTIONS

=over

=item	B<--upstream-version> I<new version>

New upstream version.

=item	B<--hook> I</path/to/hook>

Path to a perl-RPM-Source-Editor hook program to perform on the input packages.
This option can be repeated to load any number of hooks.

=item	B<--unpack,--merge,--update-spec>

Write scripts for specified stages only (unpack upstream tarball, merge branches, update spec).

=item	[B<--prefix> I<script prefix>]

Use specified prefix for the scripts.
Default is '__~.uupdate-step-'.

=item	[B<--source> I<sourcenum>]

Sourcenum is the rpm number of the source file watched,
Default is 0.

=item	[B<--destdir> I<dir>]

Alternative directory to store generated scripts. 

=item	[B<--force-unpack>]

Force update repository ignoring precence of external commits.

=item	B<-h, --help>

Display this help and exit.

=back

=head1	AUTHOR

Written by Igor Vlasenko <viy@altlinux.org>.

=head1	COPYING

Copyright (c) 2011,2012 Igor Vlasenko, ALT Linux Team.

This is free software; you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later version.

=cut

