#!/usr/bin/env perl
#
# Copyright IBM Corp. 2017
#
# Usage: norminfo <coverage-data-file> [<multiplier>]
#
# Normalize coverage data file (ensure stable order), perform some sanity
# checks, and apply optional multiplier to execution counts.
#

use strict;
use warnings;

sub ferr($$$)
{
    my ($pos, $filename, $msg) = @_;

    if (defined($pos)) {
        $pos .= ":";
    } else {
        $pos = "";
    }

    die("$0:$filename:$pos $msg");
}

sub print_sorted($$$)
{
    my ($fd, $info, $multi) = @_;
    my (%fn, %fns, %fnda, %brda, %da);
    my ($fnf, $fnh, $brf, $brh, $lf, $lh);

    while (my $line = <$fd>) {
        $line =~ s/(^\s*|\s*$)//g;

        if ($line =~ /^end_of_record$/) {
            last;
        } elsif ($line =~ /^FN:(\d+),((\d+),)?(.*)$/) {
            my ($lineno, $fnname) = ($1, $4);

            if (exists($fn{$lineno})) {
                ferr($., $info, "Duplicate FN: entry\n");
            }
            $fn{$lineno} = [$fnname, $3];
            if (exists($fns{$fnname})) {
                ferr($., $info, "Duplicate function name\n");
            }
            $fns{$fnname} = $lineno;
        } elsif ($line =~ /^FNDA:(\d+),(.*)$/) {
            my ($count, $fnname) = ($1, $2);

            if (exists($fnda{$fnname})) {
                ferr($., $info, "Duplicate FNDA: entry\n");
            }
            $fnda{$fnname} = int($count * $multi);
        } elsif ($line =~ /^FNF:(\d+)$/) {
            if (defined($fnf)) {
                ferr($., $info, "Duplicate FNF: entry\n");
            }
            $fnf = $1;
        } elsif ($line =~ /^FNH:(\d+)$/) {
            if (defined($fnh)) {
                ferr($., $info, "Duplicate FNH: entry\n");
            }
            $fnh = $1;
        } elsif ($line =~ /^BRDA:(\d+),(e)?(\d+),(\d+),(\d+|-)$/) {
            my ($lineno, $is_exception, $block, $branch, $count) =
                ($1, defined($2) && $2 eq 'e', $3, $4, $5);

            if (exists($brda{$lineno}->{$block}->{$branch})) {
                ferr($., $info, "Duplicate BRDA: entry\n");
            }
            $count = int($count * $multi) if ($count ne "-");
            $brda{$lineno}->{$block}->{$branch} = [$count, $is_exception];

        } elsif ($line =~ /^BRF:(\d+)$/) {
            if (defined($brf)) {
                ferr($., $info, "Duplicate BRF: entry\n");
            }
            $brf = $1;
        } elsif ($line =~ /^BRH:(\d+)$/) {
            if (defined($brh)) {
                ferr($., $info, "Duplicate BRH: entry\n");
            }
            $brh = $1;
        } elsif ($line =~ /^DA:(\d+),(\d+)$/) {
            my ($lineno, $count) = ($1, $2);

            if (exists($da{$lineno})) {
                ferr($., $info, "Duplicate FNDA: entry\n");
            }
            $da{$lineno} = int($count * $multi);
        } elsif ($line =~ /^LF:(\d+)$/) {
            if (defined($lf)) {
                ferr($., $info, "Duplicate LF: entry\n");
            }
            $lf = $1;
        } elsif ($line =~ /^LH:(\d+)$/) {
            if (defined($lh)) {
                ferr($., $info, "Duplicate LH: entry\n");
            }
            $lh = $1;
        } else {
            ferr($., $info, "Unknown line: $line\n");
        }
    }

    # FN:<line>,<fnname>
    foreach my $lineno (sort({ $a <=> $b } keys(%fn))) {
        my ($fnname, $endLine) = @{$fn{$lineno}};
        $endLine = $endLine ? '.' . $endLine : '';
        print("FN:$lineno$endLine,$fnname\n");
    }

    # FNDA:<counts>,<fnname>
    foreach my $fnname (keys(%fnda)) {
        if (!exists($fns{$fnname})) {
            ferr(undef, $info, "FNDA entry without FN: $fnname\n");
        }
    }
    foreach my $fnname (sort({ $fns{$a} <=> $fns{$b} } keys(%fnda))) {
        my $count = $fnda{$fnname};
        print("FNDA:$count,$fnname\n");
    }
    # FNF:<counts>
    print("FNF:$fnf\n") if (defined($fnf));
    # FNH:<counts>
    if (defined($fnh)) {
        $fnh = 0 if ($multi == 0);
        print("FNH:$fnh\n");
    }
    # BRDA:<line>,<exception><block>,<branch>,<count>
    foreach my $lineno (sort({ $a <=> $b } keys(%brda))) {
        my $blocks = $brda{$lineno};

        foreach my $block (sort({ $a <=> $b } keys(%{$blocks}))) {
            my $branches = $blocks->{$block};

            foreach my $branch (sort({ $a <=> $b }
                                     keys(%{$branches}))
            ) {
                my ($count, $is_exception) = @{$branches->{$branch}};

                $count = "-" if ($multi == 0);
                print("BRDA:$lineno," .
                      (defined($is_exception) && $is_exception ? 'e' : '') .
                      "$block,$branch,$count\n");
            }
        }

    }
    # BRF:<counts>
    print("BRF:$brf\n") if (defined($brf));
    # BRH:<counts>
    if (defined($brh)) {
        $brh = 0 if ($multi == 0);
        print("BRH:$brh\n");
    }
    # DA:<line>,<counts>
    foreach my $lineno (sort({ $a <=> $b } keys(%da))) {
        my $count = $da{$lineno};

        print("DA:$lineno,$count\n");
    }
    # LF:<counts>
    print("LF:$lf\n") if (defined($lf));
    # LH:<count>
    if (defined($lh)) {
        $lh = 0 if ($multi == 0);
        print("LH:$lh\n");
    }
}

sub main()
{
    my $infofile = $ARGV[0];
    my $multi    = $ARGV[1];
    # info: testname -> files
    # files: infofile -> data
    # data: [ starting offset, starting line ]
    my %info;
    my $fd;
    my $tn = "";
    my %allfiles;

    $multi = 1 if (!defined($multi));
    if (!defined($infofile)) {
        $infofile = "standard input";
        warn("$0: Reading data from standard input\n");
        open($fd, "<&STDIN") or
            die("$0: Could not duplicated stdin: $!\n");
    } else {
        open($fd, "<", $infofile) or
            die("$0: Could not open $infofile: $!\n");
    }

    # Register starting positions of data sets
    while (my $line = <$fd>) {
        if ($line =~ /^TN:(.*)$/) {
            $tn = $1;
        } elsif ($line =~ /^SF:(.*)$/) {
            my $sf  = $1;
            my $pos = tell($fd);

            die("$0: Could not get file position: $!\n")
                if ($pos == -1);
            if (exists($info{$tn}->{$sf})) {
                ferr($., $infofile, "Duplicate entry for $tn:$sf\n");
            }
            $info{$tn}->{$sf} = [$pos, $.];
            $allfiles{$sf} = 1;
        }
    }

    # Print data sets in normalized order
    foreach my $filename (sort(keys(%allfiles))) {
        foreach my $testname (sort(keys(%info))) {
            my $pos = $info{$testname}->{$filename};
            my ($cpos, $lpos) = @$pos;

            next if (!defined($pos));

            if (seek($fd, $cpos, 0) != 1) {
                die("$0: Could not seek in $infofile: $!\n");
            }
            printf("TN:$testname\n");
            printf("SF:$filename\n");

            $. = $lpos;
            print_sorted($fd, $infofile, $multi);

            printf("end_of_record\n");

        }
    }
    foreach my $testname (sort(keys(%info))) {
        my $files = $info{$testname};

        foreach my $filename (sort(keys(%{$files}))) {
        }
    }

    close($fd);
}

main();
exit(0);
