#!/usr/bin/perl

#   Copyright (c) MediaTek USA Inc., 2020
#
#   This program is free software;  you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or (at
#   your option) any later version.
#
#   This program is distributed in the hope that it will be useful, but
#   WITHOUT ANY WARRANTY;  without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#   General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program;  if not, see
#   <http://www.gnu.org/licenses/>.
#
#
# p4annotate
#
#   This script runs "p4 annotate" for the specified file and formats the result
#   to match the diffcov(1) age/ownership annotation specification.

#use strict;
use POSIX qw(strftime);
use File::Basename;
use File::Spec;
use Getopt::Long;

sub get_modify_time($)
{
    my $filename = shift;
    my @stat     = stat $filename;
    my $tz       = strftime("%z", localtime($stat[9]));
    $tz =~ s/([0-9][0-9])$/:\1/;
    return strftime("%Y-%m-%dT%H:%M:%S", localtime($stat[9])) . $tz;
}

my $logfile;
if (exists($ENV{LOG_P4ANNOTATE})) {
    $logfile = $ENV{LOG_P4ANNOTATE};
}
my @args   = @ARGV;
my $verify = 0;       # if set, check that we merged local changes correctly
if (!GetOptions("verify" => \$verify,
                "log=s"  => \$logfile)) {
    print(STDERR "usage: $0 [--log logfile] [--verify] filename\n");
    exit(1);
}

my @notset;
foreach my $var ("P4USER", "P4PORT", "P4CLIENT") {
    push(@notset, $var) unless exists($ENV{$var});
}
if (@notset) {
    die("$0 requires environment variable" .
        (1 < scalar(@notset) ? 's' : '') . ' ' .
        join(' ', @notset) . " to be set.");
}

if ($logfile) {
    open(LOGFILE, ">>", $logfile) or
        die("unable to open $logfile");
    print(LOGFILE "$0 " . join(" ", @args) . "\n");
    $logfile = \*LOGFILE;
}

my $pathname = shift @ARGV;

if (-e $pathname && -l $pathname) {
    $pathname = File::Spec->catfile(File::Basename::dirname($pathname),
                                    readlink($pathname));
    my @c;
    foreach my $component (split(m@/@, $pathname)) {
        next unless length($component);
        if ($component eq ".")  { next; }
        if ($component eq "..") { pop @c; next }
        push @c, $component;
    }
    $pathname = File::Spec->catfile(@c);
}

my $null = File::Spec->devnull();    # more portable
if (0 ==
    system("p4 files $pathname 2>$null|grep -qv -- '- no such file' >$null")) {
    my $version;
    my $have = `p4 have $pathname`;
    if ($have =~ /#([0-9]+) - /) {
        $version = "#$1";
    } else {
        $version = '@head';
    }
    if ($logfile) {
        print($logfile "  have $version\n");
    }
    my @annotated;
    # check if this file is open in the current sandbox...
    #  redirect stderr because p4 print "$path not opened on this client" if file not opened
    my $opened = `p4 opened $pathname 2>$null`;
    my %localAdd;
    my %localDelete;
    my ($localChangelist, $owner, $now);
    if ($opened =~ /edit (default change|change (\S+)) /) {
        $localChangelist = $2 ? $2 : 'default';
        if ($logfile) {
            print($logfile "  local edit in CL $localChangelist\n");
        }

        $owner = $ENV{P4USER};    # current user is responsible for changes
        $now   = get_modify_time($pathname)
            ;    # assume changes happened when file was liast modified

        # what is different in the local file vs the one we started with
        if (open(PIPE, "-|", "p4 diff $pathname")) {
            my $line = <PIPE>;    # eat first line
            die("unexpected content '$line'")
                unless $line =~ m/^==== /;
            my ($action, $fromStart, $fromEnd, $toStart, $toEnd);
            while ($line = <PIPE>) {
                chomp $line;
                # Also remove CR from line-end
                s/\015$//;
                if ($line =~
                    m/^([0-9]+)(,([0-9]+))?([acd])([0-9]+)(,([0-9]+))?/) {
                    # change
                    $action    = $4;
                    $fromStart = $1;
                    $fromEnd   = $3 ? $3 : $1;
                    $toStart   = $5;
                    $toEnd     = $7 ? $7 : $5;
                } elsif ($line =~ m/^> (.*)$/) {
                    $localAdd{$toStart++} = $1;
                } elsif ($line =~ m/^< (.*)$/) {
                    $localDelete{$fromStart++} = $1;
                } else {
                    die("unexpected line '$line'")
                        unless $line =~ m/^---$/;
                }
            }
        } else {
            die("unable to open pipe to p4 diff $filename");
        }
    }
    # -i: follow history across branches
    # -I: follow history across integrations
    #     (seem to be able to use -i or -I - but not both, together)
    # -u: print user name
    # -c: print changelist rather than file version ID
    # -q: quiet - suppress the 1-line header for each line
    my $annotateLineNo = 1;
    my $emitLineNo     = 1;
    if (open(HANDLE, "-|", "p4 annotate -Iucq $pathname$version")) {
        while (my $line = <HANDLE>) {

            if (exists $localDelete{$annotateLineNo++}) {
                next;    # line was deleted .. skip it
            }
            while (exists $localAdd{$emitLineNo}) {
                my $l = $localAdd{$emitLineNo};
                printf("%s|%s|%s|%s\n", $localChangelist, $owner, $now, $l);
                push(@annotated, $l) if ($verify);
                delete $localAdd{$emitLineNo};
                ++$emitLineNo;
            }

            chomp $line;
            # Also remove CR from line-end
            s/\015$//;

            if ($line =~ m/([0-9]+):\s+(\S+)\s+([0-9\/]+)\s(.*)/) {
                my $changelist = $1;
                my $owner      = $2;
                my $when       = $3;
                my $text       = $4;
                $owner =~ s/^.*<//;
                $owner =~ s/>.*$//;
                $when  =~ s:/:-:g;
                $when  =~ s/$/T00:00:00-05:00/;
                printf("%s|%s|%s|%s\n", $changelist, $owner, $when, $text);
                push(@annotated, $text) if ($verify);
            } else {
                printf("%s|%s|%s|%s\n", "NONE", "NONE", "NONE", $line);
                push(@annotated, $line) if ($verify);
            }
            ++$emitLineNo;
        }    # while (HANDLE)

        # now handle lines added at end of file
        die("lost track of lines")
            unless (0 == scalar(%localAdd) || exists($localAdd{$emitLineNo}));

        while (exists $localAdd{$emitLineNo}) {
            my $l = $localAdd{$emitLineNo};
            printf("%s|%s|%s|%s\n", $localChangelist, $owner, $now, $l);
            delete $localAdd{$emitLineNo};
            push(@annotated, $l) if ($verify);
            ++$emitLineNo;
            die("lost track of lines")
                unless (0 == scalar(%localAdd) ||
                        exists($localAdd{$emitLineNo}));
        }
        if ($verify) {
            if (open(DEBUG, "<", $pathname)) {
                my $lineNo = 0;
                while (my $line = <DEBUG>) {
                    chomp($line);
                    my $a = $annotated[$lineNo];
                    die("mismatched annotation at $pathname:$lineNo: '$line' -> '$a'"
                    ) unless $line eq $a;
                    ++$lineNo;
                }
            }
        }
    }
} else {
    if ($logfile) {
        print($logfile "  not in P4\n");
    }
    my $mtime = get_modify_time($pathname);   # when was the file last modified?
    my $owner =
        getpwuid((stat($pathname))[4]); # who does the filesystem think owns it?
    open(HANDLE, $pathname) or die("unable to open '$pathname': $!");
    while (my $line = <HANDLE>) {
        chomp $line;
        # Also remove CR from line-end
        s/\015$//;

        printf("%s|%s|%s|%s\n", "NONE", $owner, $mtime, $line);
    }
}
