#!/usr/bin/env perl
#***************************************************************************
#                                  _   _ ____  _
#  Project                     ___| | | |  _ \| |
#                             / __| | | | |_) | |
#                            | (__| |_| |  _ <| |___
#                             \___|\___/|_| \_\_____|
#
# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
# SPDX-License-Identifier: curl
#
###########################################################################
#
# Invoke script in the root of the git checkout. Scans all files in git unless
# given a specific single file.
#
# Usage: copyright.pl [file]
#

my %skips;

# file names
my %skiplist = (
    # REUSE-specific file
    ".reuse/dep5" => "<built-in>",

    # License texts
    "LICENSES/BSD-3-Clause.txt" => "<built-in>",
    "LICENSES/BSD-4-Clause-UC.txt" => "<built-in>",
    "LICENSES/GPL-3.0-or-later.txt" => "<built-in>",
    "LICENSES/ISC.txt" => "<built-in>",
    "LICENSES/LicenseRef-OpenEvidence.txt" => "<built-in>",
    "LICENSES/curl.txt" => "<built-in>",
    "COPYING" => "<built-in>",

    # imported, leave be
    'm4/ax_compile_check_sizeof.m4' => "<built-in>",

    # an empty control file
    "zuul.d/playbooks/.zuul.ignore" => "<built-in>",
    );

sub scanfile {
    my ($f) = @_;
    my $line=1;
    my $found = 0;
    open(F, "<$f") || return -1;
    while (<F>) {
        chomp;
        my $l = $_;
        # check for a copyright statement and save the years
        if($l =~ /.* ?copyright .* *\d\d\d\d/i) {
            while($l =~ /([\d]{4})/g) {
                push @copyright, {
                  year => $1,
                  line => $line,
                  col => index($l, $1),
                  code => $l
                };
                $found++;
            }
        }
        if($l =~ /SPDX-License-Identifier:/) {
            $spdx = 1;
        }
        # allow within the first 100 lines
        if(++$line > 100) {
            last;
        }
    }
    close(F);
    return $found;
}

sub checkfile {
    my ($file, $skipped, $pattern) = @_;
    my $fine = 0;
    @copyright=();
    $spdx = 0;
    my $found = scanfile($file);

    if($found < 1) {
        if($skipped) {
            # just move on
            $skips{$pattern}++;
            return 0;
        }
        if(!$found) {
            print "$file:1: missing copyright range\n";
            return 2;
        }
        # this means the file couldn't open - it might not exist, consider
        # that fine
        return 1;
    }
    if(!$spdx) {
        if($skipped) {
            # move on
            $skips{$pattern}++;
            return 0;
        }
        print "$file:1: missing SPDX-License-Identifier\n";
        return 2;
    }

    my $commityear = undef;
    @copyright = sort {$$b{year} cmp $$a{year}} @copyright;

    # if the file is modified, assume commit year this year
    if(`git status -s -- $file` =~ /^ [MARCU]/) {
        $commityear = (localtime(time))[5] + 1900;
    }
    else {
        # min-parents=1 to ignore wrong initial commit in truncated repos
        my $grl = `git rev-list --max-count=1 --min-parents=1 --timestamp HEAD -- $file`;
        if($grl) {
            chomp $grl;
            $commityear = (localtime((split(/ /, $grl))[0]))[5] + 1900;
        }
    }

    if(defined($commityear) && scalar(@copyright) &&
       $copyright[0]{year} != $commityear) {
        printf "$file:%d: copyright year out of date, should be $commityear, " .
            "is $copyright[0]{year}\n",
            $copyright[0]{line} if(!$skipped || $verbose);
        $skips{$pattern}++ if($skipped);
    }
    else {
        $fine = 1;
    }
    if($skipped && $fine) {
        print "$file:1: ignored superfluously by $pattern\n" if($verbose);
        $superf{$pattern}++;
    }

    return $fine;
}

sub dep5 {
    my ($file) = @_;
    my @files;
    my $copy;
    open(F, "<$file") || die "can't open $file";
    my $line = 0;
    while(<F>) {
        $line++;
        if(/^Files: (.*)/i) {
            push @files, `git ls-files $1`;
        }
        elsif(/^Copyright: (.*)/i) {
            $copy = $1;
        }
        elsif(/^License: (.*)/i) {
            my $license = $1;
            for my $f (@files) {
                chomp $f;
                if($f =~ /\.gitignore\z/) {
                    # ignore .gitignore
                }
                else {
                    if($skiplist{$f}) {
                        print STDERR "$f already skipped at $skiplist{$f}\n";
                    }
                    $skiplist{$f} = "dep5:$line";
                }
            }
            undef @files;
        }
    }
    close(F);
}

dep5(".reuse/dep5");

my @all;
my $verbose;
if($ARGV[0] eq "-v") {
    $verbose = 1;
    shift @ARGV;
}
if($ARGV[0]) {
    push @all, @ARGV;
}
else {
    @all = `git ls-files`;
}

for my $f (@all) {
    chomp $f;
    my $skipped = 0;
    my $miss;
    my $wro;
    my $pattern;
    if($skiplist{$f}) {
        $pattern = $skip;
        $skiplisted++;
        $skipped = 1;
    }

    my $r = checkfile($f, $skipped, $pattern);
    $mis=1 if($r == 2);
    $wro=1 if(!$r);

    if(!$skipped) {
        $missing += $mis;
        $wrong += $wro;
    }
}

if($verbose) {
    print STDERR "$missing files have no copyright\n" if($missing);
    print STDERR "$wrong files have wrong copyright year\n" if ($wrong);
    print STDERR "$skiplisted files are skipped\n" if ($skiplisted);

    for my $s (@skiplist) {
        if(!$skips{$s}) {
            printf ("Never skipped pattern: %s\n", $s);
        }
        if($superf{$s}) {
            printf ("%s was skipped superfluously %u times and legitimately %u times\n",
                    $s, $superf{$s}, $skips{$s});
        }
    }
}

exit 1 if($missing || $wrong);
