package Apache::AxKit::PipeCtrl;

use Apache::AxKit::Pipeline;
use Apache::AxKit::Pipeline::Trace;
use Apache::AxKit::Pipeline::Language;
use Apache::AxKit::Pipeline::Head;
use Apache::AxKit::Pipeline::Cache;
use Apache::AxKit::Pipeline::DoStyles;
use Apache::AxKit::Pipeline::Transformer;
use Apache::AxKit::Exception;
use AxKit;
use strict;

sub new{
	my $class = shift;
		
	my $self = {
		pre_styles		=> [],
		post_styles		=> [],
	};
	bless $self, $class;
	return $self;
}

sub add_styles{
	my $self = shift;
	foreach my $style (@_) {
		$self->add_style( %$style )
	}
}

sub add_style{
	my $self = shift;
	my %args = @_;
	my $type 	= $args{'type'};
	my $href 	= $args{'href'};
	my $module	= $args{'module'};
	my $prepend = $args{'prepend'};
		
	if($prepend) {
		push(@{$self->{'pre_styles'}}, {
			type	=> $type,
			href	=> $href,
			module	=> $module,
		});		
	} else {
		push(@{$self->{'post_styles'}}, {
			type	=> $type,
			href	=> $href,
			module	=> $module,
		});
	} 
	return $self;		
}

sub send{
	my ( $self, $r, $provider ) = @_;
	return $self->create_pipeline($r, $provider, 0)->send( $r );
}

sub get_subrequest_ref{
	my ( $self, $r, $provider ) = @_;
	return $self->create_pipeline($r, $provider, 1)->get_strref( $r );
}



sub create_pipeline{
	my ($self, $r, $provider, $no_output) = @_;

    my $styles = $self->extract_styles($r, $provider);
        
    $self->add_styles( @$styles );
    
	my ($transformer, $doit) = AxKit::get_output_transformer();
	
	my $lastinchain = !($no_output || $doit || lc($r->dir_config('Filter')) eq 'on' );
		
	# Add any pre/post defined styles into main styles list.

	AxKit::Debug(3, "PipeCtrl: create_pipeline()");

	my @styles;

	push(@styles, @{$self->{'pre_styles'}} );
	push(@styles, @{$self->{'post_styles'}} );
	
	# Clear pre/post style list.
	
	$self->{'pre_styles'} 	= [];
	$self->{'post_styles'} 	= [];

	# passthru interface.... only take the first N - 1 styles.
	# So N=1 is the provider only. N=2 is provider + first stylesheet....
					
	if( my $p = $r->notes()->{'axkit_passthru'} && $lastinchain ) {
		my @passthru = splice( @styles, 0, $p - 1 );
		@styles = @passthru;
	}
	
	my $i = 1;
	
	# Set pipeline stage id.
	# Do this before trace, so trace picks up upstream stage id.
	
	foreach (@styles) {
		$_->{'stage'} = $i++;
	}
	
	# Cleaner interface between provider and pipeline.
	# Ideal would be to extend providers, or have a ProviderPlus
	# which inherits from Pipeline.
	
	unshift(@styles, {
		module 	=> 'Apache::AxKit::Pipeline::Head',
		href	=> undef,
	});
		
	if( $AxKit::Cfg->TraceIntermediate ) {
		
		# Add a trace module between each style.
		# No stage id, will use upstream() id.
				
		@styles = map { 
			( $_, 
				{ 	module 	=> 'Apache::AxKit::Pipeline::Trace',
					href  	=> undef }, 			  
			) }   @styles;			
	}

	
	my $pipeline = $provider;
	
	foreach ( @styles ) {
		$pipeline = $self->create_processor($r, $_ )->upstream( $pipeline );		
		if($pipeline->enable_dynamic_styles()) {
			$pipeline = Apache::AxKit::Pipeline::DoStyles->new()->upstream( $pipeline );
		} 
	}

				
	# always ensure pipeline is cache enabled.
	# so add one on the end, unless either there already is a cache somewhere in the pipeline
	# or the cache is disabled.
	
	if( !$AxKit::Cfg->NoCache() && $pipeline->cache_required()) {
		 AxKit::Debug(4, "PipeCtrl: adding final cache");
		$pipeline = Apache::AxKit::Pipeline::Cache->new()->upstream($pipeline);
	}

	if($lastinchain) {
		unless( $pipeline->output_binary() ) {
		
			if( $doit ) {
				AxKit::Debug(4, "PipeCtrl: adding Output Transformer");
				$pipeline = Apache::AxKit::Pipeline::Transformer->new()->upstream($pipeline);
			}
		}
	}
	
	
	AxKit::Debug(4, "PipeCtrl: Running Initialisation phase");

	$pipeline->pipeline_init($r);	# allow things like XSP to build code before cache runs.
	
	return $pipeline;
}

sub post_styles_pending{
	return scalar @{shift->{'post_styles'}};
}

sub pre_styles_pending{
	return scalar @{shift->{'pre_styles'}};
}	

sub _create_pipeline{
	my ( $self, $r, $provider, @styles ) = @_;
	my $i = 1;
	foreach (@styles) {
		$_->{'stage'} = $i++;
	}

	unshift(@styles, {
		module 	=> 'Apache::AxKit::Pipeline::Head',
		href	=> undef,
	});

	my $pipeline = $provider;
		
	foreach my $style ( @styles ) {
		$pipeline = $self->create_processor($r, $style )->upstream($pipeline);	
		if($pipeline->enable_dynamic_styles()) {
			$pipeline = Apache::AxKit::Pipeline::DoStyles->new()->upstream( $pipeline );
		}
			
	}
	
	AxKit::Debug(4, "PipeCtrl: Running Initialisation phase");	
	$pipeline->pipeline_init($r);
	
	return $pipeline;
}	

sub create_pre_pipeline{
	my ($self, $r, $provider) = @_;

	AxKit::Debug(3, "PipeCtrl: Creating pre-styles pipeline");

	my @styles = @{$self->{'pre_styles'}};
	$self->{'pre_styles'} = [];

	return $self->_create_pipeline( $r, $provider, @styles );
}

sub create_post_pipeline{
	my ($self, $r, $provider) = @_;

	AxKit::Debug(3, "PipeCtrl: Creating post-styles pipeline");		
	my @styles = @{$self->{'post_styles'}};
	$self->{'post_styles'} 	= [];

	return $self->_create_pipeline( $r, $provider, @styles );
}

sub create_processor{
	my ($self, $r, $style ) = @_;

	AxKit::Debug(4, "styles: ", $style->{module}, "(", $style->{href}, ")");			
	no strict 'refs';
	my $processor = $style->{module} || $AxKit::Cfg->StyleMap->{$style->{type}};

    AxKit::load_module($processor);
	
	return UNIVERSAL::isa( $processor, 'Apache::AxKit::Language') ? 
		Apache::AxKit::Pipeline::Language->new(
			href		=> $style->{'href'},
			module		=> $processor,
			stage		=> $style->{'stage'},
			
		) : 
		$processor->new(
			stage		=> $style->{'stage'},
			href		=> $style->{'href'},
		);	
					
}	

sub extract_styles{
	my ($self, $r, $provider) = @_;

    # get preferred stylesheet and media type
    my ($preferred, $media) = AxKit::get_style_and_media();
    AxKit::Debug(2, "media: $media, preferred style: $preferred");

    my $key = Apache::AxKit::Cache->new($r, $provider->key() . 
    	($r->path_info() || ''), $preferred, $media )->key();

    AxKit::Debug(2, "getting styles and external entities from the XML");
    # get styles/ext_ents from cache or re-parse

    if (exists($AxKit::Stash{$key}) && !$provider->has_changed($AxKit::Stash{$key}{mtime}))
    {
        AxKit::Debug(3, "styles cached");
        return $AxKit::Stash{$key}{'styles'};
    }
    else {
        AxKit::Debug(3, "styles not cached - calling \$provider->get_styles()");

    	if ($preferred eq '#default') {
        	undef $preferred;
   	 	}

		my @styles;

		# need to extract the following from the XML file:
		#   DocType Public Identifier
		#   DTD filename
		#   Root element name (including namespace)
		# use three element array @$vals
		if( $AxKit::Cfg->IfAxAddFooProcessors($media, $preferred) || ! $AxKit::Cfg->IgnoreStylePI()) {
		
			my ($doctype, $dtd, $root, $xml_styles) = $provider->get_xml_info( $media, $preferred );
		
			foreach my $style (@$xml_styles) {
				$style->{title} ||= '#default';
			}
		
			# Let GetMatchingProcessors to process the @$styles array
			{
			  local $^W; # suppress "Use of uninitialized value" warnings
			  AxKit::Debug(4, "Calling GetMatchingProcessors with ($media, $preferred, $doctype, $dtd, $root)");
			}
			@styles = $AxKit::Cfg->GetMatchingProcessors($media, $preferred, $doctype, $dtd, $root, $xml_styles, $provider);
		} else{
			{
			  local $^W; # suppress "Use of uninitialized value" warnings
			  AxKit::Debug(4, "No AxAdd*Processors detected, bypassing get_styles()");
			  AxKit::Debug(4, "Calling GetMatchingProcessors with ($media, $preferred).");
			}
	
			@styles = $AxKit::Cfg->GetMatchingProcessors($media, $preferred, undef, undef, undef, [], $provider);
		
		}
		if (!@styles) {
			throw Apache::AxKit::Exception::Declined( reason => sprintf("No styles defined for '%s'", $provider->key() ));
		}
		
		# get mime-type => module mapping
		my $style_mapping = $AxKit::Cfg->StyleMap;
	
		AxKit::Debug(3, "get_styles: loading style modules");    
		for my $style (@styles) {
			my $mapto;
			AxKit::Debug(4, "get_styles: looking for mapping for style type: '$style->{type}'");
			if (!( $mapto = $style_mapping->{ $style->{type} } )) {
				throw Apache::AxKit::Exception::Declined(
						reason => "No implementation mapping available for type '$style->{type}'"
						);
			}
	
			$style->{module} = $mapto;
	
			# first load module if it's not already loaded.
			eval {
				AxKit::load_module($mapto);
			};
			if ($@) {
				throw Apache::AxKit::Exception::Error(
						-text => "Load of '$mapto' failed with: $@"
						);
			}
		}
   
        $AxKit::Stash{$key} = {
            styles => \@styles,
            mtime => $provider->mtime(),
       	} unless $r->dir_config('AxDisableGetStylesCache');

        return \@styles;
    }	
}




1;
