#!/usr/bin/env perl

# Generates Makefiles for different systems and packages
# Uses a hashmap to do config variables  (see readvar for how variables are
#  read in and an example of how they are resolved / used)

use strict;
use warnings;
use utf8;
use File::Basename;
use File::Path 'make_path';
use File::Copy 'cp';
use Storable 'dclone';
use Digest::MD5;


my $USAGE = "Usage: " . basename($0) . " <distros> <packages>
    distros: A comma separated list of the distros defined in distros/
               Or 'all' (for all distros)
    packages:  A comma separated list of the packages defined in packages/
               Or 'all' (for all packages)
";

$#ARGV == 1 or die $USAGE;

sub globbify {
    my ($dir, $files) = @_;
    $files =~ /^all$/i and return glob "$dir/*";
    my @list = glob "$dir/{$files}";
    for (@list) {
        -f or die "Could not find '", $_, "'";
    }
    return @list;
}

sub empty {
    shift =~ /^\s*$/ and return 1;
}

sub comment {
    shift =~ /^\s*#/ and return 1;
}

sub trim {
    for (@_) {
        s/^\s+|\s+$//g;
    }
    return @_;
}

sub add {
    my ($src, $dst, $subst) = @_;
    $dst =~ s/@([\w]+)@/$subst->{$1}/g;

    if (-d $src) {
        mkdir $dst;
        opendir my $DIRFH, $src or die "Could not open '$src': $!";
        while (readdir $DIRFH) {
            next if ($_ eq '.' or $_ eq '..');
            add ("$src/$_", "$dst/$_", $subst);
        }
        closedir $DIRFH;
    } else {
        print '    - ', $dst, "\n";
        if (-B $src or $src =~ /\.patch$/i) {
            # copy binary file
            cp $src, $dst or die "Failed to copy '$src': $!";
        } else {
            # copy normal file
            open IN, '<', $src
                or die "Failed to open '$src' for reading: $!";
            open OUT, '>', $dst
                or die "Failed to open '$dst' for writing: $!";
            while (<IN>) {
                s/@([\w]+)@/$subst->{$1}/g;
                print OUT;
            }
            my $perms = (stat IN)[2] & 07777;
            close IN;
            close OUT;
            chmod ($perms | 0600, $dst);
        }
    }
}

my %escape = (
    "n" => "\n",
    "\\" => "\\",
    " " => " "
);

sub readvar {
    my ($vars, $line) = @_;
    my ($var, $val) = trim (split ":", $line, 2);
    defined $var and defined $val or die "Invalid variable assignment: '$line'";
    local $/ = '';
    $val =~ s/@([\w]+)@/$vars->{$1}/g;
    $val =~ s/\\([ \\n]|$)/$escape{$1}/g;
    $vars->{$var} = $val;
}

sub addsource {
    my ($vars, $source) = @_;
    my $md5 = Digest::MD5->new;
    open my $MD5FH, '<', $source;
    binmode $MD5FH;
    $md5->addfile ($MD5FH);
    my $field_separator = $vars->{'FIELD_SEPARATOR'};
    $vars->{'SOURCES'} .= $field_separator . basename($source);
    $vars->{'SOURCE_SUMS'} .= $field_separator . $md5->hexdigest;
    close $MD5FH;
}

my @distros  = globbify "distros",  shift;
my @packages = globbify "packages", shift;

for my $dconf (@distros) {
    my $distro = basename $dconf;
    print "$distro\n";

    open DISTRO, '<', $dconf;
    my %distro_vars;
    while (my $var = <DISTRO>) {
        chomp $var;
        (comment $var or empty $var) and next;
        readvar \%distro_vars, $var;
    }
    close DISTRO;

    for my $pconf (@packages) {
        my $package = basename $pconf;
        print " - $package\n";
        my $package_path = "build/$distro/$package";
        make_path $package_path;

        open PACKAGE, '<', $pconf;
        my %package_vars = %{dclone \%distro_vars};
        while (my $var = <PACKAGE>) {
            chomp $var;
            empty $var and last;
            comment $var and next;
            readvar \%package_vars, $var;
        }
        for (keys %package_vars) {
            defined $package_vars{$_} or $package_vars{$_} = ' ';
            # print "$_ = $package_vars{$_}\n";
        }

        while (my $file = <PACKAGE>) {
            chomp $file;
            empty $file and last;
            comment $file and next;
            add ("files/$file", "$package_path/$file", \%package_vars);
            addsource (\%package_vars, "$package_path/$file");
        }

        # write the makefile
        my $outfile = "$package_path/Makefile";
        print "    - $outfile\n";
        open MAKEFILE, '>', $outfile
            or die "Failed to open '$outfile' for writing: $!";
        while (my $segment = <PACKAGE>) {
            chomp $segment;
            empty $segment and last;
            comment $segment and next;
            open IN, '<', "segments/$segment"
                or die "Failed to open 'segments/$segment' for reading: $!";
            while (<IN>) {
                s/@([\w]+)@/$package_vars{$1}/g;
                print MAKEFILE;
            }
            close IN;
            print MAKEFILE "\n";
        }
        close PACKAGE;
        # add a target for packaging
        if (open IN, '<', "segments/$distro_vars{'MANAGER'}") {
            while (<IN>) {
                s/@([\w]+)@/$package_vars{$1}/g;
                print MAKEFILE;
            }
            close IN;
        }
        close MAKEFILE;
        addsource \%package_vars, $outfile;

        # write the package manager files
        add ("managers/$distro_vars{'MANAGER'}", $package_path, \%package_vars);
    }
}
