package Gscan2pdf::Ocropus;

use 5.008005;
use strict;
use warnings;
use Carp;
use File::Temp;    # To create temporary files
use File::Basename;
use HTML::Entities;
use Encode;
use Gscan2pdf::Tesseract;
use Readonly;

# TODOS: (ggb->?;20130530)
# Need a means of setting OPTS externally (pass in via setup?
# read from .gscan2pdf?  Something else?)
# Need a way to translate language -> ORCopus model.

Readonly my $NOISE_HANDLER  => 'ocropus-nlbin';
Readonly my $PAGE_SEGMENTER => 'ocropus-gpageseg';
Readonly my $ORCO_RECOGNIZER=> 'ocropus-rpred';
Readonly my $HOCR_GENERATOR => 'ocropus-hocr';
Readonly my $PAGES_GLOB     => '????.bin.png';   # glob from ocropus docs
Readonly my $LINES_GLOB     => '????/??????.bin.png'; # glob from ocropus docs
Readonly my $EXPECTED_STR   => 'writing to ';
Readonly my $HOCR_OUTFILE   => 'book.html';

#Readonly my $NOISE_HANDLER_OPTS  => '';
Readonly my $NOISE_HANDLER_OPTS   => '--rawcopy --parallel 4';
#Readonly my $PAGE_SEGMENTER_OPTS => '';
Readonly my $PAGE_SEGMENTER_OPTS  => '--debug --parallel 4';
#Readonly my $RECOGNIZER_OPTS     => '';
Readonly my $RECOGNIZER_OPTS      => '--parallel 4';
Readonly my $HOCR_GENERATOR_OPTS  => '';

Readonly my $INT_FILES_DESTROY          => 0; # default
Readonly my $INT_FILES_DESTROY_ON_CLOSE => 1; # Keep until app exits
Readonly my $INT_FILES_KEEP             => 0; # must be manually deleted

Readonly my $DIR_TEMPLATE => 'gs2p-orco-XXXXXXXX';

my ( $use_tesseract, $installed, $setup, $logger );
my ( $intfile_lifespan, @temp_dirs );

sub setup {
 ( my $class, $logger ) = @_;
 return $installed if $setup;

 my $missing_nh = system("which $NOISE_HANDLER >/dev/null 2>/dev/null");
 my $missing_pg = system("which $PAGE_SEGMENTER >/dev/null 2>/dev/null");
 my $missing_hg = system("which $HOCR_GENERATOR >/dev/null 2>/dev/null");
 if ( $missing_nh || $missing_pg || $missing_hg ) {
  $installed = 0;
  $logger->warn("Required $NOISE_HANDLER not found.")  if $missing_nh;
  $logger->warn("Required $PAGE_SEGMENTER not found.") if $missing_pg;
  $logger->warn("Required $HOCR_GENERATOR not found.") if $missing_hg;
 }
 else {
  # TODO: (ggb->?:20030530) pass this in
  $intfile_lifespan = $INT_FILES_DESTROY_ON_CLOSE;
  $setup = 1;
  if ( system("which $ORCO_RECOGNIZER >/dev/null 2>/dev/null") == 0 ) {
   $use_tesseract = 0;
   $installed = 1;
   $logger->info('Using ocropus with its own recognizer.');
  }
  elsif ( Gscan2pdf::Tesseract->setup($logger) ) {
   $use_tesseract = 1;
   $installed = 1;
   $logger->info('Using ocropus with tesseract as the recognizer.');
  }
  else {
   $logger->warn("Found ocropus, but neither $ORCO_RECOGNIZER"
      ." nor Tesseract.  Disabling.");
   $installed = 0;
  }
 }
 return $installed;
}

sub hocr {
 my ( $class, $file, $language, $loggr, $pidfile ) = @_;
 my ( $image_base );
 Gscan2pdf::Ocropus->setup($loggr) unless $setup;

 if ( $file !~ /\.(?:png|jpg|pnm)$/x ) {
  # Copy of the image in an ORCopus-friendly graphics format.
  $image_base = File::Temp->new( SUFFIX => '.png' );
  my $image = Image::Magick->new;
  $image->Read($file);
  $image->Write( filename => $image_base );
 }
 else {
  $image_base = $file;
 }

 # If no PID file specified, create a temporary one.
 # This simplifies the future code.
 if ( !defined $pidfile ) {
  $pidfile = File::temp->new();
 }

 # create a directory for all of the generated files. Not under the heirarchy
 # for the rest of the application, so that $INT_KEEP_FILES can keep the
 # directory alive when the application's temp directory is unlinked.
 my $temp_dir = File::Temp->newdir( $DIR_TEMPLATE, TMPDIR => 1 );

 if ( $intfile_lifespan == $INT_FILES_DESTROY_ON_CLOSE ) {
  # preserves the dir in an object that doesn't go out of scope until
  # application exit.  However, push is probably not threadsafe - GGB
  push @temp_dirs, $temp_dir;
 }
 elsif ( $intfile_lifespan == $INT_FILES_KEEP ) {
  $temp_dir->unlink_on_destroy(0);  # turns off auto-delete
 }

 if ( !_handle_noise($class,$image_base,$temp_dir,$pidfile) ) {
  $logger->warn("Failure from noise recognizer; aborting.");
  return undef;
 }
 if ( !_segment_page($class,$temp_dir,$pidfile) ) {
  $logger->warn("Failure from page segmenter; aborting.");
  return undef;
 }
 if ( !_recognize_tesseract($class,$temp_dir,$language,$pidfile)
    && !_recognize_ocropus($class,$temp_dir,$language,$pidfile) ) {
  $logger->warn("Failure from recognizer; aborting.");
  return undef;
 }
 my $output = _generate_hocr($class,$temp_dir,$pidfile);
 if ( !$output ) {
  $logger->warn("Failure from hocr generator; aborting.");
  return undef;
 }
 $logger->debug( substr($output, 0, 1024) );

 # decode html->utf8
 my $decoded = decode_entities($output);

 # Unfortunately, there seems to be a case (tested in t/31_ocropus_utf8.t)
 # where decode_entities doesn't work cleanly, so encode/decode to finally
 # get good UTF-8
 # TODO (ggb->?,20130530) Is this still true?
 return decode_utf8( encode_utf8($decoded) );
}

sub _handle_noise {
 my ( $class, $image_file, $dir, $pidfile ) = @_;

 my $cmd = "$NOISE_HANDLER $NOISE_HANDLER_OPTS -o $dir/ $image_file";
 $logger->info("Running $cmd");
 my ( $out, $err ) = Gscan2pdf::Document::open_three("echo $$ >$pidfile;$cmd");

 $logger->debug("Noise output: ".($out||"(no output)") );
 $logger->error($err) if $err;
 return !$err;
}

sub _segment_page {
 my ( $class, $dir, $pidfile ) = @_;

 my $cmd = "$PAGE_SEGMENTER $PAGE_SEGMENTER_OPTS $dir/$PAGES_GLOB";
 $logger->info("Running $cmd");
 my ( $out, $err ) = Gscan2pdf::Document::open_three("echo $$ >$pidfile;$cmd");

 $logger->debug("Segment output: ".($out||"(no output)") );
 $logger->error($err) if $err;
 return !$err;
}

sub _recognize_ocropus {
 my ( $class, $dir, $language, $pidfile ) = @_;

 my $cmd = "$ORCO_RECOGNIZER $RECOGNIZER_OPTS $dir/$LINES_GLOB";
 # TODO (ggb->;20130530) add model selection, if $language a valid model

 $logger->info("Running $cmd");
 my ( $out, $err ) = Gscan2pdf::Document::open_three("echo $$ >$pidfile;$cmd");

 $logger->debug("Recog output: ".($out||"(no output)") );
 $logger->error($err) if $err;
 return !$err;
}

sub _generate_hocr {
 my ( $class, $dir, $pidfile ) = @_;

 my $cmd = "$HOCR_GENERATOR $HOCR_GENERATOR_OPTS $dir/$PAGES_GLOB"
  ." -o $dir/$HOCR_OUTFILE";
 $logger->info("Running $cmd");
 my ( $out, $err ) = Gscan2pdf::Document::open_three("echo $$ >$pidfile;$cmd");

 ( my $result, undef ) = split( "\n", $err );
 my $tmpstr = "$EXPECTED_STR$dir/$HOCR_OUTFILE";
 my $success = $result =~ /$tmpstr/;

 if ( $success ) {
  $logger->info("hocr: ".($out||"")." $err");
 }
 else {
  $logger->error("hocr: ".($out||"")." ".($err||"(no error output?!?!)"));
 }
 open my $RESULT_FILE, "<$dir/$HOCR_OUTFILE" or return undef;

 my @results = <$RESULT_FILE>;
 return join("",@results);
}

sub _recognize_tesseract {
# my ( $class, $dir, $language, $pidfile ) = @_;

 $logger->error("Tesseract recognition with ocropus not implemented, yet.")
  if $use_tesseract;
 return 0;
}

1;

__END__
