#!/usr/bin/perl -w
# $Id: compare-mapsyms,v 1.2 2015/01/31 17:31:45 tom Exp $
# -----------------------------------------------------------------------------
# Copyright 2015 by Thomas E. Dickey
#
#                         All Rights Reserved
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name(s) of the above copyright
# holders shall not be used in advertising or otherwise to promote the
# sale, use or other dealings in this Software without prior written
# authorization.
# -----------------------------------------------------------------------------
# Compare two ".map" files, generate a report showing the number of symbols
# added, deleted or renamed.  A "renamed" symbol is one whose scope differs
# or is in a different version (the numeric plus "." part on the end of the
# label).
#
# Optionally show only the number of symbols changed.

use strict;

$| = 1;

use Getopt::Std;

our ( $opt_d, $opt_s, $opt_v );

sub open_file($) {
    my $path = shift;
    printf STDERR ".. open %s\n", $path if ($opt_v);
    open FP, "$path" || die "cannot open $path";
}

# parse the given ".map" report
sub read_report($) {
    my $path = shift;
    my %result;

    if ( -f $path ) {
        &open_file("report-mapsyms $path|");
        my (@data) = <FP>;
        close FP;
        for my $n ( 0 .. $#data ) {
            chomp $data[$n];
            my @items = split /\s+/, $data[$n];
            my $ident = $items[0];
            my %obj;
            $obj{IDENT}     = $items[0];
            $obj{SCOPE}     = $items[1];
            $obj{LABEL}     = $items[2];
            $result{$ident} = \%obj;
        }
    }
    else {
        die "not a file: $path";
    }
    return %result;
}

# make a list of the keys from old/new reports
sub merge_reports($$) {
    my %old = %{ $_[0] };
    my %new = %{ $_[1] };
    my %result;
    for my $key ( keys %old ) {
        $result{$key} = $key;
    }
    for my $key ( keys %new ) {
        $result{$key} = $key;
    }
    return %result;
}

sub version($) {
    my $value  = shift;
    my $result = $value;
    $result =~ s/^[^\d\.]+//;
    return $result;
}

sub renamed($$) {
    my %old    = %{ $_[0] };
    my %new    = %{ $_[1] };
    my $result = 0;
    if ( $old{SCOPE} ne $new{SCOPE} ) {
        $result = 1;
    }
    elsif ( &version( $old{LABEL} ) ne &version( $new{LABEL} ) ) {
        $result = 2;
    }
    return $result;
}

sub show_detail($$) {
    my $tag = $_[0];
    my %obj = %{ $_[1] };
    printf "%s\t%s\t%s\t%s\n", $tag, $obj{IDENT}, $obj{SCOPE}, $obj{LABEL};
}

sub detailed_compare($$$) {
    my %all = %{ $_[0] };
    my %old = %{ $_[1] };
    my %new = %{ $_[2] };
    for my $key ( sort keys %all ) {
        if ( not $new{$key} ) {
            &show_detail( "-", $old{$key} );
        }
        elsif ( not $old{$key} ) {
            &show_detail( "+", $old{$key} );
        }
        elsif ( &renamed( $old{$key}, $new{$key} ) ) {
            &show_detail( "!", $old{$key} );
        }
        elsif ($opt_v) {
            &show_detail( "=", $old{$key} );
        }
    }
}

sub summarize($) {
    my %all       = %{ $_[0] };
    my %old       = %{ $_[1] };
    my %new       = %{ $_[2] };
    my $added     = 0;
    my $deleted   = 0;
    my $changed   = 0;
    my $unchanged = 0;
    my $total     = 0;
    for my $key ( sort keys %all ) {
        ++$total;
        if ( not $new{$key} ) {
            ++$deleted;
        }
        elsif ( not $old{$key} ) {
            ++$added;
        }
        elsif ( &renamed( $old{$key}, $new{$key} ) ) {
            ++$changed;
        }
        else {
            ++$unchanged;
        }
    }
    printf "%d symbols", $total;
    if ( $total != $unchanged ) {
        my $first = " (";
        if ($added) {
            printf "$first$added added";
            $first = ", ";
        }
        if ($deleted) {
            printf "$first$deleted deleted";
            $first = ", ";
        }
        if ($changed) {
            printf "$first$changed changed";
            $first = ", ";
        }
        if ($unchanged) {
            printf "$first$unchanged unchanged";
            $first = ", ";
        }
        printf ")";
    }
    printf "\n";
}

sub main::HELP_MESSAGE() {
    printf STDERR <<EOF
Usage: $0 [options] mapfile1 mapfile2

Options:

-d         debug, shows parsed values
-s         summary
-v         verbose, shows files opened
EOF
      ;
    exit;
}

&getopts('dsv') || &main::HELP_MESSAGE;
if ( $#ARGV == 1 ) {
    my %old_symbols = &read_report( $ARGV[0] );
    my %new_symbols = &read_report( $ARGV[1] );
    my %all_symbols = &merge_reports( \%old_symbols, \%new_symbols );
    if ($opt_s) {
        &summarize( \%all_symbols, \%old_symbols, \%new_symbols );
    }
    else {
        &detailed_compare( \%all_symbols, \%old_symbols, \%new_symbols );
    }
}
else {
    printf STDERR "expected exactly two input map-files\n";
    &main::HELP_MESSAGE;
}

1;
