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

#
# Author: Slaven Rezic
#
# Copyright (C) 2004,2009,2021 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:  https://github.com/eserte/bbbike
#

package BBD::Diff;

use strict;

######################################################################
# Extend path (unfortunately rather complicated for a modulino)
use Cwd qw(abs_path);
use File::Basename qw(dirname);
my $bbbike_root;
BEGIN { $bbbike_root = dirname(dirname(abs_path(__FILE__))) }
use File::Spec;
BEGIN {
    for my $candidate (File::Spec->catfile($bbbike_root, "lib"),
		       $bbbike_root,
		      ) {
	if (!grep { $candidate eq $_ } @INC) {
	    require lib;
	    lib->import($candidate);
	}
    }
}
######################################################################

use Getopt::Long;
use Algorithm::Diff qw(diff sdiff);
use Strassen::Core;
use Strassen::Util ();

sub usage {
    my $msg = shift;
    die <<EOF;
$msg
usage: $0 -o outputbbd oldbbd newbbd
EOF
}

sub process {
    my(@args) = @_;

    my @bbd_diffs;
    my $o;
    my $encoding = 'utf-8';

    local @ARGV = @args;
    if (!GetOptions(
		    "o=s" => \$o,
		    "encoding=s" => \$encoding,
		   )) {
	usage("Wrong option (accepted: -o, -encoding)");
    }

    usage("-o missing") if !$o;

    my $file1 = shift @ARGV || usage("First (old) bbd file missing");
    my $file2 = shift @ARGV || usage("Second (new) bbd file missing");

    my $s1 = Strassen->new($file1);
    my $s2 = Strassen->new($file2);

    my @diffs = diff($s1->data, $s2->data);
    
    for my $diff (@diffs) {
	for my $subdiff_new (@$diff) {
	    if ($subdiff_new->[0] eq "+") {
		my $r2 = Strassen::parse($subdiff_new->[2]);
		my $r1;
		for my $subdiff_old (@$diff) {
		    if ($subdiff_old->[0] eq "-" &&
			$subdiff_old->[1] == $subdiff_new->[1]) { # found corresponding
			$r1 = Strassen::parse($subdiff_old->[2]);
			last;
		    }
		}
		if (!$r1) {
		    # no corresponding found, try more fuzzyness (match names)
		    for my $subdiff_old (@$diff) {
			if ($subdiff_old->[0] eq "-") {
			    my $tmp_r = Strassen::parse($subdiff_old->[2]);
			    if ($tmp_r->[Strassen::NAME] ne '' &&
				$tmp_r->[Strassen::NAME] eq $r2->[Strassen::NAME]) { # maybe found corresponding
				$r1 = $tmp_r;
				last;
			    }
			}
		    }
		}

		if (!$r1 && !$r2) {
		    warn "Can't find anything in for $subdiff_new->[1], skipping...";
		    next;
		}

		my($name1, $name2);
		if ($r1) {
		    ($name1 = $r1->[Strassen::NAME]) =~ s/^\s+//;
		    $name1 =~ s/\s+$//;
		}
		if ($r2) {
		    ($name2 = $r2->[Strassen::NAME]) =~ s/^\s+//;
		    $name2 =~ s/\s+$//;
		}
		    
		if (!$r1) {
		    # could not found old entry -> assume new one
		    push @bbd_diffs,
			Strassen::arr2line2(["n+: $name2",
					     $r2->[Strassen::COORDS],
					     'added_line']);
		    next;
		}

		# XXX The !$r2 cannot happen yet, but should to detect
		# deleted lines (which should occur more rarely, so
		# it's not that pressing...)

		if ($name1 ne $name2) {
		    my $strname;
		    if ($encoding eq 'utf-8') {
			$strname = "n~: $name1 \x{2192} $name2";
		    } else {
			$strname = "n~: $name1 (old) vs. $name2 (new)";
		    }
		    push @bbd_diffs,
			Strassen::arr2line2([$strname,
					     $r2->[Strassen::COORDS],
					     "changed_name"]);
		}

		(my $cat1 = $r1->[Strassen::CAT]) =~ s/^\s+//;
		$cat1 =~ s/\s+$//;
		(my $cat2 = $r2->[Strassen::CAT]) =~ s/^\s+//;
		$cat2 =~ s/\s+$//;

		if ($cat1 ne $cat2) {
		    my $strname;
		    if ($encoding eq 'utf-8') {
			$strname = "c~: $cat1 \x{2192} $cat2 ($name2)";
		    } else {
			$strname = "c~: $cat1 (old) vs. $cat2 (new) ($name2)";
		    }
		    push @bbd_diffs,
			Strassen::arr2line2([$strname,
					     $r2->[Strassen::COORDS],
					     "changed_cat"]);
		}

		my @coords1 = @{ $r1->[Strassen::COORDS] };
		my @coords2 = @{ $r2->[Strassen::COORDS] };
		my @coord_diffs = sdiff(\@coords1, \@coords2);
		for my $coord_diff (@coord_diffs) {
		    my($action, $old, $new) = @$coord_diff;
		    if ($action eq 'c') {
			my $dist = int Strassen::Util::strecke_s($old, $new);
			push @bbd_diffs,
			    Strassen::arr2line2(["p~: $old (${dist}m) ($name2)",
						 [$old, $new],
						 "changed_point"]);
		    } elsif ($action eq '+') {
			push @bbd_diffs,
			    Strassen::arr2line2(["p+: $new ($name2)",
						 [$new], # XXX with prev and succ
						 "added_point"]); # XXX insert_point
		    } elsif ($action eq '-') {
			push @bbd_diffs,
			    Strassen::arr2line2(["p-: $old ($name1)",
						 [$old],
						 "deleted_point"]);
		    }
		}
	    }
	}
    }

    my $out = Strassen->new;
    $out->{Data} = [ map { "$_\n" } @bbd_diffs ];
    $out->set_global_directives({ 'category_color.changed_name'  => ['#000000'],
				  'category_color.changed_cat'   => ['#000080'],
				  'category_color.deleted_point' => ['#c04040'],
				  'category_color.added_point'   => ['#40c040'],
				  'category_color.changed_point' => ['#4040c0'],
				  'category_color.deleted_line'  => ['#c04040'],
				  'category_color.added_line'    => ['#40c040'],
				  'line_arrow.changed_point'     => ['last'],
				  'encoding'                     => [$encoding],
				  ($s1->get_global_directive('map') ? (map => [$s1->get_global_directive('map')]) : ()),
				});
    $out->write($o);
}

return 1 if caller;

BBD::Diff::process(@ARGV);

__END__

=head1 NAME

diffbbd - create diffs between bbd files

=head1 SYNOPSIS

    diffbbd [-encoding iso-8859-1] old.bbd new.bbd -o diff.bbd

=head2 DESCRIPTION

Create a bbd file from two (similar) bbd files showing the differences.

The names of the bbd records have a prefix which shows the type of change:

=over

=item n+

Added street name, probably a completely new record

=item n~

Changed street name

=item c~

Changed category

=item p+

Added point

=item p~

Changed (moved) point

=item p-

Deleted (removed) point

=back

Note that C<n-> (deleted names, that is, deleted records) should also
be detected, but this does not happen yet.

Normally output encoding is C<utf-8>, but can be changed using the
C<-encoding> option, e.g. C<-encoding iso-8859-1>. Only with C<utf-8>
special characters like right arrows are used in the output.

=head2 EXAMPLES

=head3 Diff older versions of BBBike data

For example: diff the L<strassen> data between one month ago and
current state. Requires a git checkout of BBBike:

=over

=item * zsh syntax (using shell-generated temporary files)

    ./miscsrc/diffbbd -o /tmp/diff.bbd =(git show 'master@{1 months ago}:data/strassen') data/strassen

=item * other shells

    git show 'master@{1 months ago}:data/strassen' > /tmp/strassen-old.bbd
    ./miscsrc/diffbbd -o /tmp/diff.bbd /tmp/strassen-old.bbd data/strassen

=back

=head3 Diff osm data

Take an old and a new .osm file and create .bbd files out of them:

    ./miscsrc/osm2bbd -map bbbike -v -f -o /tmp/oldberlin misc/download/osm.OLD/berlin.osm.bz2
    ./miscsrc/osm2bbd -map bbbike -v -f -o /tmp/newberlin misc/download/osm/berlin.osm.bz2

Now create the diff bbd for the "strassen" file:

    ./miscsrc/diffbbd /tmp/oldberlin/strassen /tmp/newberlin/strassen -o /tmp/diff.bbd 

Filter added lines, which are candidates for new streets:

    perl -nle 'm{^(#|n\+: [^\t])} and print ' /tmp/out8.bbd >| /tmp/out10.bbd

=head2 ALTERNATIVE

A diff just with Unix tools:

    diff -u /tmp/strassen strassen|grep '^\+'|cut -c2- | perl -pe 's/\t(\S+)/\t#ff0000/'

=cut
