#                                                         -*- Perl -*-
# Copyright (c) 2007-2009  Kazuhiro Ito
#
# 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 2.
# 
# 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.
#

require 5.005;

use English;
use FreePWING::Sound;
use FreePWING::FPWUtils::FPWUtils;
use Getopt::Long;
use warnings;
use FileHandle;
use Compress::Raw::Zlib;

use vars qw ($name_pos $name_size $content_pos $content_size);
use vars qw ($unzipped $zipped $last_unzipped $last_zipped);
use vars qw ($contents);
use vars qw ($content_handle $content_index_handle);
use vars qw ($index_handle $name_handle);
use vars qw (%fpwoald7_conf);
use vars qw ($silent_wav);

$silent_wav = pack("C*", 
		   0x52, 0x49, 0x46, 0x46, 0x25, 0x00, 0x00, 0x00,
		   0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20,
		   0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
		   0x11, 0x2b, 0x00, 0x00, 0x11, 0x2b, 0x00, 0x00,
		   0x01, 0x00, 0x08, 0x00, 0x64, 0x61, 0x74, 0x61,
		   0x01, 0x00, 0x00, 0x00, 0x7f);

 MAIN: {
   my %tag_table;

#
# コマンド行を解析する。
#
   my ($srcdir, $conf_file);

   if (!GetOptions('workdir=s' => \$work_directory, 
		   'srcdir=s' => \$srcdir, 
		   'conf=s' => \$conf_file)) {
     exit 1;
   }

   require $conf_file;

#
# fpwutils を初期化する。
#
   initialize_fpwutils();

#
# これから出力するファイルがすでにあれば、削除する。
#
   unlink($sound_file_name);
   unlink($sound_tag_file_name);
   unlink($sound_fmt_file_name);

   $sound = FreePWING::Sound->new();

#
# 生成側ファイルを開く。
#
   if (!$sound->open($sound_file_name,
		     $sound_tag_file_name,
		     $sound_fmt_file_name)) {
     die "$PROGRAM_NAME: " . $sound->error_message() . "\n";
   }

   if($fpwoald7_conf{'sound_type'} == 0) {
     goto FINISH;
   }
   
#
# 読み込みファイルを開く
#
   $srcdir =~ s/([^\/])$/$1\//;

   my $tmp;
   my ($content, $content_name);
   my $output_handle;
   my $tag_name;

   initialize_content_reader($srcdir."us/");
   while (1) {
     ($content_name, $content) = get_next_content();

     if (!length($content_name)) {
       last;
     }

     if ($content_name =~ /(.+)\.MP3$/) {
       $tag_name = "us_$1";
       $tag_name =~ s/\./_/g;
	
#
# Convert sound if needed.
#
       if ($fpwoald7_conf{'sound_type'} == 2) {
	 $content = get_converted_sound($content);
       } else {
	 $content = get_riff_header($content) . $content;
       }

#
# 音声データを追加する。
#

       if (!$sound->add_binary($tag_name, $content)) {
	 die "$PROGRAM_NAME: " . $sound->error_message() . "\n";
       }
       if(verbose_mode()) {
	 print "$tag_name -> $content_name\n";
       }
     }
   }
   finalize_content_reader();
   print "US pronunciation sounds are registerd.\n";


   initialize_content_reader($srcdir."uk/");
   while (1) {
     ($content_name, $content) = get_next_content();

     if (!length($content_name)) {
       last;
     }

     if ($content_name =~ /(.+)\.MP3$/) {
       $tag_name = "uk_$1";
       $tag_name =~ s/\./_/g;

#
# Convert sound if needed.
#
       if ($fpwoald7_conf{'sound_type'} == 2) {
	 $content = get_converted_sound($content);
       } else {
	 $content = get_riff_header($content) . $content;
       }

#
# 音声データを追加する。
#
       if (!$sound->add_binary($tag_name, $content)) {
	 die "$PROGRAM_NAME: " . $sound->error_message() . "\n";
       }
       if (verbose_mode()) {
	 print "$tag_name -> $content_name\n";
       }
     }
   }
   finalize_content_reader();
   print "UK pronunciation sounds are registerd.\n";

# 
# 音声の生成側ファイルを閉じる。
# 
   printf ("%6d sounds are registered.\n", $sound->entry_count());
 FINISH:
   if (!$sound->close()) {
     die "$PROGRAM_NAME: " . $sound->error_message() . "\n";
   }

#
# fpwutils の後始末をする。
#
   finalize_fpwutils();

   exit 0;
}

sub initialize_content_reader {
  my $srcdir = $_[0];
  my $content_filename = $srcdir.'CONTENT.tda';
  my $content_index_filename = $srcdir.'CONTENT.tda.tdz';
  my $index_filename = $srcdir.'files.dat';
  my $name_filename = $srcdir.'NAME.tda';
  my $tmp;

  $content_handle = new FileHandle;
  if (!$content_handle->open("$content_filename", 'r')) {
    die "$PROGRAM_NAME: Failed to open the file, $ERRNO: $content_filename\n";
  }
  binmode $content_handle;
    
  $content_index_handle = new FileHandle;
  if (!$content_index_handle->open("$content_index_filename", 'r')) {
    die "$PROGRAM_NAME: Failed to open the file, $ERRNO: $content_index_filename\n";
  }
  binmode $content_index_handle;
    
  $index_handle = new FileHandle;
  if (!$index_handle->open("$index_filename", 'r')) {
    die "$PROGRAM_NAME: Failed to open the file, $ERRNO: $index_filename\n";
  }
  binmode $index_handle;

  $name_handle = new FileHandle;
  if (!$name_handle->open("$name_filename", 'r')) {
    die "$PROGRAM_NAME: Failed to open the file, $ERRNO: $name_filename\n";
  }
  binmode $name_handle;

  ($name_size, $content_size) = (0, 0);
  ($unzipped, $zipped) = (0, 0);
  ($last_zipped, $last_unzipped) = (0, 0);

  ($name_pos, $content_pos) = get_next_index();
  if($name_pos == -1) {
    die "$PROGRAM_NAME: Unexpected index end.\n";
  }

  return 0;
}

sub finalize_content_reader {
  $content_handle->close();
  $content_index_handle->close();
  $index_handle->close();
  $name_handle->close();

  return 0;
}

sub get_next_index {
  my ($name, $content, $i, $tmp);
  if (read($index_handle, $tmp, 12) == 12) {
    ($name, $i, $content) = unpack("vCxxV", $tmp);
    $name += ($i << 16);
  } else {
    # 最終エントリ
    ($name, $content) = (-1, -1);
  }

  return ($name, $content);
}

sub get_next_content {
  my $tmp;
  my ($inflater, $status);
  my ($content_name, $content);

  if($name_size == -1) {
    return ('', '');
  }

  $name_pos += $name_size;
  $content_pos += $content_size;

  ($name_size, $content_size) = get_next_index();
    
  if ($name_size != -1) {
    $name_size -= $name_pos;
    $content_size -= $content_pos;
  }

  if ($unzipped + $last_unzipped <= $content_pos) {
    if (read($content_index_handle, $tmp, 8) != 8) {
      die "$PROGRAM_NAME: Unexpected index structure.\n";
    }
    $unzipped += $last_unzipped;
    $zipped += $last_zipped;
    ($last_unzipped, $last_zipped) = unpack("VV", $tmp);
    if (read($content_handle, $tmp, $last_zipped) != $last_zipped) {
      die "$PROGRAM_NAME: failed to read file\n";
    }
    ($inflater, $status) = new Compress::Raw::Zlib::Inflate();
    if ($status != Z_OK) {
      die "$PROGRAM_NAME: Failed to initialize inflater\n";
    }
    $status = $inflater->inflate($tmp, $contents);
    if ($status != Z_OK && $status != Z_STREAM_END) {
      # Do not die, because original data is incorrect.
      print "$PROGRAM_NAME: warning: failed to inflate, $status\n";
    }
  }

  # ファイル名を得る。
  if ($name_size != -1) {
    if (read($name_handle, $content_name, $name_size) != $name_size) {
      die "$PROGRAM_NAME: failed to read file\n";
    }
  } else {
    if (read($name_handle, $content_name, 1024) == 0) {
      die "$PROGRAM_NAME: failed to read file\n";
    }
  }
  $content_name = substr($content_name, 0, -1);

  # コンテントを得る。
  if ($content_size != -1) {
    $content = substr($contents, $content_pos - $unzipped, $content_size - 1);
  } else {
    $content = substr($contents, $content_pos - $unzipped, -1);
  }

  return ($content_name, $content);
}

sub get_riff_header {
  my $length = length($_[0]);
  my $header = "RIFF";

  $header .= pack("V", $length + 70 - 8);
  $header .= "WAVEfmt ";
  $header .= pack("V", 30);
  $header .= pack("v", 0x55);
  $header .= pack("v", 0x01);
  $header .= pack("V", 16 * 1000);
  $header .= pack("V", 24 * 1000 / 8);
  $header .= pack("v", 1);
  $header .= pack("v", 0);
  $header .= pack("v", 12);
  $header .= pack("v", 1);
  $header .= pack("V", 2);
  $header .= pack("v", 216);
  $header .= pack("v", 1);
  $header .= pack("v", 0x571);

  $header .= "fact";
  $header .= pack("V", 4);
  $header .= pack("V", $length / 108);

  $header .= "data";
  $header .= pack("V", $length);

  return $header;
}

sub get_converted_sound {
  my ($sound) = @_;
  my $tmp_wav_name = './oald7-fpw-temp.wav';

  my $handle = new FileHandle;

  unlink($tmp_wav_name);
  if (!$handle->open("|$fpwoald7_conf{'sound_lame'} --silent --mp3input --decode - $tmp_wav_name")) {
    die "$PROGRAM_NAME: failed to start lame, $ERRNO\n";
  }

  binmode($handle);
  $handle->print($sound);
  $handle->close();

  $handle->new();
  if (!$handle->open("$fpwoald7_conf{'sound_sox'} -V1 -t wav $tmp_wav_name $fpwoald7_conf{'sound_sox_options'} -t wav - vol 0.9|")) {
    die "$PROGRAM_NAME: failed to start sox, $ERRNO\n";
  }

  $sound ='';
  while ($_ = $handle->getline()) {
    $sound .= $_;
  }
  $handle->close();

  if (length($sound) < 45) {
    print "warning: failed to convert sound.\n";
    $sound = $silent_wav;
  } else {
    # Set correct wave form length
    if ($sound =~ /RIFF....(.*?)data....(.*$)/) {
      $sound = 'RIFF' . pack("V", length($1) + length($2) + 8) . $1 .
	  'data' . pack("V", length($2)).$2;
    } else {
      print "warning: missed RIFF/WAVE header.\n";
    }
  }

  return $sound;
}
