package Lire::ReportParser::ChartWriter;

use strict;

use vars qw/ $VERSION @ISA /;

use Lire::Config;
use Lire::ReportParser;
use Lire::Program qw( :msg );

use GD::Graph;
use GD::Graph::pie;
use GD::Graph::bars;
use GD::Graph::lines;

use File::Basename;
use Carp;

BEGIN {
    ($VERSION)	= '$Revision: 1.9 $' =~ m!Revision: ([.\d]+)!;
    @ISA = qw( Lire::ReportParser );
}

sub new {
    my $proto = shift;
    my $class = ref $proto || $proto;
    my $self  = $class->SUPER::new( @_, all_entries => 1 );

    my %args  = @_;

    $args{format}    ||= $Lire::Config::LR_IMAGE_FORMAT;
    $args{outputdir} ||= ".";

    die "unsupported file format: $args{format} (png, gif or jpeg)\n"
      unless $args{format} =~ /^(png|gif|jpeg)$/i;

    unless ( grep { lc $_ eq $args{format} } GD::Graph->export_format ) {
	my @fmt = grep { /^(png|gif|jpeg)$/i } GD::Graph->export_format;
	die "format $args{format} isn't supported by your version of libgd, try one of ", 
	  join( ", ", @fmt );
    }
    die "$args{outputdir} is not a directory\n"
      unless -d $args{outputdir};

    die "can't write in $args{outputdir}\n"
      unless -w $args{outputdir};

    $self->{format}	    = lc $args{format};
    $self->{outputdir}	    = $args{outputdir};
    $self->{graph_width}    = $args{graph_width} || 400;
    $self->{graph_height}   = $args{graph_height} || 300;
    $self->{y_ticks}	    = $args{number_of_y_ticks} || 5;
    $self->{max_label_len}  = $args{max_label_length} || 20;
    $self;
}

sub parse_start {
    my ($self ) = @_;
    # Register handler for XMLDcl and DocType event, so that 
    # we output them as is

    my $print_it = sub {
	print $_[0]->original_string(), "\n";
    };

    $self->expat->setHandlers( XMLDecl    => $print_it,
			       Comment    => $print_it,
			     );
    1;
}

# Print an identical copy of the XML report.
# We will add image and file element when we create graphics.
sub element_start {
    my ( $self, $name, %attr ) = @_;

    print $self->original_string;

    return 0; # Continue normal processing
}

sub element_end {
    my ( $self, $name ) = @_;

    print $self->original_string;

    return 0; # Continue normal processing
}

sub pcdata {
    my ( $self, $str ) = @_;

    print $self->original_string;
    return 0; # Continue normal processing
}

sub ignorable_ws {
    my ( $self, $str ) = @_;

    print $self->original_string;
    return 0; # Continue normal processing
}

sub table_start {
    my ( $self ) = shift;

    $self->SUPER::table_start( @_ );

    $self->{write_chart} = defined $self->current_charttype;
    $self->{keep_only}	 = $self->current_group_entry_show;

    if ($self->{write_chart}) {
	$self->{x_data} = [];
	$self->{y_data} = [];
    }
}

sub table_end {
    my ( $self ) = shift;

    $self->SUPER::table_end( @_ );

    return unless $self->{write_chart};

    unless ( @{$self->{x_data}} && @{$self->{y_data}} ) {
	lr_info( "skipping chart for ", $self->current_type , " because table is empty" );
	return;
    }

    my $graph;
    my %common_opts = ( x_labels_vertical => 1,
			transparent => 1,
		      );
  SWITCH:
    for ( $self->current_charttype ) {
	/^bars$/ && do {
	    $graph = new GD::Graph::bars( $self->{graph_width},
					  $self->{graph_height} );
	    $graph->set( bar_spacing  => 4,
			 shadow_depth => 2,
			 %common_opts );
	    last SWITCH;
	};
	/^lines$/ && do {
	    $graph = new GD::Graph::lines( $self->{graph_width},
					   $self->{graph_height} );
	    $graph->set( %common_opts );
	    last SWITCH;
	};
	/^pie$/ && do {
	    $graph = new GD::Graph::pie( $self->{graph_width},
					 $self->{graph_height} );
	    $graph->set( start_angle => "240",
			 %common_opts );
	    last SWITCH;
	};
	/^histogram$/ && do {
	    $graph = new GD::Graph::bars( $self->{graph_width},
					  $self->{graph_height} );
	    $graph->set( bar_spacing => 0,
			 %common_opts );
	    last SWITCH;
	};
	croak "Unknown chart type: $_\n";
    }

    if ( $self->current_charttype =~ /^bars|lines|histogram$/) {
	my $maxy = 0;
	foreach my $val ( @{$self->{y_data}} ) {
	    $maxy = $val if $val > $maxy;
	}

	$maxy = int( $maxy * 1.1);
	$maxy = $maxy + ( $self->{y_ticks} - ($maxy % $self->{y_ticks}));
	$graph->set( y_max_value    => $maxy,
		     y_tick_number  => $self->{y_ticks} );
    }

    my $img = $graph->plot( [ $self->{x_data}, $self->{y_data} ] );
    unless ( $img ) {
	warn "failed to generate chart ", $self->current_type, " (", 
	  $self->current_charttype, "): ", $graph->error, "\n";
	delete $self->{x_data};
	delete $self->{y_data};
	return;
    }
    my $basename = $self->current_subreport_count() . "-" . $self->current_type();
    $basename =~ s/\./_/g;

    my $ext = $self->{format} eq 'jpeg' ? "jpg" : $self->{format};
    my $img_file = $self->{outputdir} . "/" . $basename . "." . $ext;
    open ( GRAPH, "> $img_file" )
      or die "can't create $img_file: $!\n";
    binmode GRAPH;
    {
	no strict 'refs';
	my $type = $self->{format};
	print GRAPH $img->$type()
	  or die "can't convert graph to $type: ", $graph->error, "\n";
    }
    close GRAPH;


    my $format = uc $self->{format}; # DBX Notation are in uppercase
    my $file = basename ( $img_file );
    print qq{\n  <lire:image>\n};
    print qq{   <lire:file format="$format">$file</lire:file>\n};
    print qq{  </lire:image>};

    lr_info( "writing chart $img_file" );

    delete $self->{x_data};
    delete $self->{y_data};
}

sub handle_name {
    my ( $self, $name_rec ) = @_;

    return unless $self->{write_chart};
    if ($self->{keep_only}) {
	return unless @{$self->{x_data}} < $self->{keep_only};
    }

    my $name = $name_rec->{content};
    # Crop $name if necessary
    $name = substr( $name, 0, $self->{max_label_len} ) . "..."
      if length $name > $self->{max_label_len};

    push @{$self->{x_data}}, $name;
}

sub handle_value {
    my ( $self, $value ) = @_;

    return unless $self->{write_chart};

    if ($self->{keep_only}) {
	return unless @{$self->{y_data}} < $self->{keep_only};
    }

    my $num_value = $value->{value};
    push @{$self->{y_data}}, $num_value;
}

# keep perl happy
1;

__END__

=pod

=head1 NAME

Lire::ReportParser::ChartWriter - Lire::ReportParser processor that generate charts for all available subreport.

=head1 SYNOPSIS

    use Lire::ReportParser::ChartWriter;

    my $parser = new Lire::ReportParser::ChartWriter( format => "png" );

    eval { $parser->parsefile( "report.xml" ) };
    print "Parse failed: $@" if $@;

=head1 DESCRIPTION

This is a Lire::ReportParser processor which will generate charts for
every table that has the C<charttype> attribute set. It outputs on STDOUT
a copy of the XML report with the appropriate C<image> and C<file> elements added.

Its constructor takes the following parameters:

=over 4

=item format

Selects the format of the charts. This can be one of C<png>, C<jpg> or
C<gif>. Defaults to C<png>. The GD(3pm) module must support the
requested format.

=item outputdir

The directory where the charts will be created. Defaults to F<.>.

=item number_of_y_ticks

The number of ticks that should appear on the y axis. Defaults to 5.

=item graph_height

The height of the chart in pixels. Defaults to 300.

=item graph_width

The width of the chart in pixels. Defaults to 400.

=item max_label_length

The number of characters that should be kept from C<name> elements.
Defaults to 20.

=back

=head1 SEE ALSO

Lire::ReportParser(3pm) GD::Graph(3pm)

=head1 VERSION

$Id: ChartWriter.pm,v 1.9 2002/07/28 19:29:17 flacoste Exp $

=head1 COPYRIGHT

Copyright (C) 2001-2002 Stichting LogReport Foundation LogReport@LogReport.org

This file is part of Lire.

Lire is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program (see COPYING); if not, check with
http://www.gnu.org/copyleft/gpl.html or write to the Free Software 
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.

=head1 AUTHOR

Francis J. Lacoste <flacoste@logreport.org>

=cut
