#!/usr/bin/perl -w
# -*- perl -*-

#
# Author: Slaven Rezic
#
# Copyright (C) 2007,2014,2015,2021,2023,2024,2025 Slaven Rezic. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# Mail: slaven@rezic.de
# WWW:  http://www.rezic.de/eserte/
#

use strict;
use 5.010; # named capture groups
use File::Basename qw(basename);
use Getopt::Long;
use Tie::IxHash;

Getopt::Long::Configure('pass_through');

my $include_orig;
my $diff_file;
my $add_file_label;
my $add_fixed_label;
my $diff_color = '#000080';
my $preserve_areas; # otherwise areas are at least turned into closed unfilled polygons
my $with_date;
my $with_date_time;
my $with_removed;
my $title;
tie my %localized_title, 'Tie::IxHash';
GetOptions(
	   "add-file-label!" => \$add_file_label,
	   "add-fixed-label=s" => \$add_fixed_label,
	   "include-orig!" => \$include_orig,
	   "diff-file=s"   => \$diff_file,
	   'diff-color=s'  => \$diff_color,
	   'preserve-areas' => \$preserve_areas,
	   'with-date!'    => \$with_date,
	   'with-date-time!' => \$with_date_time,
	   'with-removed!' => \$with_removed, # XXX does not work well --- changed names cause two entries (old and new)
	   'title=s'           => \$title,
	   'localized-title=s' => \%localized_title,
	   "help|?" => sub { show_usage() },
	  );

my @cmd;
if ($diff_file) {
    @cmd = ('cat', $diff_file);
    @ARGV = ();
} elsif (-d "RCS") {
    @cmd = qw(rcsdiff);
    die "RCS support is NYI";
} else {
    @cmd = qw(cvs diff);
}
push @cmd, @ARGV;

$diff_color =~ m{^#[0-9a-f]{6}$}i
    or die "--diff-color argument does not look like #RRGGBB\n";

($with_date && $with_date_time)
    and die "Cannot use --with-date and --with-date-time together\n";

use constant STAGE_SHOW => 1;
use constant STAGE_HIDE => 2;
my $stage = STAGE_SHOW;
my $current_file;
my $current_base;
my $current_date;

my $bbdrx = qr{^
	       (?:>[ ]|(?<diffdir>[-+])) # context or unified diff marker (latter with both - and +)
	       (?<name>[^\t]*)           # name
	       \t(?<cat>\S+)             # cat
	       \s(?<coords>[ 0-9,.+-]+)  # coords
	       $}x;
my @diffdirs = ('+');
if ($with_removed) {
    push @diffdirs, '-';
}

my %diffdir_records; # +/- -> {name -> [[cat, coords], [cat, coords]]}
my $flush = sub {
    for my $diffdir (@diffdirs) {
	for my $name (keys %{ $diffdir_records{$diffdir} }) {
	    for my $record (@{ $diffdir_records{$diffdir}->{$name} }) {
		my($cat, $coords) = @$record;

		# Maybe shorten coordinate list
		if ($diffdir eq '+') {
		    my @minus_records = @{ $diffdir_records{'-'}->{$name} || [] };
		    for my $minus_record (@minus_records) {
			my($minus_cat, $minus_coords) = @$minus_record;
			if ($cat eq $minus_cat) {
			    my @plus_coords = split /\s+/, $coords;
			    my @minus_coords = split /\s+/, $minus_coords;

			    my $begin_i;
			    for($begin_i = 0; $begin_i <= $#plus_coords; $begin_i++) {
				no warnings 'uninitialized';
				last if $plus_coords[$begin_i] ne $minus_coords[$begin_i];
			    }
			    if ($begin_i > 0) { $begin_i-- }

			    my($end_plus_i, $end_minus_i);
			    for($end_plus_i = $#plus_coords, $end_minus_i = $#minus_coords;
				$end_plus_i >= 0 && $end_minus_i >= 0;
				$end_plus_i--, $end_minus_i--) {
				no warnings 'uninitialized';
				last if $plus_coords[$end_plus_i] ne $minus_coords[$end_minus_i];
			    }
			    if ($end_plus_i < $#plus_coords) { $end_plus_i++ }

			    $coords = join(' ', @plus_coords[$begin_i..$end_plus_i]);
			    last;
			}
		    }
		}

		if ($name =~ m{^#}) {
		    print $name, "\n";
		} else {
		    if ($diffdir eq '-') {
			$name = "REMOVED: $name";
		    }
		    if ($coords eq '') {
			# may happen if e.g. just a directive changed, but not the line itself
		    } else {
			if ($add_file_label) {
			    print "$current_base: ";
			} elsif ($add_fixed_label) {
			    print $add_fixed_label;
			}
			print "$name\t$cat $coords\n";
		    }
		}
	    }
	}
    }
    %diffdir_records = ();
};

my $have_global_directives;
if ($title) {
    print "#: title: $title\n";
    $have_global_directives++;
}
if (%localized_title) {
    for my $lang (keys %localized_title) {
	print "#: title.$lang: $localized_title{$lang}\n";
    }
    $have_global_directives++;
}
if ($have_global_directives) {
    print "#:\n";
}

for my $line (`@cmd`) {
    chomp $line;
    my $new_file_started = 0;
    if ($line =~ /^Index:\s+(.*)/) { # old CVS diff style
	$current_file = $1;
	$new_file_started = 1;
    } elsif ($with_date && $line =~ /^Date:\s+(\d{4}-\d{2}-\d{2})/) { # note: requires --date=iso
	$current_date = $1;
    } elsif ($with_date_time && $line =~ /^Date:\s+(.*)/) {
	$current_date = $1;
    } elsif ($line =~ m{^diff.*? [aci]/(\S+) [bw]/\S+}) { # new git diff style (a vs. b, or with mnemonics: c vs. w resp. i vs. w)
	$current_file = $1;
	$new_file_started = 1;
    } elsif ($line =~ m{^diff.* (\S+) (\S+)$}) { # traditional diff
	$current_file = $1;
	$new_file_started = 1;
    } elsif ($line =~ m{^\@\@}) {
	$flush->();
    }

    if ($new_file_started) {
	if ($current_file =~ m{(bbbike-temp-blockings-optimized\.pl
			       |sehenswuerdigkeit_img/
			       |\.coords\.data
			       |Makefile
			       |\.modified
			       )}x) {
	    $stage = STAGE_HIDE;
	} elsif (!$include_orig && $current_file =~ /-orig/) {
	    $stage = STAGE_HIDE;
	} else {
	    $stage = STAGE_SHOW;
	}
    }
    if ($stage == STAGE_SHOW) {
	if ($new_file_started) {
	    $flush->();
	    print "# File: $current_file\n";
	    if ($add_file_label) {
		$current_base = basename($current_file);
	    }
	} elsif ($line =~ m{$bbdrx}o) {
	    my $diffdir = $+{diffdir};
	    $diffdir = '+' if !defined $diffdir;
	    my($name, $cat, $coords) = ($+{name}, $+{cat}, $+{coords});
	    if (defined $current_date) {
		$name .= ' ' . $current_date;
	    }
	    if (!exists $diffdir_records{$diffdir}) {
		tie %{ $diffdir_records{$diffdir} }, 'Tie::IxHash';
	    }
	    my $this_diff_color = $diff_color;
	    if ($cat =~ m{^F:}) {
		if ($preserve_areas) {
		    $this_diff_color = 'F:' . $diff_color;
		} else {
		    # make a closed polygon
		    my @c = split / /, $coords;
		    if ($c[0] ne $c[-1]) {
			$coords .= ' ' . $c[0];
		    }
		}
	    }
	    push @{ $diffdir_records{$diffdir}->{$name} }, [$this_diff_color, $coords];
	}
    }
}
$flush->();

sub show_usage {
    print STDERR <<'EOF';
Usage examples:
    cd .../bbbike
    git diff -c 'master@{1 month ago}'..  -- data | ./miscsrc/cvsdiffbbd [--title ...] [--localized-title lang=... ...] --diff-file - > /tmp/diff_last_month.bbd

EOF
    exit 0;
}
__END__

=head1 EXAMPLES

Create per-month diffs since beginning of time:

    cd .../bbbike
    for y in `seq 2003 2014`; do for m in 01 02 03 04 05 06 07 08 09 10 11 12; do echo $y$m; git diff $(git rev-list -n1 --before="$y-$m-01 00:00:00" master)..$(git rev-list -n1 --before="$y-$m-$(perl -MDate::Calc=Days_in_Month -e "print Days_in_Month(@ARGV)" $y $m) 23:59:59" master) -- data | ./miscsrc/cvsdiffbbd --diff-file - >| /tmp/diff-$y$m.bbd; done; done

Like before, but restrict to changes relevant for routing (routing
algorithm and routing instructions):

    cd .../bbbike
    for y in `seq 2003 2014`; do for m in 01 02 03 04 05 06 07 08 09 10 11 12; do echo $y$m; git diff $(git rev-list -n1 --before="$y-$m-01 00:00:00" master)..$(git rev-list -n1 --before="$y-$m-$(perl -MDate::Calc=Days_in_Month -e "print Days_in_Month(@ARGV)" $y $m) 23:59:59" master) -- data/strassen data/plaetze data/landstrassen data/landstrassen2 data/faehren data/hoehe data/orte data/orte2 data/orte_city data/ampeln data/qualitaet_s data/qualitaet_l data/handicap_s data/handicap_l data/nolighting data/green data/gesperrt data/comments_cyclepath data/comments_ferry data/comments_misc data/comments_mount data/comments_path data/comments_route data/comments_tram data/comments_kfzverkehr data/comments_scenic data/fragezeichen data/culdesac data/radwege_exact data/mount data/temp_blockings/bbbike-temp-blockings.pl data/inaccessible_strassen data/inaccessible_landstrassen | ./miscsrc/cvsdiffbbd --diff-file - >| /tmp/diff-routing-$y$m.bbd; done; done

Create a gif animation out of it:

    for i in /tmp/diff-2*bbd; do mkdir $(echo $i | sed 's/.bbd//'); done
    for i in /tmp/diff-2*bbd; do cp $i $(echo $i | sed 's/.bbd//')/strassen; done
    for i in /tmp/diff-2*bbd; do ./miscsrc/bbbikedraw.pl -bg '#000000' -bbox -10984,-1574,35441,32759 -datadirs $(echo $i | sed 's/.bbd//') -o $(echo $i | sed 's/.bbd/.png/'); done
    convert /tmp/diff-2*.png /tmp/diff.gif
    display /tmp/diff.gif

Create diff of converted Berlin OSM data and send to bbbike (if
started with -server mode):

    cd .../bbbike
    diff -ur "--exclude=_*" .../bbbike-old-osm-data .../bbbike-new-osm-data | (echo "#: encoding: utf-8"; echo "#: "; ./miscsrc/cvsdiffbbd --diff-file=-) >| /tmp/osm.diff.bbd
    ./bbbikeclient /tmp/osm.diff.bbd

Create bbd file with differences since last deployment (does not
include temp_blockings changes, but see below):

    cd .../src/bbbike
    git diff deployment/bbbikede/current -- `awk '{print $1}' data/.modified` | ./miscsrc/cvsdiffbbd --diff-file=- >| /tmp/diff.bbd
    ./bbbikeclient /tmp/diff.bbd

Same as above, but prepend the bbd basename to every record:

    git diff deployment/bbbikede/current -- `awk '{print $1}' data/.modified` | ./miscsrc/cvsdiffbbd --add-file-label --diff-file=- >| /tmp/diff.bbd

Create bbd file with difference for temp_blockings changes:

    git show deployment/bbbikede/current:data/temp_blockings/bbbike-temp-blockings.pl | ./miscsrc/temp_blockings_tasks pl_to_yml /dev/stdin /dev/stdout | ./miscsrc/temp_blockings_tasks yml_to_bbd --basedir data/temp_blockings /dev/stdin | diff -u - tmp/bbbike-temp-blockings.bbd | ./miscsrc/cvsdiffbbd --add-fixed-label="bbbike-temp-blockings: " --diff-file=- >| /tmp/diff-temp-blockings.bbd

Old outdated example with CVS repository:

    for y in 2004 2005 2006 2007; do for m in 01 02 03 04 05 06 07 08 09 10 11 12; do echo $y$m; ~/src/bbbike/miscsrc/cvsdiffbbd -D$y/$m/01 -D$y/$m/`perl -MDate::Calc=Days_in_Month -e "print Days_in_Month(@ARGV)" $y $m` >| /tmp/$y$m.bbd; done;done

Create a diff from a specific date and add the change date to every
record:

    (cd ~/src/bbbike && git log --reverse --date=iso -p deployment/bbbikede/20240501.. -- `awk '{print $1}' data/.modified` | ./miscsrc/cvsdiffbbd --diff-color="#ff8080" --with-date --diff-file=- >| /tmp/bbbike-diff.bbd && ./bbbikeclient /tmp/bbbike-diff.bbd)

=cut
