#!/usr/bin/perl -w
#
# File:         multi_diff
#
# Description:  compare multiple text files and print unique lines
#               (only if half of the files have the same line)
#
# Syntax:       multi_diff [OPTIONS] FILE1 FILE2 FILE3 ...
#
# Options:      -p PATTERN - bit pattern to search across files (int8u values only)
#               -u - look for values that increase or decrease monotonically
#               -d - look for a value that is different in all files
#
# Created:      2009/07/17 - P. Harvey
#
# Notes:        also looks for numbers that increment with each file (ShutterCount)
#
use strict;
use warnings;

my @files;
my @fh;
my @patterns;
my $upDown;
my $diff;

for (;;) {
    $_ = shift;
    last unless defined $_;
    if ($_ eq '-p') {
        push(@patterns, shift);
        push(@patterns, $patterns[-1]);
        $patterns[-1] =~ tr/01/10/;
        next;
    } elsif ($_ eq '-d') {
        $diff = 1;
        next;
    } elsif ($_ eq '-u') {
        $upDown = 1;
        next;
    }
    /^-/ and warn("Unrecognized option $_\n"), next;
    push @files, $_;
    local *FILE;
    open FILE, $_ or die "Can't open file '$_'\n";
    push @fh, *FILE;
}
@files or die "no files specified\n";
@files > 1 or die "require at least 2 files to compare\n";

for (;;) {
    my (@lines, $lastLine, $i, $j, @bits, $pat);
    my $inc = 0;
    my ($up, $down) = (0,0);
    my $lastint;
    my $isDiff = '';
    my %found;
    foreach my $fh (@fh) {
        my $line = <$fh>;
        next unless defined $line;
        push @lines, $line;
        if ($line =~ /(\[.*?\].*?:)/) {
            if ($pat) {
                $pat eq $1 or die "Lost synchronization at $line";
            } else {
                $pat = $1;
            }
        }
        if ($diff) {
            if ($line =~ /(\d+)\s*$/) {
                if ($found{$1}) {
                    $isDiff = 0;
                    next;
                } else {
                    $found{$1} = 1;
                    $isDiff = 1 unless $isDiff eq '0';
                }
            }
            next;
        }
        if (@patterns and $line =~ /(\d+)\s*$/ and $1 >= 0 and $1 <= 255) {
            my $bits = unpack('B8',chr($1));
            my $n = 0;
            foreach (split '', $bits) {
                $bits[$n] = '' unless defined $bits[$n];
                $bits[$n] .= $_;
                ++$n;
            }
        }
        if ($upDown and $line =~ /(\d+)(\s*)$/) {
            if (defined $lastint) {
                my $diff = $1 - $lastint;
                # look for increasing/decreasing values, allowing for wrap
                ++$up if ($diff > 0 and $diff < 128) or $diff < -128;
                ++$down if ($diff < 0 and $diff > -128) or $diff > 128;
            }
            $lastint = $1;
        }
        if ($lastLine and
            $lastLine =~ s/(\d+)(\s*)$/sprintf("%.*d%s",length($1),int($1)+1,$2)/e)
        {
            ++$inc if $line eq $lastLine;
        }
        $lastLine = $line;
    }
    if ($diff) {
        if ($isDiff) {
            for ($i=0; $i<@files; ++$i) {
                next if not defined $files[$i] or not defined $lines[$i];
                print "$files[$i]: $lines[$i]";
            }
        }
        last unless @lines;
        next;
    }
    my $sign;
    if (($up   > scalar(@files)/2 and $down == 0 and $sign = '+') or
        ($down > scalar(@files)/2 and $up   == 0 and $sign = '-'))
    {
        my $str = $lines[0];
        chomp $str;
        my ($v) = $str =~ /(\d+)\s*$/;
        for ($i=1; $i<@files; ++$i) {
            $lines[$i] =~ /(\d+)(\s*)$/;
            if ($1 == $v) {
                $str .= $sign . '0';
            } elsif ($1 > $v) {
                $str .= '+' . ($1 - $v);
            } else {
                $str .= ($1 - $v);
            }
            $v = $1;
        }
        printf "%s      : %s\n", ($sign eq '+' ? 'Up  ' : 'Down'), $str;
    }
    my $p = 0;
    foreach $pat (@patterns) {
        my $n = 8;
        foreach (@bits) {
            --$n;
            next unless /^$pat$/;
            my $str = ($p & 0x01) ? 'Inverse' : 'Matching';
            for ($i=0; $i<@files; ++$i) {
                print "$str Pattern $pat (bit $n): $files[$i]: $lines[$i]";
            }
        }
        ++$p;
    }
    if ($inc > @files/2) {
        my $str = $lines[0];
        chomp $str;
        my ($v) = $str =~ /(\d+)\s*$/;
        $v = 0 unless defined $v;
        for ($i=1; $i<@files; ++$i) {
            $lines[$i] =~ /(\d+)(\s*)$/;
            $str .= ($1 < $v) ? ($1 - $v) : '+'.($1 - $v);
            $v = $1;
        }
        print "Increments: $str\n";
    }
    last unless @lines;
    my @same;
    for ($i=0; $i<@files; ++$i) {
        for ($j=0; $j<$i; ++$j) {
            last unless defined $lines[$i] and defined $lines[$j];
            next unless $lines[$i] eq $lines[$j];
            $same[$i] = ($same[$i] || 0) + 1;
            $same[$j] = ($same[$j] || 0) + 1;
        }
    }
    my $max = 0;
    my $min = 10000;
    my ($imin, $imax);
    for ($i=0; $i<@files; ++$i) {
        next unless $same[$i];
        $same[$i] > $max and $max = $same[$i], $imax = $i;
        $same[$i] < $min and $min = $same[$i], $imin = $i;
    }
    next unless defined $imin;
    next if $max < @files / 2;
    for ($i=0; $i<@files; ++$i) {
        next if $same[$i] or not defined $files[$i] or not defined $lines[$i];
        print "$files[$i]: $lines[$i]";
    }
}
close $_ foreach @fh;

print "done\n";
#end

