use strict;
use warnings;

package EJS::Template::Parser::Context;
use base 'EJS::Template::Base';

use EJS::Template::Runtime;

my $states = {
	'INIT' => {
		key => 'INIT', method => '_in_init',
		js_open => '', js_close => '',
	},
	'TEXT' => {
		key => 'TEXT', method => '_in_text',
		js_open => 'print("', js_close => '");',
	},
	'<%' => {
		key => '<%'  , method => '_in_script',
		js_open => '', js_close => '',
	},
	'<%=' => {
		key => '<%=' , method => '_in_script',
		js_open => 'print(' , js_close => ');',
	},
	'"' => {
		key => '"', method => '_in_quote',
		js_open => '"', js_close => '"',
	},
	"'" => {
		key => "'", method => '_in_quote',
		js_open => "'", js_close => "'",
	},
	'/*' => {},
	'//' => {},
};

=head2 new

Creates a new parser context.

=cut

sub new {
	my ($class, $config) = @_;
	my $self = $class->SUPER::new($config);
	
	$self->{stack} = [];
	$self->{result} = [];
	
	$self->{default_escape} = do {
		my $name = $self->config('escape') || '';
		
		if ($name eq '' || $name eq 'raw') {
			'';
		} else {
			$EJS::Template::Runtime::ESCAPES{$name} || '';
		}
	};
	
	$self->_push_state('INIT');
	return $self;
}

=head2 read_line

Parses a line.

=cut

sub read_line {
	my ($self, $line) = @_;
	
	while ($line =~ m{(.*?)((^\s*)?<%(?:(?:\s*:\s*\w+\s*)?=)?|%>(\s*?(?:\n|$))?|["']|/\*|\*/|//|\n|$)}g) {
		my ($text, $mark, $left, $right) = ($1, $2, $3, $4);
		my $escape;
		
		if ($mark =~ s/<%\s*:\s*(\w+)\s*=/<%=/) {
			my $name = $1;
			$escape = $EJS::Template::Runtime::ESCAPES{$name} || '';
		} elsif ($mark eq '<%=') {
			$escape = $self->{default_escape};
		}
		
		$mark =~ s/\s+(<%=?)/$1/;
		$mark =~ s/(%>)\s+/$1/;
		
		my $opt = {
			left   => $left  ,
			right  => $right ,
			escape => $escape,
		};
		
		$self->_handle_token($text) if $text ne '';
		$self->_handle_token($mark, $opt) if $mark ne '';
	}
}

=head2 result

Retrieves the result.

=cut

sub result {
	my ($self) = @_;
	$self->_pop_state() until @{$self->{stack}} <= 1;
	return $self->{result};
}

sub _handle_token {
	my ($self, $token, $opt) = @_;
	my $method = $self->{stack}[-1]{state}{method};
	return $self->$method($token, $opt);
}

sub _top_key {
	my ($self) = @_;
	return $self->{stack}[-1]{state}{key};
}

sub _top_opt {
	my ($self) = @_;
	return $self->{stack}[-1]{opt} || {};
}

sub _append_result {
	my ($self, $text) = @_;
	push @{$self->{result}}, $text;
}

sub _push_state {
	my ($self, $state_key, $opt) = @_;
	my $state = $states->{$state_key};
	$self->_append_result($state->{js_open}) if $state->{js_open} ne '';
	my $entry = {state => $state, opt => $opt};
	push @{$self->{stack}}, $entry;
}

sub _pop_state {
	my ($self) = @_;
	my $entry = pop @{$self->{stack}};
	my $state = $entry->{state};
	$self->_append_result($state->{js_close}) if $state->{js_close} ne '';
}

sub _in_init {
	my ($self, $token, $opt) = @_;
	
	if ($token eq '<%') {
		if (defined $opt->{left}) {
			$opt->{ltrim} = {};
			
			if ($opt->{left} ne '') {
				$self->_append_result($opt->{left});
				$opt->{ltrim}{index} = $#{$self->{result}};
			}
		}
		
		$self->_push_state($token, $opt);
	} elsif ($token eq "<%=") {
		if (defined $opt->{left} && $opt->{left} ne '') {
			$self->_push_state('TEXT');
			$self->_append_result($opt->{left});
			$self->_pop_state();
		}
		
		$self->_push_state($token, $opt);
		
		if (my $escape = $opt->{escape}) {
			$self->_append_result(qq{EJS.$escape(});
		}
	} elsif ($token eq "\n") {
		$self->_push_state('TEXT');
		$self->_append_result("\\n");
		$self->_pop_state();
		$self->_append_result("\n");
	} else {
		$token =~ s/([\\"])/\\$1/g;
		$self->_push_state('TEXT');
		$self->_append_result($token);
	}
}

sub _in_text {
	my ($self, $token, $opt) = @_;
	
	if ($token eq '<%') {
		$self->_pop_state();
		$self->_push_state($token, $opt);
	} elsif ($token eq '<%=') {
		$self->_pop_state();
		$self->_push_state($token, $opt);
		
		if (my $escape = $opt->{escape}) {
			$self->_append_result(qq{EJS.$escape(});
		}
	} elsif ($token eq "\n") {
		$self->_append_result("\\n");
		$self->_pop_state();
		$self->_append_result("\n");
	} else {
		$token =~ s/([\\"])/\\$1/g;
		$self->_append_result($token);
	}
}

sub _in_script {
	my ($self, $token, $opt) = @_;
	
	if ($token eq '"' || $token eq "'") {
		$self->_push_state($token);
	} elsif ($token eq '%>') {
		if (my $ltrim = $self->_top_opt->{ltrim}) {
			if (defined $opt->{right}) {
				if ($opt->{right} ne '') {
					$self->_append_result($opt->{right});
				}
			} else {
				if (defined(my $idx = $ltrim->{index})) {
					my $left = $self->{result}[$idx];
					$self->{result}[$idx] = qq{print("$left");};
				}
			}
			
			$self->_pop_state();
		} else {
			if ($self->_top_opt->{escape}) {
				$self->_append_result(qq{)});
			}
			
			$self->_pop_state();
			
			my $right = $opt->{right};
			
			if (defined $right && $right ne '') {
				if ($right =~ s/\n$//) {
					$self->_append_result(qq{print("$right\\n");\n});
				} else {
					$self->_append_result(qq{print("$right");});
				}
			}
		}
	} else {
		$self->_append_result($token);
	}
}

sub _in_quote {
	my ($self, $token, $opt) = @_;
	
	if ($token eq $self->_top_key) {
		$self->_pop_state();
	} else {
		$self->_append_result($token);
	}
}

1;
