# -*-perl-*-

#+##############################################################################
#
# tex4ht.pm: use tex4ht to convert tex to html
#
# Copyright 2005, 2007, 2009, 2011, 2012 Free Software Foundation, Inc.
#
# This program 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 3 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.  If not, see <http://www.gnu.org/licenses/>.
#
# Originally written by Patrice Dumas.
#
#-##############################################################################
# To customize the command and the options, you could set
# $Texinfo::TeX4HT::STYLE_MATH to latex/tex
# $Texinfo::TeX4HT::STYLE_TEX to latex/texi
# and/or change
# $Texinfo::TeX4HT::tex4ht_command_math 
#    and $Texinfo::TeX4HT::tex4ht_options_math
# $Texinfo::TeX4HT::tex4ht_command_tex 
#    and $Texinfo::TeX4HT::tex4ht_options_tex

use strict;

my $global_cmds = get_conf('GLOBAL_COMMANDS');
if (!defined($global_cmds)) {
  set_from_init_file('GLOBAL_COMMANDS', []);
  $global_cmds = get_conf('GLOBAL_COMMANDS');
}
push @$global_cmds, ('math', 'tex');

texinfo_register_handler('structure', \&tex4ht_prepare);
texinfo_register_handler('init', \&tex4ht_convert);
texinfo_register_handler('finish', \&tex4ht_finish);

texinfo_register_command_formatting('math', \&tex4ht_do_tex);
texinfo_register_command_formatting('tex', \&tex4ht_do_tex);

{
use Cwd;

package Texinfo::TeX4HT;

use vars qw(
$STYLE_MATH
$STYLE_TEX
$tex4ht_command_math
$tex4ht_command_tex
$tex4ht_options_math
$tex4ht_options_tex
);

$STYLE_MATH = 'texi' if (!defined($STYLE_MATH));
$STYLE_TEX = 'tex' if (!defined($STYLE_TEX));

if (!defined($tex4ht_command_math)) {
  $tex4ht_command_math = 'httexi';
  $tex4ht_command_math = 'htlatex' if ($STYLE_MATH eq 'latex');
  $tex4ht_command_math = 'httex' if ($STYLE_MATH eq 'tex');
}
if (!defined($tex4ht_command_tex)) {
  $tex4ht_command_tex = 'httex';
  $tex4ht_command_tex = 'htlatex' if ($STYLE_TEX eq 'latex');
  $tex4ht_command_tex = 'httexi' if ($STYLE_TEX eq 'texi');
}

}

my %commands = ();

my $tex4ht_initial_dir;
my $tex4ht_out_dir;

sub tex4ht_prepare($)
{
  # set file names
  my $self = shift;

  return 1 if (defined($self->get_conf('OUTFILE'))
        and $Texinfo::Common::null_device_file{$self->get_conf('OUTFILE')});

  $tex4ht_initial_dir = Cwd::abs_path;
  $tex4ht_out_dir = $self->{'destination_directory'};
  $tex4ht_out_dir = File::Spec->curdir()
    if (!defined($tex4ht_out_dir) or $tex4ht_out_dir =~ /^\s*$/);

  my $document_name = $self->{'document_name'};
  my $tex4ht_basename = "${document_name}_tex4ht";

  # this initialization doesn't seems to be needed, but it is cleaner anyway
  %commands = ();
  $commands{'math'}->{'style'} = $Texinfo::TeX4HT::STYLE_MATH;
  $commands{'tex'}->{'style'} = $Texinfo::TeX4HT::STYLE_TEX;
  $commands{'math'}->{'exec'} = $Texinfo::TeX4HT::tex4ht_command_math;
  $commands{'tex'}->{'exec'} = $Texinfo::TeX4HT::tex4ht_command_tex;
  foreach my $command ('math', 'tex') {
    my $style = $commands{$command}->{'style'};
    $commands{$command}->{'basename'} = $tex4ht_basename . "_$command";
    my $suffix = '.tex';
    $suffix = '.texi' if ($style eq 'texi');
    $commands{$command}->{'basefile'} = $commands{$command}->{'basename'} . $suffix;
    $commands{$command}->{'html_file'} = $commands{$command}->{'basename'} . '.html';
    $commands{$command}->{'rfile'} = File::Spec->catfile($tex4ht_out_dir, 
                                          $commands{$command}->{'basefile'});
    my $rfile = $commands{$command}->{'rfile'};
    $commands{$command}->{'counter'} = 0;
    $commands{$command}->{'output_counter'} = 0;

    if ($self->{'extra'}->{$command}) {
      local *TEX4HT_TEXFILE;
      unless (open (*TEX4HT_TEXFILE, ">$rfile")) {
        $self->document_warn(sprintf($self->__("tex4ht.pm: could not open %s: %s"), 
                                      $rfile, $!));
        return 1;
      }
      $commands{$command}->{'handle'} = *TEX4HT_TEXFILE;

      my $style = $commands{$command}->{'style'};
      my $fh = $commands{$command}->{'handle'};
      my $comment = '@c';
      $comment = '%' if ($style ne 'texi');
      $comment .= " Automatically generated\n";
      if ($style eq 'texi') {
        print $fh "\\input texinfo
\@setfilename $commands{$command}->{'basename'}.info\n";
        print $fh "$comment";
      } else {
        print $fh "$comment";
        if ($style eq 'latex') {
          print $fh "\\documentstyle{article}\n\\begin{document}\n";
        } elsif ($style eq 'tex') {
          print $fh "\\csname tex4ht\\endcsname\n";
        }
      }
      foreach my $root (@{$self->{'extra'}->{$command}}) {
        $commands{$command}->{'counter'}++;
        my $counter = $commands{$command}->{'counter'};
        my $tree;
        if ($command eq 'math') {
          $tree = $root->{'args'}->[0];
        } else {
          $tree = {'contents' => [@{$root->{'contents'}}]};
          if ($tree->{'contents'}->[0] 
              and $tree->{'contents'}->[0]->{'type'}
              and $tree->{'contents'}->[0]->{'type'} eq 'empty_line_after_command') {
            shift @{$tree->{'contents'}};
          }
          if ($tree->{'contents'}->[-1]->{'cmdname'} 
              and $tree->{'contents'}->[-1]->{'cmdname'} eq 'end') {
            pop @{$tree->{'contents'}};
          }
        }
        my $text = Texinfo::Convert::Texinfo::convert($tree);
        $commands{$command}->{'commands'}->[$counter-1] = $root;

        # write to tex file
        my ($before_comment_open, $after_comment_open, $before_comment_close,
            $after_comment_close);

        if ($style eq 'texi') {
          $before_comment_open = "\@verbatim\n\n";
          $after_comment_open = "\n\@end verbatim\n";
          $before_comment_close = "\@verbatim\n";
          $after_comment_close = "\n\n\@end verbatim\n";
        } else {
          $before_comment_open = "\\HCode{\\Hnewline \\Hnewline ";
          $after_comment_open = "\\Hnewline}\n";
          $before_comment_close = "\\HCode{\\Hnewline ";
          $after_comment_close = "\\Hnewline \\Hnewline}\n";
        }
  
        my $begin_comment = "<!-- tex4ht_begin $commands{$command}->{'basename'} $command $counter -->";
        print $fh "$before_comment_open$begin_comment$after_comment_open";
        if ($command eq 'tex') {
          print $fh $text;
        } elsif ($command eq 'math') {
          if ($style eq 'texi') {
            print $fh '@math{' . $text . "}\n";
          } else {
            print $fh "\\IgnorePar \$" . $text . "\$";
          }
        }
        my $end_comment = "<!-- tex4ht_end $commands{$command}->{'basename'} $command $counter -->";
        print $fh "$before_comment_close$end_comment$after_comment_close";
      }
      # finish the tex file
      if ($style eq 'latex') {
        print $fh "\\end{document}\n";
      } elsif ($style eq 'tex') {
        print $fh "\n\\bye\n";
      } else {
        print $fh "\n\@bye\n";
      }
      close ($fh);
      # this has to be done during the 'process' phase, in 'output' it is 
      # too late.
      push @{$self->{'css_import_lines'}}, 
         "\@import \"$commands{$command}->{'basename'}.css\";\n";
    }
  }
  return 1;
}

sub tex4ht_convert($)
{
  my $self = shift;
  unless (chdir $tex4ht_out_dir) {
    $self->document_warn(sprintf($self->__("tex4ht.pm: chdir %s failed: %s"),
                         $tex4ht_out_dir, $!));
    return 0;
  }
  print STDERR "cwd($tex4ht_out_dir): " . Cwd::cwd() ."\n" 
    if ($self->get_conf('VERBOSE'));

  my $errors = 0;
  foreach my $command (keys(%commands)) {
    $errors += tex4ht_process_command($self, $command);
  }
  unless (chdir $tex4ht_initial_dir) {
    $self->document_warn(sprintf($self->__(
          "tex4ht.pm: unable to return to initial directory: %s"), $!));
    return 0;
  }
  return 1;
}

sub tex4ht_process_command($$) {
  my $self = shift;
  my $command = shift;
  
  return 0 unless ($commands{$command}->{'counter'});

  $self->document_warn(sprintf($self->__("tex4ht.pm: output file missing: %s"),
                               $commands{$command}->{'basefile'}))
    unless (-f $commands{$command}->{'basefile'});
  my $style = $commands{$command}->{'style'};
  # now run tex4ht
  my $options = '';
  if ($style eq 'math' and defined($Texinfo::TeX4HT::tex4ht_options_math)) {
    $options = $Texinfo::TeX4HT::tex4ht_options_math 
  } elsif ($style eq 'tex' and defined($Texinfo::TeX4HT::tex4ht_options_tex)) {
    $options = $Texinfo::TeX4HT::tex4ht_options_tex;
  }

  my $cmd = "$commands{$command}->{'exec'} $commands{$command}->{'basefile'} $options";
  print STDERR "tex4ht command: $cmd\n" if ($self->get_conf('VERBOSE'));
  if (system($cmd)) {
    $self->document_warn(sprintf($self->__(
                         "tex4ht.pm: command failed: %s"), $cmd));
    return 1;
  }

  # extract the html from the file created by tex4ht
  my $html_basefile = $commands{$command}->{'html_file'};
  unless (open (TEX4HT_HTMLFILE, $html_basefile)) {
    $self->document_warn(sprintf($self->__("tex4ht.pm: could not open: %s"), 
                                  $html_basefile, $!));
    return 1;
  }
  my $got_count = 0;
  my $line;
  while ($line = <TEX4HT_HTMLFILE>) {
    #print STDERR "$html_basefile: while $line";
    if ($line =~ /!-- tex4ht_begin $commands{$command}->{'basename'} (\w+) (\d+) --/) {
      my $command = $1;
      my $count = $2;
      my $text = '';
      my $end_found = 0;
      while ($line = <TEX4HT_HTMLFILE>) {
        #print STDERR "while search $command $count $line";
        if ($line =~ /!-- tex4ht_end $commands{$command}->{'basename'} $command $count --/) {
          $got_count++;
          chomp($text) if ($command eq 'math');
          $commands{$command}->{'results'}->{$commands{$command}->{'commands'}->[$count-1]} = $text;
          $end_found = 1;
          last;
        } else {
          $text .= $line;
        }
      }
      unless ($end_found) {
        $self->document_warn(sprintf($self->__(
                               "tex4ht.pm: end of \@%s item %d not found"), 
                                      $command, $count));
      }
    }
  }
  if ($got_count != $commands{$command}->{'counter'}) {
    $self->document_warn(sprintf($self->__(
       "tex4ht.pm: processing produced %d items in HTML; expected %d, the number of items found in the document for \@%s"), 
                                 $got_count, $commands{$command}->{'counter'},
                                 $command));
  }
  close (TEX4HT_HTMLFILE);
  return 0;
}

sub tex4ht_do_tex($$$$)
{
  my $self = shift;
  my $cmdname = shift;;
  my $command = shift;
  # return the resulting html 
  if (exists ($commands{$cmdname}->{'results'}->{$command})
      and defined($commands{$cmdname}->{'results'}->{$command})) {
    $commands{$cmdname}->{'output_counter'}++;
    return $commands{$cmdname}->{'results'}->{$command};
  } else {
    $self->document_warn(sprintf($self->__(
                       "tex4ht.pm: output has no HTML item for \@%s %s"),
                                  $cmdname, $command));
    return '';
  }
}

sub tex4ht_finish($)
{
  my $self = shift;
  # this is different from the warning in tex4ht_process_command as, here,
  # this is the number of retrieved fragment, not processed fragment.
  if ($self->get_conf('VERBOSE')) {
    foreach my $command (keys(%commands)) {
      if ($commands{$command}->{'output_counter'} != $commands{$command}->{'counter'}) {
        $self->document_warn(sprintf($self->__(
           "tex4ht.pm: processing retrieved %d items in HTML; expected %d, the number of items found in the document for \@%s"), 
                                  $commands{$command}->{'output_counter'}, 
                                  $commands{$command}->{'counter'}, $command));
      }
    }
  }
  return 1;
}

1;
