#!/usr/bin/perl
# usage: tb-update [options]

use warnings;
use strict;

use Getopt::Long;
use Topbloke;

Getopt::Long::Configure(qw(bundling));

die "bad usage\n" if @ARGV;

check_clean_tree();


sub memo ($$$) { 
    my ($memos,$key,$code) = @_;
    return $memos->{$key} if exists $memos->{$key};
    debug("----- $key");
    $memos->{$key} = $code->();
}

sub merge_base ($$) {
    my ($r,$s) = @_; # refs, ideally
    our %memos;
    return memo(\%memos, "$r $s", sub {
	run_git_1line(qw(merge-base), $r, $s);
		});
}

sub committ_date ($) {
    my ($ref) = @_;
    my $l = run_git_1line(qw(git-log --date=raw -n1 --pretty=format:%cd), $ref);
    $l =~ m/^(\d+)\s/ or die;
    return $l;
}

sub update_base ($) {
    my ($patch) = @_;

    for (;;) {
	check_baseref_metadata("$baserefs/$patch");

	my $head = git_run_1line(qw(rev-parse),"$baserefs/$patch");

	# 2.i. Compute set of desired included deps
	my %desired;
	my $add_desired = sub {
	    my ($sub) = @_;
	    my ($obk,$deps) = git_get_object("$baserefs/$sub:.topbloke/deps");
	    die "$sub $obk ??" unless $obk eq 'blob';
	    foreach my $depline (split /\n/, $deps) {
		next if exists $desired{$depline};
		$desired{$depline} = 1;
		if ($depline =~ m/^- /) {
		} elsif ($depline =~ m/^-/) {
		    die "$depline ?";
		} else {
		    check_baseref_metadata("$baserefs/$depline");
		    $add_desired->($depline);
		}
	    }
	};
	$add_desired->($patch);

	# 2.ii. do the merges
	# first, find the list of sources

	my @sources;
	my ($obk,$deps) = git_get_object("$baserefs/$patch:.topbloke/deps");
	die "$patch $obk ??" unless $obk eq 'blob';
	foreach my $depline (split /\n/, $deps) {
	    if ($depline =~ m/^- /) {
		push @sources, { Ref => "$'", Kind => 'foreign' };
	    } elsif ($depline =~ m/^-/) {
		die "$depline ?"; # should have failed earlier
	    } else {
		push @sources, { 
		    Name => $depline,
		    Ref => "$tiprefs/$depline", 
		    Kind => 'tb',
		};
	    }
	}

	my ($obk,$tg) = git_get_object("$baserefs/$patch:.topbloke/topgit-");
	if ($obk ne 'missing') {
	    $obj eq 'blob' or die "$patch $obk ??";
	    chomp $tg or die "$patch ??";
	    push @sources, {
		Name => "-topgit $tg",
		Ref => "refs/top-bases/$tg", 
		Kind => 'topgit',
	    };
	}

	# This bit involves rather too much history walking
	# and could perhaps be optimised.

	# Find the merge base for each source
	foreach my $source (@sources) {
	    $source->{Head} = run_git_1line(qw(rev-parse), $source->{Ref});
	    $source->{MergeBase} = merge_base($head, "$baserefs/$patch");
	}
	# The merge base is contained in $head, so if it is equal
	# to the source's head, the source is contained in $head -
	# ie we are ahead of the source.  Skip those sources.
	@sources = grep { $source->{MergeBase} ne $source->{Head} } @sources;

	our %cmp_memos;
	@sources = sort { 
	    memo(\%cmp_memos, "$a->{Name} $b->{Name}", sub {
		my $mb = merge_base($a->{Ref}, $b->{Ref});
		return -($mb eq $a->{Ref}) cmp ($mb eq $b->{Ref})
		    # if merge base is $a then $a must be before $b
		    # ie the commit equal to the merge base is earlier
		    or (committ_date($a->{Ref}) cmp committ_date($b->{Ref}));
	    })
	} @sources;


	# Now we run git-rev-list to walk the graph back to those
	# sources so we can tell which is the most recent.
	foreach my $source (@sources) {
	    

    
	    $source->{MergeBase} = $mergebase;
	    
	    bad_metadata("$patch base topgit $obj") unless $obj eq 'blob';
	    
	    # ok
	} elsif ($obk 


sub done ($) { 
    our %done; 
    return 1 if $done{$_[0]}++;
    debug("----- $key");
}

sub memo ($$) { 
    my ($key,$code) = @_;
    our %memo;
    return $memo{$key} if exists $memo{$key};
    debug("----- $key");
    $memo{$key} = $code->();
}

sub tip_sources_core

sub tip_sources ($) {
    my ($patch) = @_;
    my @sources = ();
    push @sources, { How => 'base', Ref => "$baserefs/$patch" };
    foreach my $remote (get_configured_remotes()) {
	push @sources, { How => 'remote', 
			 Ref => "refs/remotes/$remote/topbloke-tips/$patch" };
    }

sub compute_desired_deps ($) {
    my ($patch) = @_;
 return memo("compute_desired_deps $patch", {
     foreach my $sourceref (tip_source_refs($patch)) {
	 die...
 });
}

sub update_base ($) {
    my ($patch) = @_;
    return if done("update_base $patch");
    my @desired_deps = compute_desired_deps($patch);
    die...

sub update_deps ($) {
    my $patch = @_;
    return if done("update_deps $patch");
    my $deps = git_get_object("$baserefs/$patch:.topbloke/deps");
    foreach my $dep (split /\n/, $deps) {
	if ($dep =~ m/^tb /) {
	    my $dep_patch = $'; #';
	    update_patch($dep_patch);
	}
    }
}

sub update_patch ($) {
    my $patch = @_;
    update_deps($patch);
    update_base($patch);
    update_tip($patch);
}

our $current = current_branch();
if ($current->{Kind} eq 'tip') {
    update_patch($current);
} elsif ($current->{Kind} eq 'base') {
    update_deps($current);
    update_base($current);
} else {
    die "Not a topbloke branch ($current->{Kind})\n";
}
