package Lire::ReportParser::LaTeXWriter;

use strict;

use Lire::Utils qw/check_param check_object_param latex_encode file_content/;
use Lire::I18N qw/ set_fh_encoding/;
use Lire::ReportParser::LaTeXDocBookFormatter qw/dbk2latex/;
use Lire::DlfSchema;
use File::Basename qw/dirname basename/;
use POSIX qw/strftime/;
use Locale::TextDomain 'lire';
use Lire::Config;

=pod

=head1 NAME

Lire::ReportParser::LaTeXWriter - Lire::ReportParser processor that formats the report in LaTeX

=head1 SYNOPSIS

    use Lire::ReportParser::LaTeXWriter;
    use Lire::ReportParser::ReportBuilder;
    my $writer = new Lire::ReportParser::LaTeXWriter(),
 );

    my $parser = new Lire::ReportParser::ReportBuilder();
    my $report = $parser->parsefile( 'report.xml' );
    $parser->write_report( $report, 'report.pdf', 'pdf',
                           'preamble' => 'myfont.tex' );

=head1 DESCRIPTION

This is a Lire::ReportParser processor which will format the XML
report into PDF, PS or DVI format using LaTeX as intermediary format.

=head1 METHODS

=head2 new()

Returns an new LaTeXWriter.

=cut

sub new {
    return bless {}, shift;
}


=pod

=head2 write_report( $report, $outputfile, $format, [ $preamble ] )

This will write the report Lire::Report in $outputfile in $format
output format.

=over

=item outputfile

This manadatory parameter specifies the file where the file will be
written.

=item format

This mandatory parameter may be one of 'pdf', 'ps', 'dvi' or 'latex'.

=item preamble

This optional parameter can be used to specify a file that will be
included in the LaTeX preamble. It can be use to change the fonts for
example.

=cut

sub write_report {
    my ( $self, $report, $outputfile, $format, $preamble ) = @_;

    check_object_param( $report, 'report', 'Lire::Report' );
    check_param( $outputfile, 'outputfile' );
    # \s is for broken cperl-mode in Emacs, *sigh*
    check_param( $format, 'format', qr/^(dvi|ps|pdf|latex)$/,
                 'format should be one of dvi, ps, pdf or latex' );
    check_param( $preamble, 'preamble', sub { -r $preamble },
                 "preamble file isn't readable" )
        if $preamble;

    $self->{'_chart_files'} = [];
    $self->{ '_format'} = $format;
    $self->{'_outputfile'} = $outputfile;
    $self->{'_outputdir'} = dirname( $outputfile );
    $self->{'_outputbase'} = basename( $outputfile );
    $self->{'_outputbase'} =~ s/\.\w+?$//;
    $self->{'_preamble'} = $preamble;

    open $self->{'_fh'}, "> $self->{'_outputdir'}/$self->{'_outputbase'}.tex"
      or die "can't write to $self->{'_outputdir'}/$self->{'_outputbase'}.tex\n";
    set_fh_encoding( $self->{'_fh'}, 'utf-8' );


    $self->write_header();
    $self->write_titlepage( $report );
    foreach my $section ( $report->sections() ) {
        $self->write_section( $section );
    }
    $self->write_appendix( $report );

    print {$self->{'_fh'}} "\\end{document}\n";

    $self->process_latex();

    return;
}

sub write_header {
    my $self = $_[0];

    my $hyperref = $self->{'_format'} eq 'pdf' ? 'ps2pdf' : 'dvips';
    my $graphics = $self->{'_format'} eq 'pdf' ? 'dvipdf' : 'dvips';

    my $unicode = Lire::Config->get( 'unicode.tex' );
    my $fh = $self->{'_fh'};
    print $fh <<'EOF';
\ocp\MyTexUTF=inutf8
\InputTranslation currentfile \MyTexUTF

\documentclass{report}

\addtolength{\textwidth}{2\oddsidemargin}
\addtolength{\textheight}{2\topmargin}
\setlength{\oddsidemargin}{0cm}
\setlength{\topmargin}{0cm}

EOF

    print $fh '\input ', $unicode, "\n", <<EOF;
\\usepackage[$graphics]{graphics}
\\usepackage{longtable}
\\usepackage[$hyperref,bookmarks,bookmarksopen,bookmarksnumbered]{hyperref}
EOF

    print $fh '\input ', $self->{'_preamble'}, "\n"
      if $self->{'_preamble'};

    print $fh "\n", '\begin{document}', "\n";

    return;
}

sub write_titlepage {
    my ( $self, $report ) = @_;

    my $fh = $self->{'_fh'};
    if ( $report->title() ) {
        print $fh '\title{', latex_encode( $report->title() ) ,"}\n"
    } else {
        print $fh '\title{}', "\n";
    }
    print $fh '\author{}', "\n";
    my $fmt = '%Y-%m-%d %H:%M';
    my $start = strftime( $fmt, localtime( $report->timespan_start() ) );
    my $end = strftime( $fmt, localtime( $report->timespan_end() ) );
    my $date = strftime( $fmt, localtime( $report->date() ) );
    print $fh '\date{', __x( "Report for {start} -- {end}",
                             'start' => $start, 'end' => $end ), "\\\\\n",
                __x( 'Report generated {date}', 'date' => $date ), "}\n",
              '\maketitle', "\n", '\tableofcontents', "\n\n";
    return;
}

sub write_section {
    my ( $self, $section )  = @_;

    my $fh = $self->{'_fh'};
    print $fh '\chapter{', latex_encode( $section->title() ), "}\n\n";
    print $fh dbk2latex( $section->description() )
      if $section->description();

    foreach my $subreport ( $section->subreports() ) {
        if ( $subreport->is_missing() ) {
            $self->write_missing_subreport( $subreport );
        } else {
            $self->write_subreport( $subreport );
        }
    }

    return;
}

sub write_subreport {
    my ( $self, $subreport ) = @_;

    my $fh = $self->{'_fh'};
    print $fh '\section{', latex_encode( $subreport->title() ), "}\n\n";
    print $fh dbk2latex( $subreport->description() )
      if $subreport->description();

    foreach my $cfg ( @{$subreport->chart_configs()} ) {
        $self->write_chart( $subreport, $cfg );
    }
    $self->write_table_header( $subreport );
    $self->write_table_footer( $subreport );
    $self->write_table_entries( $subreport );

    return;
}

sub write_missing_subreport {
    my ( $self, $subreport ) = @_;

    my $fh = $self->{'_fh'};
    print $fh '\section{', latex_encode( $subreport->title() ), "}\n\n";

    print $fh '\emph{', __x( 'This report is missing: {reason}',
                             'reason' => latex_encode( $subreport->missing_reason() ) ), "}\n\n";

    return;
}

sub write_chart {
    my ( $self, $subreport, $chart_config ) = @_;

    my $fh = $self->{'_fh'};
    my $type = $chart_config->type();
    my $font = Lire::Config->get( 'lr_chart_font' );
    $font ||= '/Helvetica';
    if ( substr( $font, 0, 1) ne '/' ) {
        warn "'lr_chart_font' contains an invalid PostScript font ('$font'), using '/Helvetica'";
        $font = '/Helvetica';
    }
    my $file = eval { $type->write_chart( $chart_config, $subreport,
                                          'outputdir' => $self->{'_outputdir'},
                                          'format' => 'eps',
                                          'font' => $font ) };
    if ( $@ ) {
        print $fh '\emph{', latex_encode( __x( 'An error occured while generating the chart: {error}', 'error' => $@ ) ), "}\n\n";
    } elsif ( $file ) {
        my $base = basename( $file );
        push @{$self->{'_chart_files'}}, $file;
        print $fh "\\begin{center}\n\\includegraphics{$base}\n\\end{center}\n\n";
    }
    return;
}

sub write_table_header {
    my ( $self, $subreport ) = @_;

    my $fh = $self->{'_fh'};
    my $info = $subreport->table_info();
    my $col_spec = join(  '', '|', ( map { $_->class() eq 'categorical' ? 'l' : 'r' } $info->column_infos() ), '|' );
    print $fh "\\begin{longtable}{$col_spec}\n\\hline\n";
    my @col_infos = $info->column_infos();
    foreach my $row ( @{ $info->header_rows() } ) {
        my @data = ();
        foreach my $cell ( @$row ) {
            next unless defined $cell;
            my $i = $cell->col_start();
            my $schema = $subreport->field_schema( $cell->name() );
            if ( $schema ) {
                $data[$i] = '\bfseries \hyperlink{' . $schema . ':' 
                  . $cell->name() . '}{' . latex_encode( $cell->label() )
                    . '}';
            } else {
                $data[$i] = '\bfseries ' . latex_encode( $cell->label() );
            }
        }
        $self->write_table_row( \@col_infos, \@data );
    }
    print $fh "\\hline\n\\endhead\n\n";
    return;
}

sub write_table_entries {
    my ( $self, $subreport ) = @_;

    my $fh = $self->{'_fh'};
    unless ( $subreport->entries() ) {
        print $fh '\multicolumn{',
          $subreport->table_info()->ncols(), '}{|l|}{\emph{', 
          __( 'There is no entries in this table.' ),
          "}}\\\\\n\\end{longtable}\n\n";
        return;
    }

    my @col_infos = $subreport->table_info()->column_infos();
    foreach my $row ( @{$subreport->getrows()} ) {
        my @data = map { defined $_ ? latex_encode( $_->{'content'} ) : undef } @$row;
        $self->write_table_row( \@col_infos, \@data );
    }
    print $fh "\\end{longtable}\n\n";
    return;
}

sub write_table_row {
    my ( $self, $infos, $row, $link ) = @_;

    my $fh = $self->{'_fh'};
    my $skip = 0;
    for ( my $i=0; $i < @$infos; $i++ ) {
        if ( $skip ) {
            $skip--;
            next;
        }
        my $col_info = $infos->[$i];
        if ( defined $row->[$i] && $col_info->col_start() != $col_info->col_end() ) {
            my $spec = '';
            $spec .= '|' if $col_info->col_start() eq 0;
            $spec .= $col_info->class() eq 'categorical' ? 'l' : 'r';
            $spec .= '|' if $col_info->col_start() eq $#$infos;
            print $fh '\multicolumn{',
              ($col_info->col_end() - $col_info->col_start()) + 1,
                '}{', $spec, '}{', $row->[$i], '}';
            $skip = $col_info->col_end() - $col_info->col_start();
        } elsif ( defined $row->[$i] ) {
            print $fh $row->[$i];
        }
        print $fh " & " if $i != $#$infos;
    }
    print $fh "\\\\\n";

    return;
}

sub write_table_footer {
    my ( $self, $subreport ) = @_;

    my $fh = $self->{'_fh'};
    print $fh "\\hline\n\\multicolumn{",
      $subreport->table_info()->ncols(),
        '}{r}{\emph{', __( 'continued on next page' ), "}}\n\\endfoot\n\\hline\n";

    my @values = grep { $_->class() eq 'numerical' } $subreport->table_info()->column_infos();
    print $fh '\multicolumn{',  $values[0]->col_start(), '}{|l}{\emph{',
      __x( 'Total for {nrecords} records', 
           'nrecords' => $subreport->nrecords() ), '}} & ';
    my @row = ();
    foreach my $col_info ( @values ) {
        $row[ $col_info->col_start() ] =
          latex_encode( $subreport->get_summary_value( $col_info->name() )->{'content'} );
    }
    print $fh join ( ' & ', map { defined $_ ? $_ : '' }
                      @row[ $values[0]->col_start() .. $#row ] );
    print $fh "\\\\\n\\hline\n\\endlastfoot\n\n";
    return;
}

sub write_appendix {
    my ( $self, $report )  = @_;

    my $fh = $self->{'_fh'};
    print $fh "\\appendix\n";
    foreach my $schema ( @{ $report->schemas() } ) {
        $self->write_schema( $schema );
    }

    return;
}

sub write_schema {
    my ( $self, $schema_id ) = @_;

    my $schema = Lire::DlfSchema::load_schema( $schema_id );
    my $fh = $self->{'_fh'};
    print $fh '\chapter{', latex_encode( $schema->title() ), "}\n\n";
    print $fh dbk2latex( $schema->description() )
      if $schema->description();

    print $fh '\begin{description}', "\n";

    foreach my $field ( $schema->fields() ) {
        next if $field->name() =~ /(dlf_id|dlf_source)/; # Skip internal
        print $fh '\item[\hypertarget{', $schema_id, ":",
          $field->name(), '}{', latex_encode( $field->label() ), "}]\n";
        print $fh dbk2latex( $field->description() )
          if $field->description();
    }
    print $fh '\end{description}', "\n\n";

    return;
}

sub process_latex {
    my $self = $_[0];

    return if $self->{'_format'} eq 'latex';
    $self->create_dvi();
    $self->create_ps()
      if $self->{'_format'} =~ /ps|pdf/;
    $self->create_pdf()
      if $self->{'_format'} eq 'pdf';

    my @clean = ( "$self->{'_outputbase'}.tex",
                  "$self->{'_outputbase'}.log", "$self->{'_outputbase'}.aux",
                  "$self->{'_outputbase'}.toc", "$self->{'_outputbase'}.out", 
                  "$self->{'_outputbase'}.ofl");
    push @clean, "$self->{'_outputbase'}.dvi", @{$self->{'_chart_files'}}
      if $self->{'_format'} =~ /^(ps|pdf)$/;
    push @clean, "$self->{'_outputbase'}.ps"
      if $self->{'_format'} eq 'pdf';

    unlink( @clean );

    return;
}

sub create_dvi {
    my $self = $_[0];

    my $lambda = Lire::Config->get( 'lambda_path' );
    my $status = system( "cd $self->{'_outputdir'} && $lambda -interaction=batchmode $self->{'_outputbase'} >/dev/null 2>/dev/null" );

    my $log = file_content( "$self->{'_outputdir'}/$self->{'_outputbase'}.log" );
    if ( !$status ) {
        # No error, check for rerun
        $self->create_dvi() if $log =~ /rerun/i;
    } else {
        die "Error processing LaTeX file $self->{'_outputbase'}.tex:\n" .
          join ( "", $log =~ /^(!.*)$/mg );
    }
    return;
}

sub create_ps {
    my $self = $_[0];

    my $odvips = Lire::Config->get( 'odvips_path' );
    my $printer = $self->{'_format'} eq 'pdf' ? 'pdf' : 'ps';
    my $status = system( "cd $self->{'_outputdir'} && $odvips -o $self->{'_outputbase'}.ps -P$printer -q $self->{'_outputbase'} > $self->{'_outputbase'}.log 2>&1 " );
    die ("Error converting file  $self->{'_outputbase'}.dvi to PS:\n",
         file_content( "$self->{'_outputbase'}.log" ) )
      if $status;
    return;
}

sub create_pdf {
    my $self = $_[0];

    my $ps2pdf = Lire::Config->get( 'ps2pdf_path' );
    my $status = system( "cd $self->{'_outputdir'} && $ps2pdf -dAutoRotatePages=/None $self->{'_outputbase'}.ps >$self->{'_outputbase'}.log 2>&1" );
    die ("Error converting file  $self->{'_outputbase'}.ps to PDF:\n",
         file_content( "$self->{'_outputbase'}.log" ) )
      if $status;
    return;
}


1;

__END__

=pod

=head1 SEE ALSO

Lire::ReportParser::LaTeXDocBookFormatter(3pm)
Lire::OutputFormat(3pm) Lire::OutputFormats::LaTeX(3pm)

=head1 VERSION

$Id: LaTeXWriter.pm,v 1.17 2006/07/29 17:57:34 vanbaal Exp $

=head1 COPYRIGHT

Copyright (C) 2004 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. 

=head1 AUTHOR

Francis J. Lacoste <flacoste@logreport.org>

=cut
