package PerlRPG::Script;
use strict;
require Exporter;
use SDL;
use SDL::Event;
use SDLx::App;
use SDLx::Sprite;
use SDLx::Sprite::Animated;
use PerlRPG::Console;
use PerlRPG::Game;
use PerlRPG::Assets;
use PerlRPG::Drawing;

use vars qw/@ISA @EXPORT @EXPORT_OK/;
@ISA = qw/Exporter/;
@EXPORT = qw/CompileScripts RunScript RunScriptTick GetGameVar SetGameVar DefGameVar EvalString/;
@EXPORT_OK = @EXPORT;

my %labels;
my $current_file;
my $current_line;

my %script_commands = (
	'Wait'			=> {'sub' => sub {},					'wait'	=> 1},
	'AddSayer'		=> {'sub' => \&AddSayer,				'wait'	=> 0},
	'Define'		=> {'sub' => \&DefGameVar,				'wait'	=> 0},
	'ExitGame'		=> {'sub' => \&PerlRPG::Game::ExitGame,			'wait'	=> 1},
	
	'Jump'			=> {'sub' => \&PerlRPG::Script::RunScript,		'wait'	=> 0},

	'Show'			=> {'sub' => \&PerlRPG::Drawing::ShowSprite,		'wait'	=> 0},
	'Hide'			=> {'sub' => \&PerlRPG::Drawing::HideSprite,		'wait'	=> 0},
	'SetBackground'		=> {'sub' => \&PerlRPG::Drawing::SetBackgroundColor,	'wait'	=> 0},

	'ScanDirectory'		=> {'sub' => \&PerlRPG::Assets::ScanDirectory,		'wait'	=> 0},
	'LoadAssets'		=> {'sub' => \&PerlRPG::Assets::LoadAssets,		'wait'	=> 0},
	'UnloadAsset'		=> {'sub' => \&PerlRPG::Assets::UnloadAsset,		'wait'	=> 0},
	'SetAssetOption'	=> {'sub' => \&PerlRPG::Assets::SetAssetOption,		'wait'	=> 0},
	'RecompileScripts'	=> {'sub' => \&RecompileScripts,			'wait'	=> 1},
);

my %sayers = ();
my $lastsayer=undef;

my %game_vars = ();

sub GetGameVar {
	my $key = shift(@_);
	if(exists $game_vars{$key}) {
		return $game_vars{$key};
	}
	LogData(WARN, "GetGameVar($key): Unknown variable");
	return undef;
}

sub IsString {
	return 1 if($_[0]=~/^\s*".+"\s*$/);
}

sub SetGameVar {
	my($key, $val)=@_;
	if(exists $game_vars{$key}) {
		return $game_vars{$key} = StringSubst($val);
	}
	LogData(WARN, "SetGameVar($key): Unknown variable");
	return undef;
}

# It is legal to Define a game variable twice, value will not be reset
sub DefGameVar {
	my($key, $val)=@_;
	if(!exists $game_vars{$key}) {
		$game_vars{$key} = StringSubst($val);
	}
}

sub RecompileScripts {
	my $jumpto = (shift(@_) || '__init__');
	my @scripts = LoadAssets('gs');

	LogData(INFO, "Doing full script recompile and jumping to '$jumpto'");
	UnloadAsset(@scripts);
	CompileScripts();
	RunScript($jumpto);
}
		
# Load script files, locate labels
sub CompileScripts {
	my @scripts = sort { $a cmp $b } LoadAssets('gs');
	%labels = ();
	
	foreach my $name (@scripts) {
		my $ref = GetAsset($name);
		for(my $line = 0; $line < @$ref-0; $line++) {
			my($lbl)=$ref->[$line]=~/^\s*(\S+):\s*$/;
			if($lbl) {
				$labels{$lbl} = {
					'File'	=> $name,
					'Line'	=> $line,
					};
				LogData(DEBUG, "Found script label '$lbl' at $name:$line");
			}
		}
	}
}

# Setup script to run
sub RunScript {
	my($label)=@_;

	if(exists $labels{$label}) {
		$current_file = $labels{$label}{'File'};
		$current_line = $labels{$label}{'Line'};
		LogData(DEBUG, "RunScript($label) moving to $current_file:$current_line");
	} else {
		LogData(ERROR, "RunScript($label) unknown label");
	}
}

# Run script from current position until a wait condition
sub RunScriptTick {
	return 1 if($lastsayer);
	for(;;) {
		my $script = GetAsset($current_file); # current_file can change in RunScriptLine, so reload each time
		my $file = $current_file;
		my $line = $current_line++;
		if($line >= @$script) {
			LogData(ERROR, "Script file ended");
			SetGameVar('GameRunning', 0);
			return;
		}
		if(RunScriptLine($file, $line, $script->[$line])) {
			return;
		}
	}
};

# Returns true if a wait condition is seen
sub RunScriptLine {
	my($file, $linenum, $line)=@_;

	# Copy to refer to
	my $oline = $line;

	$line=~s/#.+$//; # Remove comments
	$line=~s/^\s+//; # Remove leading whitespace
	$line=~s/\s+$//; # Remove trailing whitespace

	if($line=~/^\s*\S+:\s*$/ || $line=~/^\s+$/ || !$line) {
		# Label or blank, skip
		return 0;
	}

	my @opts;
	{
		my $sline = $line;
		$sline=~s/\\ /\x01/g;
		(@opts)=split(/\s+/, $sline);
		map { s/\x01/ /g } (@opts);
	}
	my $cmd = shift(@opts);
	LogData(DEBUG, "$file:$linenum $cmd(" . join(',',@opts) . ")");
	if(exists $script_commands{$cmd}) {
		$script_commands{$cmd}{'sub'}->(@opts);
		return $script_commands{$cmd}{'wait'};
	} elsif(exists $sayers{$cmd}) {
		my($text)=$oline=~/\Q$cmd\E\s+(".+?")\s*$/;
		if($text) {
			return SayLine($sayers{$cmd}, $text);
		} else {
			LogData(ERROR, "Invalid sayer text in script at $file:$linenum");
			return;
		}
	} elsif(my($key,$val)=$line=~/^\s*(\S+)\s*=\s*(.+?)\s*$/) {
		LogData(DEBUG, "Setting '%s' to '%s'", $key, $val);
		return SetGameVar($key, $val);
	}

	LogData(ERROR, "Unknown command '$cmd' in script at $file:$linenum");
	return undef;
}

sub AddSayer {
	my($name, $displayname, $sprite, $r, $g, $b, $a, @opts)=@_;

	my @textcolor=($r, $g, $b, $a);
	$textcolor[3] = 255 unless(defined $a);

	my $drawleft = 0;
	my @sprite_opts;

	foreach my $opt (@opts) {
		if(lc($opt) eq 'left') {
			$drawleft = 1;
		} else {
			push @sprite_opts, $opt;
		}
	}
	

	$sayers{$name} = {
		'Name'		=> $name,
		'DisplayName'	=> StringSubst($displayname),
		'Sprite'	=> $sprite,
		'TextColor'	=> \@textcolor,
		'DrawLeft'	=> $drawleft,
		'SpriteOpts'	=> \@sprite_opts,
	};
}

sub EvalString {
	my $val = shift(@_);
	# Perform math calculations and the like

	# Check for simple numbers
	if(my($v) = $val=~/^\s*(\d+)\s*$/) {
		return $v;
	}
	
	#LogData(DEBUG, "Doing complex Eval");
	# Ensure spacing around tokens
	while(my($full,$f,$o,$s)=$val=~/((\S)([+-\/\*\(\)\.])(\S))/) {
		#LogData(DEBUG, "Replacing '$full' with '$f $o $s'");
		$val=~s/\Q$full\E/$f $o $s/;
	}
	while(my($full,$f,$o)=$val=~/((\S)([+-\/\*\(\)\.]))\s/) {
		#LogData(DEBUG, "Replacing '$full' with '$f $o'");
		$val=~s/\Q$full\E/$f $o/;
	}
	while(my($full,$o,$s)=$val=~/\s(([+-\/\*\(\)\.])(\S))/) {
		#LogData(DEBUG, "Replacing '$full' with '$o $s'");
		$val=~s/\Q$full\E/$o $s/;
	}
	if($val=~/^[+-\/\*\(\)\.]\S/) {
		substr($val, 1, 0)=' ';
		#LogData(DEBUG, "Inserting space after initial character");
	}
	if($val=~/\S[+-\/\*\(\)\.]$/) {
		substr($val, -1, 0)=' ';
		#LogData(DEBUG, "Inserting space before last character");
	}

	#LogData(DEBUG, "Val is '$val'");

	# Tokenize
	my @tokens = split(/\s+/,$val);


	#for(my $i=0;$i<@tokens-0;$i++) { LogData(DEBUG, "Pass 1 Token [%i], '%s'", $i, $tokens[$i]); }

	for(my $i=0;$i<@tokens-0;$i++) {
		my $t = $tokens[$i];
		if($i < @tokens-1 && $tokens[$i+1] eq '(') {
			# This is a function call; Don't replace it
		} elsif(exists $game_vars{$t}) {
			# Token matches a game var, replace it
			$tokens[$i] = GetGameVar($t);
		} elsif(IsString($tokens[$i])) {
			# Looks like a string, Call StringSubst on it
			$tokens[$i] = '"' . StringSubst($tokens[$i]) . '"';
		}
	}

	#for(my $i=0;$i<@tokens-0;$i++) { LogData(DEBUG, "Pass 2 Token [%i], '%s'", $i, $tokens[$i]); }

	my $eval_str = '$eval_res = ' . join(' ', @tokens) . ';';
	#LogData(DEBUG, "About to eval '$eval_str'");
	my $eval_res;
	eval $eval_str;
	if($@) {
		LogData(ERROR, "Error in eval '$eval_str': $@");
		return 0;
	}
	#LogData(DEBUG, "Got result of '$eval_res'");
	return $eval_res;
}

sub StringSubst {
	my $text=shift(@_);

	if(!IsString($text)) {
		return EvalString($text);
	}
	($text)=$text=~/^\s*"(.+)"\s*$/;

	while(my($f,$n)=$text=~/(\[(\S+)\])/) {
		my $v=GetGameVar($n);
		if(!defined $v) {
			$v = '!!INVALID!!';
		}
		$text=~s/\Q$f\E/$v/g;
	}

	$text=~s/\\n/\n/g;
	return $text;
}

sub SayLine {
	my($ref, $text)=@_;
	LogData(DEVALL, "%s saying \"%s\"", $ref->{'DisplayName'}, $text);

	$text = StringSubst($text);
	DrawSpeech($ref, $text);
	$lastsayer = $ref;
	return 1;
}

sub SkipText {
	$lastsayer = undef;
	HideSprite('_SayerText');
	HideSprite('_SayerSprite');
}


1;











