#!/usr/bin/env perl
#
# Copyright (c) 2003-2020 Julien Nadeau Carriere <vedge@csoft.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

use strict;
use Cwd;
#use Errno qw(EEXIST);

my $SRC = '';
my $COOKIE = ".mkconcurrent_$$";
my @DIRS = ();
my $BUILD = '';
my @MKFILES = (
	'Makefile\.(prog|proj|in)',
	'\.mk$',
	'\.inc$',
	'\.m4$',
	'^README$',
	'^mkdep$',
	'^install-includes.sh$',
	'^deinstall-includes.sh$',
	'^install-sh$',
	'^config\.(guess|sub)$',
	'^configure$',
	'^configure\.in$',
	'^ltconfig$',
	'^ltmain\.sh$',
	'^manlinks\.pl$',
	'^cmpfiles\.pl$',
	'^cleanfiles\.pl$',
	'^gen-bundle\.pl$',
	'^gen-declspecs\.pl$',
	'^gen-includes\.pl$',
	'^gen-includelinks\.pl$',
	'^gen-revision\.sh$',
);
my %V = ();
my $line = '';
my %CachedRules = ();

sub Debug
{
	print STDERR @_, "\n";
}

# Return a Makefile's contents, with lines expanded and variables substituted.
sub ProcessedMakefile ($$)
{
	my $path = shift;
	my $dir = shift;

	if (!open(MF, $path)) {
		return ();
	}
	my @lines = ();
	foreach $_ (<MF>) {
		chop;

		if (/^(.+)\\$/) {			# Expansion
			$line .= $1;
		} else {				# New line
			if ($line) {
				push @lines, $line . $_;
				$line = '';
			} else {
				push @lines, $_;
			}
		}
	}
	foreach $_ (@lines) {
		if (/^\s*#/) { next; }
		if (/^\t/) { next; }
		s/\$\{(\w+)\}/$V{$1}/g;
		if (/^\s*(\w+)\s*=\s*"(.+)"$/ ||
		    /^\s*(\w+)\s*=\s*(.+)$/) {
			$V{$1} = $2;
		} elsif (/^\s*(\w+)\s*\+=\s*"(.+)"$/ ||
		         /^\s*(\w+)\s*\+=\s*(.+)$/) {
			if (exists($V{$1}) && $V{$1} ne '') {
				$V{$1} .= ' '.$2;
			} else {
				$V{$1} = $2;
			}
		}
		if (/^\s*include\s+(.+)$/) {
			my $incl = $1;
			if ($incl =~ /Makefile\.config$/) {
				# Special case: configure-generated file
				ProcessedMakefile($BUILD.'/'.$dir.'/'.$incl, $BUILD);
			} else {
				ProcessedMakefile($dir.'/'.$incl, $dir);
			}
		}
	}
	close(MF);

#	if (open(FOUT, ">>processed.txt")) {
#		print FOUT "======================= $path (in $dir) ====================================\n";
#		print FOUT join("\n", @lines), "\n";
#		close(FOUT);
#	}
	return (@lines);
}

sub MakeRule
{
	my ($lib, $rule) = @_;
	my $path = $SRC.'/mk/'.$lib;
	my $inside = 0;
	my $out = '';

	if (exists($CachedRules{$lib.' '.$rule}) && $CachedRules{$lib.' '.$rule}) {
		return $CachedRules{$lib.' '.$rule};
	}
	open(LIB, $path) || die "$path: $!";
	foreach my $l (<LIB>) {
		chop($l);
		if ($l =~ /^([\w\-\.\s]+):$/) {
			my @items = split(' ', $1);
			if (grep { $_ eq $rule } @items) {
				$inside = 1;
				next;
			} elsif ($inside) {
				$inside = 0;
				last;
			}
		}
		if ($inside) {
			$out .= $l."\n";
		}
	}
	close(LIB);
	$CachedRules{$lib.' '.$rule} = $out;
	return ($out);
}

sub ConvertMakefile
{
	my ($dir, $ndir, $ent) = @_;
	my @lines;

	open(DSTMAKEFILE, ">$BUILD/$ndir/$ent") ||
	    die "dest: $BUILD/$ndir/$ent: $!";

	%V = ();
	@lines = ProcessedMakefile($dir.'/'.$ent, $dir);
	unless (@lines) {
		return;
	}

	print DSTMAKEFILE << "EOF";
#
# This file was automatically generated by a BSDBuild ./configure script
# (https://bsdbuild.hypertriton.com/) for building this project outside of
# $SRC.
#
SRC=$SRC
BUILD=$BUILD
BUILDREL=$dir

EOF

	my @deps = ();
	my @objs = ();
	my @shobjs = ();
	my $libtool = 0;
	my $isProg = 0;
	my $isLib = 0;
	my $objExt = 'o';
	my $bbLib;
	my $lineNo = 1;

	foreach $_ (@lines) {
		my @srcs = ();

		if (/^\s*PROG\s*=/) {
			$isProg = 1;
			$bbLib = 'build.prog.mk';
			push @deps, "# Using <build.prog.mk> (Makefile:$lineNo)";
		}
		if (/^\s*LIB\s*=/) {
			$isLib = 1;
			$bbLib = 'build.lib.mk';
			push @deps, "# Using <build.lib.mk> (Makefile:$lineNo)";
		}
		if (/^\s*USE_LIBTOOL\s*=\s*Yes\s*$/) {		# XXX
			$libtool = 1;
			$objExt = 'lo';
			push @deps, "# Using libtool (Makefile:$lineNo)";
		}
		if (/^\s*(SRCS|MAN\d|MOS)\s*=\s*(.+)$/) {
			my $type = $1;

			push @deps, "# Generating " . int(split(/\s/, $2)) .
			            " entries from $type (Makefile:$lineNo)";

			foreach my $src (split(/\s/, $2)) {
				unless ($src) {
					next;
				}
				my $obj = $src;
				my $shobj = $src;

				if ($type eq 'SRCS') {
					if ($isLib && $libtool) {
						$shobj =~ s/\.(c|cc|l|y|m)$/\.lo/;
					} else {
						$shobj =~ s/\.(c|cc|l|y|m)$/\.o/;
					}
					push @shobjs, $shobj;
					$obj =~ s/\.(c|cc|l|y|m)$/\.o/;
					push @objs, $obj;
				} elsif ($type =~ /MOS/) {
					$src =~ s/\.mo$/\.po/g;
				}

				if ($src =~ /\.(adb|ads|asm)$/) {
					push @deps, "$obj: $SRC/$ndir/$src";
					push @deps, MakeRule($bbLib,".$1.o");
				} elsif ($src =~ /\.(c|cc|l|m|y)$/) {
					push @deps, "$shobj: $SRC/$ndir/$src";
					push @deps, MakeRule($bbLib, ".$1.$objExt");
				} elsif ($type =~ /MOS/) {
					push @deps, MakeRule('build.po.mk', '.po.mo');
				}
			}
		}
		if (/^\s*(SRCS|MAN\d|TTF|POS)\s*=\s*(.+)$/) {
			my $type = $1;
			my $srclist = $2;

			foreach my $src (split(/\s/, $srclist)) {
				unless ($src) {
					next;
				}
				push @srcs, $src;
			}
			my $i = 0;
			foreach my $src (@srcs) {
				$srcs[$i] = "$SRC/$ndir/$srcs[$i]";
				$i++;
			}
			print DSTMAKEFILE $type . '=' . join(' ', @srcs), "\n";
		} else {
			if (/^\s*include.+\/build\.(lib|prog|po)\.mk\s*$/) {
				print DSTMAKEFILE "# Generated objects:\n";
				if ($isLib && $libtool) {
					print DSTMAKEFILE "SHOBJS=@shobjs\n";
				} else {
					print DSTMAKEFILE "OBJS=@objs\n";
				}
				print DSTMAKEFILE "\n";
			}
			print DSTMAKEFILE $_, "\n";
		}
		$lineNo++;
	}
	
	if (@deps) {
		print DSTMAKEFILE 'CFLAGS+=-I${BUILD}', "\n";
		print DSTMAKEFILE "\n", join("\n", @deps), "\n";
		print DSTMAKEFILE 'include .depend'."\n";
	}

	close(DSTMAKEFILE);

	# Prevent make from complaining.
	open(DSTDEPEND, ">$BUILD/$ndir/.depend") or
	    die "$BUILD/$ndir/.depend: $!";
	print DSTDEPEND "\n";
	close(DSTDEPEND);
}

sub Scan
{
	my $dir = shift;

	opendir(DIR, $dir) || die "$dir: $!";
	ENTRY: foreach my $ent (readdir(DIR)) {
		if ($ent eq '.' || $ent eq '..' ||
		    $ent eq 'CVS' || $ent eq '.svn') {
			next ENTRY;
		}
		my $dent = join('/',$dir,$ent);
		my $ndir = $dir;
		$ndir =~ s/^\.\///;
		my $ndent = join('/', $BUILD,$ndir,$ent);

		if ((! -l $dent) && (-d $dent && ! -e "$dent/$COOKIE")) {
			mkdir($ndent, 0755);
			Scan($dent);
		} else {
			if ($ent eq 'Makefile') {
				ConvertMakefile($dir, $ndir, $ent);
			} else {
				foreach my $pat (@MKFILES) {
					if ($ent =~ $pat) {
						open(OLDMK, $dent) || die "$dent: $!";
						open(NEWMK, ">$ndent") || die "$ndent: $!";
						print NEWMK <OLDMK>;
						close(NEWMK);
						close(OLDMK);
						last;
					}
				}
			}
		}
	}
	closedir(DIR);
}

$SRC = $ARGV[0];
unless ($SRC) {
	print STDERR "Usage: $0 [source-directory-path]\n";
	exit (1);
}

unless (-d $SRC) {
	print STDERR "$SRC: $!\n";
	exit(1);
}
if (-e 'INSTALL') {
	print STDERR "Cannot perform concurrent build in source directory\n";
	exit(1);
}

$BUILD = getcwd();
chdir($SRC) || die "$SRC: $!";

open(COOKIE, ">$BUILD/$COOKIE") || die "$BUILD/COOKIE: $!";
Scan('.');
close(COOKIE);

END
{
	unlink("$BUILD/$COOKIE");
}

