#!/usr/bin/perl
# Merge debian/copyright.header and debian/copyright.in.d/* fragments
# into debian/copyright, deduplicating license texts and handling clashes.

use strict;
use warnings;
use File::Find;
use Cwd 'abs_path';

my $header_file = 'debian/copyright.header';
my $fragments_dir = 'debian/copyright.in.d';
my $output_file = 'debian/copyright';

die "Missing $header_file\n" unless -f $header_file;
die "Missing $fragments_dir/\n" unless -d $fragments_dir;

# Read entire file
sub read_file {
    my ($path) = @_;
    open my $fh, '<', $path or die "Cannot open $path: $!\n";
    local $/;
    my $content = <$fh>;
    close $fh;
    return $content;
}

# Write entire file
sub write_file {
    my ($path, $content) = @_;
    open my $fh, '>', $path or die "Cannot write $path: $!\n";
    print $fh $content;
    close $fh;
}

# Parse a DEP-5 file into stanzas
sub parse_dep5 {
    my ($content) = @_;
    my @stanzas;
    my @lines = split /\n/, $content;
    my %current;
    my $in_continuation = 0;
    my $last_field;
    
    for my $line (@lines) {
        if ($line =~ /^$/) {
            # Blank line ends a stanza
            if (%current) {
                push @stanzas, {%current};
                %current = ();
                $in_continuation = 0;
                $last_field = undef;
            }
        } elsif ($line =~ /^(\S+):\s*(.*)$/) {
            # New field
            my ($field, $value) = ($1, $2);
            $current{$field} = $value;
            $last_field = $field;
            $in_continuation = 1;
        } elsif ($line =~ /^ (.*)$/ && $in_continuation) {
            # Continuation line
            $current{$last_field} .= "\n$1";
        }
    }
    
    # Don't forget the last stanza
    push @stanzas, {%current} if %current;
    
    return @stanzas;
}

# Format a stanza back to DEP-5
sub format_stanza {
    my ($stanza, $fields_order) = @_;
    my @lines;
    
    for my $field (@$fields_order) {
        next unless exists $stanza->{$field};
        my $value = $stanza->{$field};
        my @value_lines = split /\n/, $value;
        push @lines, "$field: " . shift(@value_lines);
        push @lines, map { " $_" } @value_lines;
    }
    
    return join("\n", @lines);
}

# Normalize whitespace for comparison
sub normalize_text {
    my ($text) = @_;
    $text =~ s/\s+/ /g;
    $text =~ s/^\s+|\s+$//g;
    return lc($text);
}

# Read header
my $header_content = read_file($header_file);
my @header_stanzas = parse_dep5($header_content);

# Separate header stanza from license stanzas
my @header_part = grep { exists $_->{Format} || exists $_->{Files} } @header_stanzas;
my @license_stanzas = grep { exists $_->{License} && !exists $_->{Files} && !exists $_->{Format} } @header_stanzas;

# Build a map of license texts (normalized) to their stanzas
my %license_texts;
my %license_names;

for my $stanza (@license_stanzas) {
    my $name = $stanza->{License};
    $name =~ s/\n.*//s;  # Just the first line (short name)
    my $text = $stanza->{License};
    my $normalized = normalize_text($text);
    
    $license_texts{$normalized} = $stanza unless exists $license_texts{$normalized};
    $license_names{$name} = 1;
}

# Read all fragments
my @fragments;
opendir(my $dh, $fragments_dir) or die "Cannot open $fragments_dir: $!\n";
while (my $file = readdir($dh)) {
    next if $file =~ /^\./;
    my $path = "$fragments_dir/$file";
    next unless -f $path;
    
    my $content = read_file($path);
    next if $content =~ /^\s*$/;  # Skip empty fragments
    
    my @stanzas = parse_dep5($content);
    
    # Skip the header stanza (first stanza with Format) from fragments
    # Keep Files and License stanzas
    my $first = 1;
    @stanzas = grep { 
        if ($first && exists $_->{Format}) {
            $first = 0;
            0;  # Skip first Format stanza
        } else {
            1;  # Keep everything else
        }
    } @stanzas;
    
    # Track which crate this came from
    for my $stanza (@stanzas) {
        $stanza->{_crate} = $file;
    }
    
    push @fragments, @stanzas;
}
closedir($dh);

# Separate Files stanzas from License stanzas in fragments
my @files_stanzas = grep { exists $_->{Files} } @fragments;
my @fragment_license_stanzas = grep { exists $_->{License} && !exists $_->{Files} } @fragments;

# Merge license stanzas, deduplicating by text
my %crates_by_license;  # normalized text -> list of crates

for my $stanza (@fragment_license_stanzas) {
    my $name = $stanza->{License};
    $name =~ s/\n.*//s;
    my $text = $stanza->{License};
    my $normalized = normalize_text($text);
    
    unless (exists $license_texts{$normalized}) {
        # New license text
        $license_texts{$normalized} = $stanza;
        
        # Handle name clashes
        my $final_name = $name;
        my $suffix = 2;
        while (exists $license_names{$final_name}) {
            $final_name = "$name-$suffix";
            $suffix++;
        }
        
        if ($final_name ne $name) {
            # Rename in the stanza
            my $full_text = $stanza->{License};
            $full_text =~ s/^[^\n]+/$final_name/;
            $stanza->{License} = $full_text;
        }
        
        $license_names{$final_name} = 1;
    }
    
    # Track which crate uses this license
    push @{$crates_by_license{$normalized}}, $stanza->{_crate} if $stanza->{_crate};
}

# Build final license stanzas with "Used by:" comments
my @final_license_stanzas;
for my $normalized (sort keys %license_texts) {
    my $stanza = $license_texts{$normalized};
    my @crates = @{$crates_by_license{$normalized} || []};
    
    if (@crates) {
        my $crate_list = join(', ', sort @crates);
        my $comment = "Used by: $crate_list";
        if (exists $stanza->{Comment}) {
            $stanza->{Comment} .= "\n$comment";
        } else {
            $stanza->{Comment} = $comment;
        }
    }
    
    push @final_license_stanzas, $stanza;
}

# Assemble final output
my @output;

# Header stanzas (Format + Files)
for my $stanza (@header_part) {
    if (exists $stanza->{Format}) {
        push @output, format_stanza($stanza, ['Format', 'Upstream-Name', 'Upstream-Contact', 'Source', 'Comment']);
    } else {
        push @output, format_stanza($stanza, ['Files', 'Copyright', 'License', 'Comment']);
    }
}

# Files stanzas from fragments
for my $stanza (@files_stanzas) {
    push @output, format_stanza($stanza, ['Files', 'Copyright', 'License', 'Comment']);
}

# All license stanzas
for my $stanza (@final_license_stanzas) {
    push @output, format_stanza($stanza, ['License', 'Comment']);
}

# Write output
write_file($output_file, join("\n\n", @output) . "\n");

print "Merged copyright file written to $output_file\n";
print "  Header stanzas: " . scalar(@header_part) . "\n";
print "  Files stanzas: " . scalar(@files_stanzas) . "\n";
print "  License stanzas: " . scalar(@final_license_stanzas) . "\n";
