package WebPAC::Parser;

use warnings;
use strict;


use PPI;
use PPI::Dumper;
use Data::Dump qw/dump/;
use File::Slurp;

use base qw/WebPAC::Common WebPAC::Normalize/;

=head1 NAME

WebPAC::Parser - parse perl normalization configuration files and mungle it

=head1 VERSION

Version 0.03

=cut

our $VERSION = '0.03';

=head1 SYNOPSIS

FIXME

=head1 FUNCTIONS

=head2 new

Create new parser object.

  my $parser = new WebPAC::Parser(
  	config => new WebPAC::Config(),
	base_path => '/optional/path/to/conf',
  );

=cut

sub new {
	my $class = shift;
	my $self = {@_};
	bless($self, $class);

	my $log = $self->_get_logger();

	$log->logdie("config isn't WebPAC::Config") unless ($self->{config} && $self->{config}->isa('WebPAC::Config'));

	$log->logdie("can't iterate_inputs over this config object") unless ($self->{config}->can('iterate_inputs'));

	$self->read_sources;

	$self->{config}->iterate_inputs( sub {
		my ($input, $database) = @_;
		return unless $self->valid_database_input($database, $input->{name});
		$self->parse_lookups($database,$input->{name});
	} );

	$self ? return $self : return undef;
}

=head2 read_sources

  my $source_files = $parser->read_sources;

Called by L</new>.

=cut

sub read_sources {
	my $self = shift;

	my $log = $self->_get_logger();

	my $nr = 0;

	$self->{config}->iterate_inputs( sub {
		my ($input, $database) = @_;

		my $path = $input->{normalize}->{path} || return;
		my $full = $self->{base_path} ? $self->{base_path} . '/' . $path : $path;

		$log->logdie("normalization input $full doesn't exist") unless (-e $full);

		my $s = read_file( $full ) || $log->logdie("can't read $full: $!");

		my $input_name = $input->{name} || $log->logdie("can't deduce name of input: ", dump( $input ));

		$log->debug("$database/$input_name: adding $path");

		$self->{valid_inputs}->{$database}->{$input_name} = {
			source => $s,
			path => $full,
			usage => 0,
		} unless defined($self->{valid_inputs}->{$database}->{$input_name});

		$self->{valid_inputs}->{$database}->{$input_name}->{usage}++;

		$nr++;
	} );

	$log->debug("found $nr source files");

	return $nr;
}

=head2 parse_lookups

  $parser->parse_lookups($database,$input);

=cut

sub parse_lookups {
	my $self = shift;
	my ($database, $input) = @_;

	my $log = $self->_get_logger();

	$log->logdie("invalid database $database" ) unless $self->valid_database( $database );
	$log->logdie("invalid input $input of database $database", ) unless $self->valid_database_input( $database, $input );

	my $source = $self->{valid_inputs}->{$database}->{$input}->{source};
	my $path = $self->{valid_inputs}->{$database}->{$input}->{path};

	$log->logdie("no source found for database $database input $input path $path") unless ($source);

	$log->info("parsing lookups for $database/$input from $path");

	my $Document = PPI::Document->new( \$source ) || $log->logdie("can't parse source:\n", $self->{source});

	$Document->prune('PPI::Token::Whitespace');
	#$Document->prune('PPI::Token::Operator');

	# Find all the named subroutines

	$self->{_lookup_errors} = ();

	sub _lookup_error {
		my $self = shift;
		my $msg = shift;
		$self->_get_logger->logconfess("error without message?") unless ($msg);
		push @{ $self->{_lookup_errors} }, $msg;
		return '';
	}

	$Document->find( sub {
			my ($Document,$Element) = @_;

			$Element->isa('PPI::Token::Word') or return '';
			$Element->content eq 'lookup' or return '';

			$log->debug("expansion: ", $Element->snext_sibling);

			my $args = $Element->snext_sibling;
		
			my @e = $args->child(0)->elements;
			$log->logdie("hum, expect at least 8 elements, got ", scalar @e, " in $args") if ($#e < 8);

			if ($log->is_debug) {
				my $report = "found " . scalar @e . " elements:\n";

				foreach my $i ( 0 .. $#e ) {
					$report .= sprintf("# %-2d: %-30s %s\n", $i, ( $e[$i] || 'undef' ), $e[$i]->class );
				}

				$log->debug($report);
			}

			my $key_element = $e[8]->clone;

			$log->logdie("key element must be PPI::Structure::Block") unless $key_element->isa('PPI::Structure::Block');

			$log->debug("key part: ", $key_element);

			my @key;

			$key_element->find( sub {
				my $e = $_[1] || die "no element?";
				$e->isa('PPI::Token::Word') or return '';
				$e->content eq 'rec' or return '';

				my $kf = $e->snext_sibling;

				$log->debug("key fragment = $kf");

				push @key, eval $kf;
				$log->logdie("can't eval { $kf }: $@") if ($@);

				return 1;
			});

			my $key = join('-', @key ) || $log->logdie("no key found!");

			$log->debug("key = $key");

			my $create = ' 
				$coderef = ' . $e[7] . $e[8] . ';
				foreach my $v ($coderef->()) {
					next unless (defined($v) && $v ne \'\');
					push @{ $lookup->{\'' . $key . '\'}->{$v} }, $mfn;
				}
			';

			$log->debug("create: $create");

			return $self->_lookup_error("invalid database $e[3] in $path" ) unless $self->valid_database( $e[3] );
			return $self->_lookup_error("invalid input $e[5] of database $e[3] in $path", ) unless $self->valid_database_input( $e[3], $e[5] );

			$self->add_lookup_create( $e[3], $e[5], $create );

			if ($#e < 10) {
				$e[8]->insert_after( $e[8]->clone );
				$e[8]->insert_after( $e[7]->clone );
				$e[8]->insert_after( $e[6]->clone );
			}

			$e[7]->remove;
			$e[8]->insert_before( new PPI::Token::Quote::Single( "'$key'" ) );
			$e[8]->remove;


			$log->debug(">>> ", $Element->snext_sibling);
	});

	my $source = $Document->serialize;
	$log->debug("create: ", dump($self->{_lookup_create}) );
	$log->debug("normalize: $source");

	$self->{_normalize_source}->{$database}->{$input} = $source;

	if ($self->{debug}) {
		my $Dumper = PPI::Dumper->new( $Document );
		$Dumper->print;
	}

	$log->error("Parser errors:\n", join("\n",@{ $self->{_lookup_errors} }) ) if ($self->{_lookup_errors});

	return 1;
}

=head2 add_lookup_create

  $parse->add_lookup_create($database,$input,$source);

=cut

sub add_lookup_create {
	my $self = shift;
	my ($database,$input,$source) = @_;
	$self->{_lookup_create}->{$database}->{$input} .= $source;
}


=head2 valid_database

  my $ok = $parse->valid_database('key');

=cut

sub valid_database {
	my $self = shift;

	my $database = shift || return;
	$database =~ s/['"]//g;

	return defined($self->{valid_inputs}->{$database});
}

=head2 valid_database_input

  my $ok = $parse->valid_database('database_key','input_name');

=cut

sub valid_database_input {
	my $self = shift;

	my ($database,$input) = @_;
	$database =~ s/['"]//g;
	$input =~ s/['"]//g;

	return defined($self->{valid_inputs}->{$database}->{$input});
}

=head1 AUTHOR

Dobrica Pavlinusic, C<< <dpavlin@rot13.org> >>

=head1 COPYRIGHT & LICENSE

Copyright 2006 Dobrica Pavlinusic, All Rights Reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut

1; # End of WebPAC::Parser
