#!/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   = '/home/rpolzer/Games/Xonotic';

	# Path to your q3map2 program. You find it in your GtkRadiant/install
	# directory.
	our $Q3MAP2      = '/home/rpolzer/Games/Xonotic/netradiant/install/q3map2.x86';

	# General flags for q3map2 (for example -threads 4)
	our $Q3MAP2FLAGS = '-fs_forbiddenpath xonotic*-data*.pk3* -fs_forbiddenpath xonotic*-nexcompat*.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  = '-lightmapsearchpower 3 -deluxe -patchshadows -randomsamples -samples 4 -lightmapsize 512 -fast -fastbounce -dirty -bouncegrid -fill';

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

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

# 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
};

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 '-noshaderlist')
	{
		$options->{noshaderlist} = 1;
	}
	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 '-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);

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;
	my @args = ($Q3MAP2, split(/\s+/, $Q3MAP2FLAGS), '-game', 'xonotic', '-fs_basepath', $XONOTICDIR, '-fs_basepath', $linkdir, '-v', @_);
	print "\$ @args\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: $!";
	}
}

(my $mapdir = getcwd()) =~ s!/[^/]*(?:$)!!;
$mapdir = "/" if $mapdir eq "";
symlink "$mapdir", "$linkdir/data";

my ($prescale, $postscale) = ($options->{scalefactor} =~ /^([0-9.]+)(?::([0-9.]+))?$/);
$prescale = 1 if not defined $prescale;
$postscale = 1 if not defined $postscale;

for my $m(@{$options->{maps}})
{
	$m =~ s/\.(?:map|bsp)$//;

	if($prescale != 1)
	{
		unshift @{$options->{bsp}}, "-keeplights";
	}

	my %shaders = map { m!/([^/.]*)\.shader(?:$)! ? ($1 => 1) : () } glob "../scripts/*.shader";

	my $restore_shaderlist = sub { };
	if(!$options->{noshaderlist})
	{
		my $previous_shaderlist = undef;
		my $shaderlist = "";
		if(open my $fh, "<", "$XONOTICDIR/data/scripts/shaderlist.txt")
		{
			while(<$fh>)
			{
				$shaderlist .= $_;
			}

			# we may have to restore the file on exit
			$previous_shaderlist = $shaderlist
				if "$XONOTICDIR/data" eq $mapdir;
		}
		else
		{
			# possibly extract the shader list from a pk3?
			local $ENV{N} = $XONOTICDIR;
			$shaderlist = `cd "\$N" && for X in "\$N"/data/data*.pk3; do Y=\$X; done; unzip -p "\$Y" scripts/shaderlist.txt`;
		}

		my $shaderlist_new = "";
		for(split /\r?\n|\r/, $shaderlist)
		{
			delete $shaders{$_};
			$shaderlist_new .= "$_\n";
		}
		if(%shaders)
		{
			for(sort keys %shaders)
			{
				$shaderlist_new .= "$_\n";
			}
		}
		else
		{
			$shaderlist_new = undef;
		}

		$restore_shaderlist = sub
		{
			if(defined $shaderlist_new)
			{
				if(defined $previous_shaderlist)
				{
					open my $fh, ">", "$mapdir/scripts/shaderlist.txt";
					print $fh $previous_shaderlist;
					close $fh;
				}
				else
				{
					unlink "$mapdir/scripts/shaderlist.txt";
				}
			}
		};

		if(defined $shaderlist_new)
		{
			mkdir "$mapdir/scripts";
			open my $fh, ">", "$mapdir/scripts/shaderlist.txt";
			print $fh $shaderlist_new;
			close $fh;
		}
	}

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

	eval
	{
		unlink <$m/lm_*>; # delete old external lightmaps
		q3map2 '-bsp', @{$options->{bsp}},   "$m.map"
			or die "-bsp: $?";
		if($prescale != 1)
		{
			q3map2 '-scale', @{$options->{scale}}, $prescale, "$m.bsp"
				or die "-scale: $?";
			rename "${m}_s.bsp", "$m.bsp"
				or die "rename ${m}_s.bsp $m.bsp: $!";
		}
		my @o = @{$options->{order}};
		push @o, qw/light vis/;
		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($postscale != 1)
		{
			q3map2 '-scale', @{$options->{scale}}, $postscale, "$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";

		$restore_shaderlist->();
		1;
	}
	or do
	{
		$restore_shaderlist->();
		die $@;
	};
}
