package HTML::Template::Extension;

$VERSION 			= "0.25";
sub Version 		{ $VERSION; }

use HTML::Template;
@HTML::Template::Extension::ISA = qw(HTML::Template);



use Carp;
use Data::Dumper;
use FileHandle;
use vars qw($DEBUG $DEBUG_FILE_PATH);
use strict;

$DEBUG 				= 0;
$DEBUG_FILE_PATH	= '/tmp/HTML-Template-Extension.debug.txt';

my %fields 	=
			    (
			    	plugins => {},
					plugins_cid => {},
			    	filename => undef,
			    	scalarref=>undef,
			    	arrayref=>undef,
			    	filehandle=>undef,
					filter_internal	=> [],
			     );
     
my @fields_req	= qw//;
my $DEBUG_FH;     

sub new
{   
	my $proto = shift;
    my $class = ref($proto) || $proto;
    # aggiungo il filtro
    my $self  = {};
    # I like %TAG_NAME% syntax
    push @_,('vanguard_compatibility_mode' => 1);
    # no error if a tag present in html was not set
    push @_,('die_on_bad_params' => 0);
    # enable loop variable items
    push @_,('loop_context_vars' => 1);
	# if don't exists neither filename, nor filehandle, nor scalarref,
	# nor arrayref, add an empty scalarref to correct init HTML::Template
	my %check = @_;
	push @_,('scalarref' => \'') unless (	exists $check{'filename'}   || 
											exists $check{'filehandle'} ||
											exists $check{'scalarref'}  || 
											exists $check{'arrayref'});
	bless $self,$class;
    $self->_init_local(@_);
#	$self->_loadDynamicModule;
#	$self->_reloadFilter;
    my $htmpl = $class->HTML::Template::new(@_);
    foreach (keys(%{$htmpl})) {
    	$self->{$_} = $htmpl->{$_};
    }
    bless $self,$class;
    return $self;
}							

sub _init_local {
	my $self = shift;
	my (%options) = @_;
	# add plugins
	# Assign default options
	while (my ($key,$value) = each(%fields)) {
		$self->{$key} = $self->{$key} || $value;
    }
	# add plugins
	foreach (@{$options{plugins}}) {
		$self->plugin_add($_);
	}
	delete $options{plugins};
    # Assign options
    while (my ($key,$value) = each(%options)) {
    	$self->{$key} = $value
    }
    # Check required params
    foreach (@fields_req) {
		croak "You must declare '$_' in " . ref($self) . "::new"
				if (!defined $self->{$_});
	}
	$self->{DEBUG_FH} = new FileHandle ">>$DEBUG_FILE_PATH" if ($DEBUG);
	#$self->push_filter;										
}

sub output {
	# redefine standard output function
	my $self = shift;
	my %args = @_;
	if ($self->{_auto_parse}) {
		$self->reloadFile();
	}
	if (exists $args{as}) {
		# delete old params settings
		$self->SUPER::clear_params();
		my %as = %{$args{as}};
		foreach (keys %as) {
			$self->SUPER::param($_ => $as{$_});
		}
	}
	my $output = $self->SUPER::output(%args);
	print {$self->{'DEBUG_FH'}} Data::Dumper::Dumper($self) if ($DEBUG);
	return $output;
}

sub html {
	my $self 		 = shift;
	my %args 		 = (defined $_[0]) ? %{$_[0]} : ();
	$self->{filename}= $_[1] if (defined $_[1]);
	if ( defined $self->{filename} 
			&& (
				!defined $self->{options}->{filename}
					||	$self->{filename} ne $self->{options}->{filename} 
				)
			|| $self->{_auto_parse}
		) {
		$self->reloadFile();
	}
	return $self->output('as' => \%args);
}

sub filename { 
	my $s=shift;
	if (@_)  {
		my $new_file = shift;
		if ($s->{filename} ne $new_file) {
			$s->{filename} = $new_file;
			# reload local file
			$s->{_auto_parse} = 1;	
			# remove other text storage
			delete($s->{scalarref});
			delete($s->{arrayref});
			delete($s->{filehandle});
		}
	};
	return $s->{filename};
}

sub scalarref { 
	my $s=shift;
	if (@_)  {
		$s->{scalarref} = shift;
		# reload local file
		$s->{_auto_parse} = 1;	
		delete($s->{filename});
		delete($s->{options}->{filename});
		delete($s->{arrayref});
		delete($s->{filehandle});
	};
	# remove other text storage
	return $s->{scalarref};
}

sub arrayref { 
	my $s=shift;
	if (@_)  {
		$s->{arrayref} = shift;
		# reload local file
		$s->{_auto_parse} = 1;	
		# remove other text storage
		delete($s->{scalarref});
		delete($s->{filename});
		delete($s->{options}->{filename});
		delete($s->{filehandle});
	};
	return $s->{arrayref};
}

sub filehandle { 
	my $s=shift;
	if (@_)  {
		$s->{filehandle} = shift;
		# reload local file
		$s->{_auto_parse} = 1;	
		# remove other text storage
		delete($s->{scalarref});
		delete($s->{arrayref});
		delete($s->{filename});
		delete($s->{options}->{filename});
	};
	
	return $s->{filehandle};
}

sub reloadFile {
	my $self = shift;
	$self->{_auto_parse} = 0;
	if ( defined $self->{filename} 
			&& ( !defined $self->{options}->{filename}
					|| $self->{filename} ne $self->{options}->{filename}
				)
		) {
		$self->{options}->{filename} = $self->{filename};
		my $filepath = $self->_find_file($self->{filename});  
		$self->{options}->{filepath} = $self->{filename};
	} elsif (exists($self->{scalarref})) {
    	$self->{options}->{scalarref} = $self->{scalarref};
	} elsif (exists($self->{arrayref})) {
		$self->{options}->{arrayref}=$self->{arrayref};
	} elsif (exists($self->{filehandle})) {
		$self->{options}->{filehandle} = $self->{filehandle};
	}
	$self->{filter} = $self->{filter_internal};
	$self->{options}->{filter}= $self->{filter};
	$self->_init_template();
	# local caching params
	my %params;
	my @parname = $self->param();
	foreach (@parname) {
		$params{$_} = $self->param($_);
	}
	$self->_parse();
	# reassign params
	foreach (keys(%params)) {
		$self->param($_=> $params{$_});
	}
	# now that we have a full init, cache the structures if cacheing is
	# on.  shared cache is already cool.
	if($self->{options}->{file_cache}){
	$self->_commit_to_file_cache();
	}
	$self->_commit_to_cache() if (($self->{options}->{cache}
	                            and not $self->{options}->{shared_cache}
	                            and not $self->{options}->{file_cache}) or
	                            ($self->{options}->{double_cache}) or
	                            ($self->{options}->{double_file_cache}));
}


sub _reloadFilter {
	my $self = shift;
	undef $self->{filter_internal} ;
	$self->{plugins_cid} = {};
	# plugin priority filter
	{
		foreach (values %{$self->{plugins}}) {
			$self->_pushModule($_);
	    }
    }
}

#sub _loadDynamicModule {
#	my $self = shift;
#	{
#		foreach (keys %{$self->{plugins}}) {
#			$self->_initModule($_);
#	    }
#	}
#} 

sub _importModule {
	my $self		= shift;
	my $module_name	= shift;
	
	$module_name	=~s/::/\//g;
	require $module_name . ".pm";
}

sub _initModule {
	my $self		= shift;
	my $module		= shift;
	if (ref($module) eq '') {
		$self->_importModule($module); 
		no strict "refs";
		&{$module . "::init"}($self);
	} else {
		$module->init($self);
	}
}

sub _pushModule {
	my $self		= shift;
	my ($module, $module_name) 	= $self->_module_info(shift);
	if (exists $self->{plugins_cid}->{$module_name}) {
		# esiste gia' qualcosa di caricato...lo devo scaricare
		# prima di poter fare qualsiasi cosa
		my @code_ids = @{$self->{plugins_cid}->{$module_name}};
		foreach (@code_ids) {
			$self->_remove_filter_id($_);
		}
		delete $self->{plugins_cid}->{$module_name};
	}
	# count coderef items
	my $pre_code_count = $self->{filter_internal} ? scalar(@{$self->{filter_internal}})-1 : -1 ;
	if (ref($module) eq '') {
		no strict "refs";
        &{$module . "::push_filter"}($self);
	} else {
		$module->push_filter($self);
	}
	# count coderef items after push_filter
	my $post_code_count = $self->{filter_internal} ? scalar(@{$self->{filter_internal}})-1 : -1;
	return if ($post_code_count == $pre_code_count);
	return if ($post_code_count <0);
	$pre_code_count++;
	# so this module as add post-pre code items
	if (exists($self->{plugins_cid}->{$module_name})) {
		push @{$self->{plugins_cid}->{$module_name}},($pre_code_count .. 
				$post_code_count)
	} else {
		$self->{plugins_cid}->{$module_name} = [$pre_code_count ..
                $post_code_count];
	}
}


sub filter { my $s=shift; return @_ ? ($s->{filter_internal}=shift) : $s->{filter_internal} }

sub plugin_add { 
	my $s		= shift; 
	if (@_)  {
		my ($module, $module_name) 	= $s->_module_info(shift);
		# plugin gia caricato
		return if (exists $s->{plugins}->{$module_name});
		$s->{plugins}->{$module_name} = $module;
		# init module
		$s->_initModule($module);
		# add filter from added module to me
		$s->_pushModule($module);
		# add the array id of added code 
		$s->{_auto_parse} = 1;
	};
	return $s->{plugins}
}

sub plugin_remove {
	my $s		= shift;
	my ($module, $module_name) 	= $s->_module_info(shift);
	delete $s->{plugins}->{$module_name};
	if (exists $s->{plugins_cid}->{$module_name}) {
		my @code_ids = @{$s->{plugins_cid}->{$module_name}};
		foreach (@code_ids) {
			$s->_remove_filter_id($_);
		}
	}
	delete $s->{plugins_cid}->{$module_name};
	$s->{_auto_parse} = 1;
}

sub _remove_filter_id {
	my $s		= shift;
	my $f_id	= shift;
	my @a		= ();
	push @a, @{$s->{filter_internal}}[0 .. $f_id-1] if ($f_id>0);
	push @a, @{$s->{filter_internal}}[$f_id+1 .. $#{$s->{filter_internal}}]
		if ($f_id< $#{$s->{filter_internal}});
	$s->{filter_internal} = \@a;
}

sub plugins_clear { 
	my $s = shift;
	undef $s->{plugins};
	undef $s->{options}->{plugins};
	undef $s->{filter_internal};
	$s->{plugins_cid} = {};
	return $s->{plugins};
}

sub DESTROY {
}

sub AUTOLOAD {
	my $self = shift;
	my @procs = split(/::/,$HTML::Template::Extension::AUTOLOAD);
	return if (scalar(@procs)<3);
	my $proc = $procs[-1];
	my $value;
	no strict "refs";
	foreach my $module (values %{$self->{plugins}}) {
		my $ret;
		if (ref($module)) {
			#$ret	= eval { return $module->
		} else {
			$ret=  eval { return &{"${module}::$proc"}($self,@_) };
		}
		if (!$@) { return  $ret };
	};
}

sub _module_info {
		my $self 	= shift;
		my $module 	= shift;
		my $module_name;
		if (ref($module)) {
			$module_name = ref($module);
		} else {
			$module = "HTML::Template::Extension::$module" 
				if ($module!~/::/);
			$module_name = $module;
		}
		return ($module,$module_name);
}



1;
