#!/usr/bin/env perl6

# This is the template for the entire generated Makefile.
sub makefile_template() {
    return q{
.PHONY: all build test install clean distclean purge

PERL6  = $binary
DESTDIR= 
PREFIX = $prefix
BLIB   = blib
P6LIB  = $(PWD)/$(BLIB)/lib,$(PWD)/lib,$(PERL6LIB)
CP     = cp -p
MKDIR  = mkdir -p

$scripts
$blib_compiled

all build: $(BLIB_COMPILED)

$build_rules

test: build
	env PERL6LIB=$(P6LIB) prove -e '$(PERL6)' -r t/

loudtest: build
	env PERL6LIB=$(P6LIB) prove -ve '$(PERL6)' -r t/

timetest: build
	env PERL6LIB=$(P6LIB) PERL6_TEST_TIMES=1 prove -ve '$(PERL6)' -r t/

install: $(BLIB_COMPILED)
$install_rules

clean:
	rm -fr $(BLIB)

distclean purge: clean
	rm -r Makefile
}

}

sub MAIN($filename = 'Makefile', Bool :$alpha) {
    my $binary = $alpha ?? 'alpha' !! $*EXECUTABLE-NAME;
    if $alpha {
        note "Using 'alpha' as the Perl 6 binary. Please upgrade your code.";
    }

    my @modules = get_modules();
    my @pods    = get_pods();
    my $scripts = get_scripts();

    my $blib_compiled = join ' ', 'BLIB_COMPILED =', @modules>>.blib-compiled;
    my $build_rules = join "\n", @modules>>.buildrule;

    my $makefile = $filename eq '-' ?? $*OUT !! open $filename, :w;
    my $install_rules = join '', @modules>>.install-rule;
	if $scripts {
		$install_rules ~= "\t\$(MKDIR) \$(DESTDIR)\$(PREFIX)/bin\n"
						~ "\t\$(CP) \$(SCRIPTS) \$(DESTDIR)\$(PREFIX)/bin\n";
	}
    for @pods {
        my $dir         = .subst(rx{<-[/]>+$}, '');
        $install_rules ~= "\t\$(MKDIR) \$(DESTDIR)\$(PREFIX)/$dir\n";
        $install_rules ~= "\t\$(CP) $_ \$(DESTDIR)\$(PREFIX)/$dir\n";
    }

    $makefile.print(
        makefile_template()\
            .subst(/^\n/, '')\
            .subst('$binary', $binary)\
            .subst('$blib_compiled', $blib_compiled)\
            .subst('$build_rules', $build_rules)\
            .subst('$install_rules', $install_rules)\
            .subst('$scripts', $scripts)\
            .subst('$prefix', get_prefix())
    );
    $makefile.close;
}

sub get_scripts() {
    if 'bin'.IO ~~ :d {
       return 'SCRIPTS= ' ~ qx[echo bin/*].chomp;
    }
	return '';
}

my %VM_FORMATS =
    parrot => { :target<pir>, :extension<.pir>    },
    jvm    => { :target<jar>, :extension<.jar>    },
    moar   => { :target<mbc>, :extension<.moarvm> },
;
my $format = %VM_FORMATS{ $*VM.name } //
    die "Unrecognized backend '$*VM<name>'. Please contact your local VM representative.";

class Module {
    has $.lib-pm;
    has @.dependencies is rw;
    has $.target    = $format<target>;
    has $.extension = $format<extension>;
    method name     { path-to-module-name($.lib-pm) }
    method lib-dir  { $.lib-pm.subst(rx{<-[/]>+$}, '') }
    method blib-compiled { $.blib-pm ~ $.extension }
    method blib-pm  { q[$(BLIB)/] ~ $.lib-pm }
    method blib-dir { $.blib-pm.subst(rx{<-[/]>+$}, '') }
    method buildrule {
        my $header  = join(' ', $.blib-compiled, ':', $.lib-pm, @.dependencies>>.blib-compiled);
        my $mkdir   = q[$(MKDIR) ] ~ $.blib-dir;
        my $copy    = join ' ', '$(CP)', $.lib-pm, $.blib-pm;
        my $compile = "PERL6LIB=\$(P6LIB) \$(PERL6) --target=$.target --output=$.blib-compiled $.lib-pm";
        return "$header\n\t$mkdir\n\t$copy\n\t$compile\n";
    }

    method install-compiled { '$(DESTDIR)$(PREFIX)/' ~  $.lib-pm ~ $.extension }
    method install-pm  { '$(DESTDIR)$(PREFIX)/' ~  $.lib-pm }
    method install-rule {
        my $rule =join '',
            map { "\t$_\n" },
            '$(MKDIR) $(DESTDIR)$(PREFIX)/' ~ $.lib-dir,
            '$(CP) ' ~ $.blib-pm  ~ ' ' ~ $.install-pm,
            '$(CP) ' ~ $.blib-compiled ~ ' ' ~ $.install-compiled;
    }
}

sub get_pods() {
    find-file-by-ext(<lib6 lib>, 'pod');
}

sub get_modules() {

    my @module-files = find-file-by-ext(<lib lib6>, <pm pm6>);
    my @modules = @module-files.map: { Module.new(lib-pm => $_) };
    my %modules-by-name = @modules>>.name Z=> @modules;
    for @modules {
        # The grep is needed because 'find' prints a final newline, so there'll be an
        # empty-string element at the end of the list.
        .dependencies.append: %modules-by-name{ flat dependencies(.lib-pm) }.grep: *.defined
    }
    return @modules;
}

sub get_prefix() {
    my @tried;
    for <site home> -> $loc {
        my $path = %*CUSTOM_LIB{$loc};
        push @tried, $path;
        mkpath(~$path);
        next unless $path.IO.d;
        say 'WARNING: You need to re-run ufo whenever you ',
            $loc eq 'site' ?? 'update to a new Rakudo version'
                           !! 'recompile Rakudo';

        return $path;
    }
    die "Could not find a writable installation location (tried @tried.join(' and'))";
}

# blatantly stolen from Shell::Command
sub mkpath($path) {
    for [\~] $path.split('/').map({"$_/"}) {
        mkdir($_) unless .IO.d
    }
}


sub dependencies($filename) {
    sub strip_pod(Str $code is copy --> Str) {
        my regex ident { \w+ }

        my regex delimited-pod-block {
            ^^
            \s*
            '=begin'
            \s+
            <ident>
            .*
            # XXX match same leading indent?
            '=end'
            \s+
            <ident> # XXX match same ident?
        }

        while $code ~~ /<delimited-pod-block>/ {
            $code = $code.substr(0, $/.from) ~ $code.substr($/.to);
        }

        $code
    }

    my @deps;
    my $code = slurp($filename);
       $code = strip_pod($code);
    gather for $code.lines() {
        if /^\s* ['use'|'need'] \s+ (\w+ ['::' \w+]*)/ && $0 -> $used {
            next if $used eq 'v6';
            next if $used eq 'MONKEY_TYPING';
            next if $used eq 'fatal';

            take ~$used;
        }
    }
}

# Internally, we treat the module names as module names, '::' and all.
# But since they're really files externally, they have to be converted
# from paths to module names, and back again.

sub path-to-module-name($path) {
    $path.subst(/^'lib/'/, '').subst(/^'lib6/'/, '').subst(/\.pm6?$/, '').subst('/', '::', :g);
}

sub find-file-by-ext(@dirs, *@ext) {
    my $ext-re = @ext.join('|');
    my $f = rx/\. <{ $ext-re }> $ /; # RAKUDO: should just be \. @ext $
    gather for @dirs {
        dirwalk($_, :$f, :fx(&take)) if .IO.d;
    }
}

sub dirwalk(Str $dir = '.', Mu :$d = none(<. ..>), Mu :$f = *, :&dx = -> $ {}, :&fx = -> $ {}) {
    for dir($dir) -> $p {
        when $p.f {
            fx($p.Str) if $p.Str ~~ $f;
        }
        when $p.d {
            dirwalk($p.Str, :$d, :$f, :&dx, :&fx)
        }
    }
    dx($dir) if $dir ~~ $d;
}

# vim: ft=perl6
