#!/usr/bin/perl

use strict;
use warnings;
use POSIX;
use File::Temp;

# change these to match your system, or define them in ~/.xonotic-map-compiler
# (just copy paste this part to the file ~/.xonotic-map-compiler)

	# Path to Xonotic (where the data directory is in)
	our $XONOTICDIR  = getcwd();

	# Path to your q3map2 program. You find it in your netradiant/build
	# directory.
	our $Q3MAP2      = getcwd() . '/netradiant/build/q3map2';

	# General flags for q3map2 (for example -threads 4)
	our $Q3MAP2FLAGS = '-fs_forbiddenpath xonotic*-data*.pk3* -fs_forbiddenpath xonotic*-nexcompat*.pk3* -fs_forbiddenpath xonotic*-xoncompat*.pk3*';

	# Default flags for the -bsp stage
	our $BSPFLAGS    = '-meta -maxarea -samplesize 8 -mv 1000000 -mi 6000000';

	# Default flags for the -vis stage
	our $VISFLAGS    = '';

	# Default flags for the -light stage
	our $LIGHTFLAGS  = '-lightmapsize 1024 -lightmapsearchpower 4 -fastallocate -deluxe -patchshadows -samples 4 -randomsamples -bounce 8 -fastbounce -bouncegrid -nobouncestore -dirty -dirtdepth 64 -dirtscale 0.8 -fill';

	# Default flags for the -minimap stage
	our $MINIMAPFLAGS = '';

	# Default order of commands
	our $ORDER = 'vis,light,scale';

# end of user changable part

do "$ENV{HOME}/.xonotic-map-compiler";

sub Usage()
{
	print <<EOF;
Usage:
$0 mapname [-bsp bspflags...] [-vis visflags...] [-light lightflags...] [-minimap minimapflags]
EOF
	exit 1;
}

my $options =
{
	bsp => [split /\s+/, $BSPFLAGS],
	vis => [split /\s+/, $VISFLAGS],
	light => [split /\s+/, $LIGHTFLAGS],
	minimap => [split /\s+/, $MINIMAPFLAGS],
	scale => [], # can't have defaults atm
	order => [split /\s*,\s*/, $ORDER],
	maps => [],
	scalefactor => 1,
	bsp_timeout => 0,
	vis_timeout => 0,
	light_timeout => 0,
	minimap_timeout => 0,
	scale_timeout => 0,
	timeout_stealing => 0,
};

my $curmode = 'maps';

while(@ARGV)
{
	$_ = shift @ARGV;
	my $enterflags = undef;
	if($_ eq '-bsp')
	{
		$enterflags = 'bsp';
	}
	elsif($_ eq '-vis')
	{
		$enterflags = 'vis';
	}
	elsif($_ eq '-light')
	{
		$enterflags = 'light';
	}
	elsif($_ eq '-minimap')
	{
		$enterflags = 'minimap';
	}
	elsif($_ eq '-map')
	{
		$curmode = 'maps';
	}
	elsif($_ eq '-scale')
	{
		$options->{scalefactor} = @ARGV ? shift(@ARGV) : 1;
		$enterflags = 'scale';
	}
	elsif($_ eq '-novis')
	{
		$options->{vis} = undef;
	}
	elsif($_ eq '-nolight')
	{
		$options->{light} = undef;
	}
	elsif($_ eq '-nominimap')
	{
		$options->{minimap} = undef;
	}
	elsif($_ eq '-bsp_timeout')
	{
		$options->{bsp_timeout} = shift @ARGV;
	}
	elsif($_ eq '-vis_timeout')
	{
		$options->{vis_timeout} = shift @ARGV;
	}
	elsif($_ eq '-light_timeout')
	{
		$options->{light_timeout} = shift @ARGV;
	}
	elsif($_ eq '-minimap_timeout')
	{
		$options->{minimap_timeout} = shift @ARGV;
	}
	elsif($_ eq '-scale_timeout')
	{
		$options->{scale_timeout} = shift @ARGV;
	}
	elsif($_ eq '-timeout_stealing')
	{
		$options->{timeout_stealing} = shift @ARGV;
	}
	elsif($_ eq '-order')
	{
		$options->{order} = [split /\s*,\s*/, shift @ARGV];
	}
	elsif($_ eq '-sRGB')
	{
		push @{$options->{bsp}}, "-sRGBtex", "-sRGBcolor";
		push @{$options->{light}}, "-sRGBtex", "-sRGBcolor", "-sRGBlight"
			if defined $options->{light};
	}
	elsif($_ eq '-nosRGB')
	{
		push @{$options->{bsp}}, "-nosRGBtex", "-nosRGBcolor";
		push @{$options->{light}}, "-nosRGBtex", "-nosRGBcolor", "-nosRGBlight"
			if defined $options->{light};
	}
	elsif($_ =~ /^--no(-.*)/)
	{
		if($curmode eq 'maps')
		{
			$curmode = 'bsp';
		}
		my $flag = $1;
		@{$options->{$curmode}} = grep { (($_ eq $flag) ... /^-/) !~ /^[0-9]+$/ } @{$options->{$curmode}};
			# so, e.g. --no-samplesize removes "-samplesize" and a following "3"
	}
	elsif($_ =~ /^-(-.*)/)
	{
		if($curmode eq 'maps')
		{
			$curmode = 'bsp';
		}
		push @{$options->{$curmode}}, $1;
	}
	elsif($_ =~ /^-/ and $curmode eq 'maps')
	{
		$curmode = 'bsp';
		push @{$options->{$curmode}}, $_;
	}
	else
	{
		push @{$options->{$curmode}}, $_;
	}
	if(defined $enterflags)
	{
		$curmode = $enterflags;
		if($ARGV[0] eq '+')
		{
			shift @ARGV;
		}
		else
		{
			$options->{$curmode} = [];
		}
	}
}

my $linkdir = File::Temp::tempdir("xonotic-map-compiler.XXXXXX", TMPDIR => 1, CLEANUP => 1);
my $starttime = time;
my $endtime = time;

sub q3map2(@)
{
	my $mode = $_[0];
	my $timeout = undef;
	$timeout = $options->{bsp_timeout} if $mode eq '-bsp';
	$timeout = $options->{vis_timeout} if $mode eq '-vis';
	$timeout = $options->{light_timeout} if $mode eq '-light';
	$timeout = $options->{minimap_timeout} if $mode eq '-minimap';
	$timeout = $options->{scale_timeout} if $mode eq '-scale';
	die "Invalid call: not a standard q3map2 stage" if not defined $timeout;
	$endtime += $timeout;
	my $stolen_timeout = $endtime - time;
	if ($stolen_timeout > $timeout)
	{
		$timeout += ($stolen_timeout - $timeout) * $options->{timeout_stealing};
	}
	my @args = ($Q3MAP2, split(/\s+/, $Q3MAP2FLAGS), '-game', 'xonotic', '-fs_basepath', $XONOTICDIR, '-fs_basepath', $linkdir, '-v', @_);
	print "\$ @args\n";
	print "Using timeout: $timeout\n";
	defined(my $pid = fork())
		or die "fork: $!";
	if($pid) # parent
	{
		local $SIG{ALRM} = sub { warn "SIGALRM caught\n"; kill TERM => $pid; };
		alarm $timeout
			if $timeout;
		if(waitpid($pid, 0) != $pid)
		{
			die "waitpid: did not return our child process $pid: $!";
		}
		alarm 0;
		return ($? == 0);
	}
	else # child
	{
		exec @args
			or die "exec: $!";
	}
}

if ($options->{scalefactor} =~ /^([0-9.]+):([0-9.]+)$/)
{
	die "Two-scale isn't supported"
		if $1 != 1 and $2 != 1;
	$options->{scalefactor} = $1
		if $2 == 1;
	$options->{scalefactor} = $2
		if $1 == 1;
}

my $origcwd = getcwd();
for my $m(@{$options->{maps}})
{
	chdir $origcwd
		or die "chdir $origcwd: $!";
	if($m =~ s!(.*)/!!)
	{
		my $predir = $1;
		chdir $predir
			or die "chdir $predir: $!";
	}
	symlink getcwd() . "/..", "$linkdir/data"
		or die "symlink $linkdir/data: $!";

	$m =~ s/\.(?:map|bsp)$//;

	# never hurts, may help with rtlights
	unshift @{$options->{bsp}}, "-keeplights";

	local $SIG{INT} = sub
	{
		print "SIGINT caught, cleaning up...\n";
		exit 0;
	};

	unlink <$m/lm_*>; # delete old external lightmaps
	q3map2 '-bsp', @{$options->{bsp}},   "$m.map"
		or die "-bsp: $?";
	my @o = @{$options->{order}};
	push @o, qw/vis scale light/;
	my %o = ();

	for(@o)
	{
		next if $o{$_}++;
		if($_ eq 'light')
		{
			if(defined $options->{light})
			{
				q3map2 '-light',        @{$options->{light}}, "$m.map"
					or die "-light: $?";
			}
		}
		if($_ eq 'vis')
		{
			if(defined $options->{vis})
			{
				q3map2 '-vis',          @{$options->{vis}},   "$m.map"
					or die "-vis: $?";
			}
		}
		if($_ eq 'scale')
		{
			if ($options->{scalefactor} != 1)
			{
				q3map2 '-scale', @{$options->{scale}}, $options->{scalefactor}, "$m.bsp"
					or die "-scale: $?";
				rename "${m}_s.bsp", "$m.bsp"
					or die "rename ${m}_s.bsp $m.bsp: $!";
			}
		}
	}

	if(defined $options->{minimap})
	{
		q3map2 '-minimap',      @{$options->{minimap}}, "$m.map"
			or die "-minimap: $?";
	}

	unlink "$m.srf";
	unlink "$m.prt";
}
