#!/usr/bin/perl
use strict;

our $flag_force;
our $module="perl";
our %module_type=(general=>"txt",perl=>"pl",as=>"s",www=>"html",win32=>"c",win32rc=>"rc",apple=>"m",matlab=>"m",autoit=>"au3",python=>"py",fortran=>"f",pascal=>"pas",plot=>"pl",rust=>"rs");
our @master_config;
our $config_outputdir;
our $config_outputdir_make=0;
our $config_filetype;
our @include_default;
our %make_targets;
our %h_copylist;
our @make_folders;
our %h_def;
our @path;
our %path;
our %macros;

my $script=$0;
my $nosub=0;
if (@ARGV and $ARGV[0] eq 'nosub') {
    shift @ARGV;
    $nosub=1;
}
foreach my $a (@ARGV) {
    if ($a eq "-f") {
        $flag_force = 1;
    }
    elsif ($a eq "-v" or $a eq "--version") {
        my $prog=$0;
        $prog=~s/^.*\///;
        print "$prog: version development - latest\"\n";
        exit(0);
    }
}
if (@ARGV and -d $ARGV[0]) {
    my $d=$ARGV[0];
    open In, "config";
    @master_config=<In>;
    close In;
    chdir $d or die "can't chdir $d\n";
}
if (!-f "config") {
    open Out, ">config" or die "Can't write config: $!\n";
    if (@master_config) {
        foreach my $l (@master_config) {
            if ($l=~/^include_path:\s*(\S+)/) {
                my @t=split /:/, $1;
                my @tt;
                push @tt, "..";
                foreach my $s (@t) {
                    if ($s!~/^\// or -d $s) {
                        push @tt, "../$s";
                    }
                    else {
                        push @tt, $s;
                    }
                }
                my $path=join(":", @tt);
                print Out "include_path: $path\n";
            }
            else {
                print Out $l;
            }
        }
    }
    else {
        $config_outputdir=prompt("Please enter the path to compile into [out]: ");
        print Out "output_dir: $config_outputdir\n";
        $module=prompt("Please enter module type [perl]: ");
        print Out "module: $module\n";
    }
    close Out;

}
open In, "config" or die "Can't open config: $!\n";
while(<In>){
    if (/module:\s+(\w+)/) {
        $module=$1;
    }
    elsif (/output_(dir|path): (\S+)/) {
        $config_outputdir=$2;
    }
    elsif (/filetype:\s*(\S+)/) {
        $config_filetype=$1;
    }
    elsif (/^include_path:\s*(\S+)/) {
        add_path($1);
    }
    elsif (/^include:\s*(\S+)/) {
        my $t=$1;
        @include_default=split /[:,]\s*/, $t;
    }
    elsif (/^make-(\w+):\s*(.*\S)/) {
        $make_targets{$1}=$2;
    }
    elsif (/^macro_(\w+):\s*(.*\S)/) {
        $macros{$1}=$2;
    }
}
close In;
add_path($ENV{MYDEFLIB});
print "    output_dir: $config_outputdir\n";

if (-f "copylist") {
    my $location;
    open In, "copylist" or die "Can't open copylist: $!\n";
    while(<In>){
        my $l=$_;
        if (/^(\S+):\s*(.*)/) {
            $location=$1;
            $l=$2;
            my $t="$config_outputdir/$location";
            if (!-d $t) {
                mkdir $t;
            }
        }
        if ($l) {
            my @tlist=split /\s+/, $l;
            foreach my $t (@tlist) {
                $t=~s/^\s+//;
                $t=~s/\s+$//;
                if ($t=~/\*/) {
                    my @all=glob($t);
                    foreach my $a (@all) {
                        $h_copylist{$a}=$location;
                    }
                }
                elsif (-f $t) {
                    $h_copylist{$t}=$location;
                }
            }
        }
    }
    close In;
    my $count=keys %h_copylist;
    print "    copylist: loaded $count entries\n";
}
my @files;
my @allfiles=glob("*");
foreach my $f (@allfiles) {
    if ($f=~/.def$/) {
        push @files, $f;
    }
    elsif (-d $f) {
        if (-e "$f/skipmake") {
            print "    Skip folder $f\n";
        }
        elsif ($f =~ /^(cmp|bootstrap|old|tests|macros|deflib|dist|macros_.*)$/) {
            print "    Skip folder $f\n";
        }
        elsif ($f eq $config_outputdir and -f "$f/Makefile") {
            $config_outputdir_make = 1;
            unshift @make_folders, $f;
        }
        else {
            my @t=glob("$f/*.def");
            if (@t) {
                print "$script $f ... \n";
                system("$script $f")==0 or die "Failed to spawn sub make: $?\n";
            }

            if (-f "$f/Makefile") {
                push @make_folders, $f;
            }
        }
    }
}

my %h_page;
my %folder;

my @extrafiles;
while(my $f=pop @files){
    my @page_list;
    my $page;
    my $output_dir;
    if (!$h_def{$f} or $h_def{$f} == 1) {
        my $deplist=[];
        $h_def{$f}=$deplist;
        my $got_page=0;
        my $inpage=0;
        open In, "$f" or die "Can't open $f: $!\n";
        while(<In>){
            if ($inpage) {
                if (/^\S/) {
                    $inpage=0;
                }
                elsif ($inpage==1 and /^(\s*)(\w+):/) {
                    if (/^\s*output_dir: (\S+)/) {
                        my $dir=expand_macros($1);

                        if ($dir !~/^[\/\.]/ and $output_dir) {
                            $dir=$output_dir."/".$dir;
                        }
                        $page->{output_dir}=$dir;
                        my $key = "$f-$page->{page}";
                        my $tlist=$folder{$dir};
                        if ($tlist) {
                            push @$tlist, $key;
                        }
                        else {
                            $folder{$dir}=[$key];
                        }
                        $page->{in_var}=$dir;
                    }
                    elsif (/^\s*\$include\s+(\S*)/ and $module ne "c" and -f $1) {
                        $page->{include}->{$1}=1;
                    }
                    elsif (/^\s*(type|module):\s*(\w+)/) {
                        if (!$page->{$1}) {
                            $page->{$1}=$2;
                        }
                    }
                    elsif (/^\s*type:\s*$/) {
                        $page->{type}="none";
                    }
                    elsif (/^\s*package:/) {
                        if ($module eq "perl") {
                            $page->{type}="pm";
                        }
                    }
                    elsif (/^\s*(make_dep|make_cmd|other|CC|CFLAGS|LIB):\s+(.*)/) {
                        $page->{$1}=$2;
                    }
                }
                else {
                    $inpage=2;
                }
            }
            if (!$inpage) {
                if (/^include:?\s*(\S+\.def)/) {
                    my $f=find_file($1);
                    if ($f) {
                        push @$deplist, $f;
                        if (!$h_def{$f}) {
                            push @files, $f;
                                $h_def{$f}=1;
                        }
                    }
                }
                elsif (/^page: .*\$\d.*/) {
                    $inpage=1;
                    $page={};
                    push @page_list, $page;
                }
                elsif (/^page: ([\w\.-]+)/) {
                    if ($1 eq "test") {
                        print "skipping page: test\n";
                    }
                    else {
                        $inpage=1;
                        $got_page=1;
                        $page={};
                        push @page_list, $page;
                        $page->{page}=$1;
                        $page->{def}=$f;
                        $page->{include}={};
                        my $key="$f-$1";
                        while($h_page{$key}){
                            $key.='1';
                        }
                        $h_page{$key}=$page;
                    }
                }
                elsif (/^output_dir: (\S+)/) {
                    $output_dir=$1;
                }
            }
        }
        close In;
        if ($got_page) {
            if (@include_default) {
                foreach my $i (@include_default) {
                    my $f=find_file($i);
                    if ($f) {
                        push @$deplist, $f;
                        if (!$h_def{$f}) {
                            push @files, $f;
                                $h_def{$f}=1;
                        }
                    }
                }
            }
        }

        if ($output_dir) {
            foreach my $page (@page_list) {
                if (!$page->{output_dir}) {
                    $page->{output_dir}=$output_dir;
                }
            }
        }
    }
}
while(my ($p, $h) = each %h_page){
    if ($h->{page}=~/\.\w+$/) {
        $h->{type}="none";
    }
    if (!$h->{type}) {
        if ($config_filetype) {
            $h->{type}=$config_filetype;
        }
        else {
            my $t_module=$module;
            if ($h->{module}) {
                $t_module=$h->{module};
            }
            if ($module_type{$t_module}) {
                $h->{type}=$module_type{$t_module};
            }
            else {
                $h->{type}=$t_module;
            }
        }
    }
    if (!$h->{in_var}) {
        $h->{in_var}="toproot";
        if ($folder{toproot}) {
            my $tlist=$folder{toproot};
            push @$tlist, $p;
        }
        else {
            $folder{toproot}=[$p];
        }
    }
    $h->{path}=$h->{page};
    if ($h->{output_dir}) {
        $h->{path}=$h->{output_dir}."/".$h->{path};
    }
    if ($config_outputdir and $h->{path}!~/^[\/\.]/) {
        $h->{path}=$config_outputdir."/".$h->{path};
    }
    if ($h->{type} and $h->{type} ne "none") {
        $h->{path}.=".$h->{type}";
    }
}
while(my ($f, $l) = each %h_def){
    my %track;
    foreach my $t (@$l) {
        $track{$t}=1;
    }
    my $j=0;
    while($j<@$l){
        my $t=$l->[$j];
        my $ll=$h_def{$t};
        foreach my $tt (@$ll) {
            if (!$track{$tt}) {
                $track{$tt}=1;
                push @$l, $tt;
            }
        }
        $j++;
    }
}
open Out, ">Makefile" or die "Can't write Makefile: $!\n";
if ($0=~/(bootstrap\/script)\/mydef_make/) {
    print Out "MakePage=perl $1/mydef_page\n";
}
else {
    print Out "MakePage=mydef_page\n";
}
print Out "\n";

my %var_hash;
my @tlist;
while(my ($f, $l) = each %folder){
    my $name;
    if ($f=~/.*\/(.*)/) {
        $name=uc($1);
    }
    else {
        $name=uc($f);
    }
    if (!$name) {
        $name="ROOT";
    }
    if ($var_hash{$name}) {
        my $j=2;
        while($var_hash{"$name$j"}){$j++;};
        $name="$name$j";
    }
    $var_hash{$name}=1;
    push @tlist, "\${$name}";
    print Out "$name=";
    foreach my $p (@$l) {
        print Out $h_page{$p}->{path}, " ";
    }
    print Out "\n";
}
if (%h_copylist) {
    print Out "COPY=";
    while(my ($f, $l) = each %h_copylist){
        my $sep="/";
        if($l){$sep="/$l/"};
        if ($f=~/(.+)\/(.+)/) {
            print Out $config_outputdir, $sep, $2, " ";
        }
        else {
            print Out $config_outputdir, $sep, $f, " ";
        }
    }
    print Out "\n";
    push @tlist, "\${COPY}";
}
print Out "\n";

print Out "all_targets: ", join(" ", @tlist), "\n\n";

if (@make_folders) {
    print Out "all: all_targets ", join(' ', @make_folders), "\n\n";
}

print Out ".NOTPARALLEL:\n\n";
if(%h_copylist){
    while( my ($f, $l) = each %h_copylist){
        my $sep="/";
        if($l){$sep="/$l/"};
        if($f=~/(.+)\/(.+)/){
            print Out $config_outputdir, "$sep$2: $f\n";
            print Out "\t cp $f $config_outputdir$sep$2\n";
        }
        else{
            print Out $config_outputdir, "/$l/$f: templates/$f\n";
            print Out "\t cp templates/$f $config_outputdir/$l/$f\n";
        }
    }
    print Out "\n";
}
while(my ($p, $h)=each %h_page){
    my $def=$h->{def};
    my $inc=$h->{include};
    my $inc_dep=join(" ", keys %$inc);
    my $extra_dep='';
    if ($h->{make_dep}) {
        $extra_dep=$h->{make_dep};
    }
    if ($h->{path}) {
        my @t;
        my $l=$h_def{$def};
        foreach my $tt (@$l) {
            if (!$inc->{$tt} and -f $tt) {
                push @t, $tt;
                $inc->{$tt}=1;
            }
        }
        print Out $h->{path}, ": ", $def, " ", join(" ", @t), " $inc_dep $extra_dep\n";
        if ($h->{make_cmd}) {
            print Out "\t$h->{make_cmd}\n";
        }
        elsif ($h->{module} and ($h->{module} ne $module)) {
            print Out "\t\${MakePage} -m$h->{module} $def $h->{path}\n";
        }
        else {
            print Out "\t\${MakePage} $def $h->{path}\n";
        }
        print Out "\n";
    }
}
if (-f "install_def.sh") {
    print Out "install:\n";
    open In, "install_def.sh" or die "Can't open install_def.sh: $!\n";
    while(<In>){
        if (/^(mydef_install.*)/) {
            print Out "\t\@$1\n";
        }
    }
    close In;
    print Out "\n";
}

elsif ($make_folders[0] eq $config_outputdir) {
    print Out "install: $config_outputdir\n";
    print Out "\tmake -C $config_outputdir install\n";
    print Out "\n";
}
if (-f "tests/TESTS") {
    print Out "test:\n\tcd tests; mydef_test\n\n";
}
if (@make_folders) {
    foreach my $f (@make_folders) {
        if (!$nosub) {
            print Out "$f: force_look\n";
            print Out "\tmake -C $f\n";
            print Out "\n";
        }
    }

    print Out "force_look:\n\ttrue\n";
}
while (my ($k, $v) = each %make_targets) {
    print Out "$k:\n";
    print Out "\t$v\n";
    print Out "\n";
}
close Out;
while(my ($dir, $list) = each %folder){
    if ($dir=~/toproot|ROOT/) {
        $dir = $config_outputdir;
    }
    elsif ($dir!~/^\/|\.\.?\//) {
        $dir = "$config_outputdir/$dir";
    }

    if ($dir and !-e "$dir/skipmake") {
        if ($module eq "c" || $module eq "win32") {
            if (!-d $dir) {
                mkdir $dir;
            }
            if ($flag_force || !-f "$dir/Makefile") {
                print "Create $dir/Makefile\n";
                my $t_module;
                my $CC;
                my $CFLAGS;
                my $LIB;
                my $RC;

                my @target_list;
                if ($list) {
                    foreach my $t (@$list) {
                        my $t_page = $h_page{$t};
                        if ($t_page->{make} or $t_page->{other}) {
                            my $page=$h_page{$t};
                            my $name=$page->{path};
                            if ($page->{path}=~/.*\/(.*)\.(\w+)/) {
                                $name = $1;
                            }
                            push @target_list, [$page, $name];

                            if (!$t_module and $page->{module}) {
                                $t_module = $page->{module};
                            }

                            if ($page->{CC}) {
                                $CC = $page->{CC};
                            }
                            if ($page->{CFLAGS}) {
                                $CFLAGS = $page->{CFLAGS};
                            }
                            if ($page->{LIB}) {
                                $LIB = $page->{LIB};
                            }

                            if ($page->{make}=~/win32/) {
                                $CC = "x86_64-w64-mingw32-gcc";
                            }
                        }
                    }
                }
                if (!@target_list) {
                    my $page=$h_page{$list->[0]};
                    my $name=$page->{path};
                    if ($page->{path}=~/.*\/(.*)\.(\w+)/) {
                        $name = $1;
                    }
                    push @target_list, [$page, $name];

                    if (!$t_module and $page->{module}) {
                        $t_module = $page->{module};
                    }

                    if ($page->{CC}) {
                        $CC = $page->{CC};
                    }
                    if ($page->{CFLAGS}) {
                        $CFLAGS = $page->{CFLAGS};
                    }
                    if ($page->{LIB}) {
                        $LIB = $page->{LIB};
                    }

                    if ($page->{make}=~/win32/) {
                        $CC = "x86_64-w64-mingw32-gcc";
                    }
                }
                if (!$t_module) {
                    $t_module = $module;
                }
                print "    module: $t_module\n";
                if (@target_list) {
                    open Out, ">$dir/Makefile" or die "Can't write $dir/Makefile: $!\n";
                    if (!$CC) {
                        if ($t_module eq "win32") {
                            $CC = "x86_64-w64-mingw32-gcc";
                            $RC = "x86_64-w64-mingw32-windres";
                            $LIB = "-Wl,-subsystem,windows";
                        }
                        else {
                            $CC = "gcc";
                        }
                    }
                    print Out "CC=$CC\n";
                    print Out "CFLAGS=$CFLAGS\n";
                    print Out "LIB=$LIB\n";
                    if ($RC) {
                        print Out "RC=$RC\n";
                    }
                    print Out "\n";
                    foreach my $t (@target_list) {
                        my ($page, $name)=@$t;
                        my @objs = ("$name.o");
                        if ($page->{other}) {
                            my @tlist = split /,\s*/, $page->{other};
                            foreach my $p (@tlist) {
                                push @objs, "$p.o";
                            }
                        }
                        my $obj_list = join(' ', @objs);
                        my $lib_list;
                        if (-f "$dir/$name.c") {
                            open In, "$dir/$name.c" or die "Can't open $dir/$name.c: $!\n";
                            while(<In>){
                                if (/^\/\*\s*link:\s*(.*?)\s*\*\/$/) {
                                    $lib_list = $1;
                                    last;
                                }
                            }
                            close In;
                        }
                        my $target = $name;
                        if ($t_module eq "win32" or $page->{make}=~/win32/) {
                            $target = "$name.exe";
                        }

                        print Out "$target: $obj_list\n";
                        print Out "\t\$(CC) -o $target \$^ \$(LIB) $lib_list \n";
                        print Out "\n";
                    }
                    print Out "%.o: %.c\n";
                    print Out "\t\$(CC) -c \$(CFLAGS) -o \$@ \$<\n";
                    print Out "\n";

                    if ($RC) {
                        print Out "%.o: %.rc\n";
                        print Out "\t\$(RC) \$< \$@ \n";
                        print Out "\n";
                    }
                    close Out;
                }

            }
        }
    }
}
if ($config_outputdir) {
    if (!-d $config_outputdir) {
        print "Create output folder $config_outputdir ...\n";
        mkdir $config_outputdir;
    }
}
else {
    $config_outputdir=".";
}

foreach my $f (keys(%folder)) {
    if ($f=~/toproot|ROOT/) {
        next;
    }
    if ($f !~/^[\/\.]/) {
        $f=$config_outputdir."/".$f;
    }
    if (!-d $f) {
        print "Create output folder $f ...\n";
        system "mkdir -p $f";
    }
}

# ---- subroutines --------------------------------------------
sub prompt {
    my ($msg) = @_;
    while(1){
        print "$msg\n";
        my $t=<STDIN>;
        chomp $t;
        if ($t) {
            return $t;
        }
        elsif ($msg=~/\[(.*)\]: $/) {
            return $1;
        }
        else {
            die "Must reply $msg\n";
        }
    }
}

sub add_path {
    my ($dir) = @_;
    if (!$dir) {
        return;
    }

    my $deflib=$ENV{MYDEFLIB};
    my $defsrc=$ENV{MYDEFSRC};

    if ($dir=~/\$\(MYDEFSRC\)/) {
        if (!$defsrc) {
            die "MYDEFSRC not defined (in environment)!\n";
        }
        $dir=~s/\$\(MYDEFSRC\)/$defsrc/g;
    }

    my @tlist = split /:/, $dir;
    foreach my $t (@tlist) {
        $t=~s/\/$//;
        if ($t and !$path{$t}) {
            if (-d $t) {
                $path{$t}=1;
                push @path, $t;
            }
            else {
                warn "add_path: [$t] not a directory\n";
            }
        }
    }
}

sub expand_macros {
    my ($t) = @_;
    if ($t=~/\$\((\w+)\)/) {
        if ($macros{$1}) {
            $t=$`.$macros{$1}.$';
        }
        else {
            die "Unknown Macro in $t\n";
        }
    }
    return $t;
}

sub find_file {
    my ($file) = @_;
    my $nowarn;
    if ($file=~/^(\S+)\?/) {
        $file=$1;
        $nowarn = 1;
    }

    if (-f $file) {
        return $file;
    }

    if (@path) {
        foreach my $dir (@path) {
            if (-f "$dir/$file") {
                return "$dir/$file";
            }
        }
    }
    if (!$nowarn) {
        warn "$file not found\n";
        warn "  search path: ".join(":", @path)."\n";
    }

    return undef;
}

