#                                                         -*- Perl -*-
# Copyright (c) 1999, 2000  Motoyuki Kasahara
#
# 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, 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.
#

#
# 単語一覧を収めたファイルを生成するクラス (FreePWING::Word, 
# FreePWING::EndWord) のための基底仮想クラス。
#
package FreePWING::BaseWord;

require 5.005;
require Exporter;
use FileHandle;
use English;
use FreePWING::CharConv;
use strict;
use integer;

use vars qw(@ISA
	    @EXPORT
	    @EXPORT_OK
	    $max_word_length
	    $word_direction
	    $endword_direction);

@ISA = qw(Exporter);

#
# 単語の最大長。
#
$max_word_length = 254;

#
# 単語の書き込み方向
#
$word_direction = 1;
$endword_direction = -1;

#
# 書式:
#	new()
# メソッドの区分:
# 	public クラスメソッド。
# 説明:
# 	新しいオブジェクトを作る。
# 戻り値:
# 	作成したオブジェクトへのリファレンスを返す。
#
sub new {
    my $type = shift;
    my $new = {
	# 単語ファイルのハンドラ
	'handle' => FileHandle->new(),

	# 単語ファイル名
	'file_name' => '',

	# これまでに書き込んだエントリ数
	'entry_count' => 0,

	# 単語の書き込み方向 ($word_direction か $endword_direction)
	'direction' => $word_direction,

	# エラーメッセージ
	'error_message' => '',
    };
    return bless($new, $type);
}

#
# 書式:
#	open(file_name)
#           file_name
#		開く単語ファイルの名前。
# メソッドの区分:
# 	public インスタンスメソッド
# 説明:
# 	書き込み用に単語ファイルを開く。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub open {
    my $self = shift;
    my ($file_name) = @ARG;

    #
    # 単語ファイルを開く。
    #
    $self->{'file_name'} = $file_name;
    if (!$self->{'handle'}->open($self->{'file_name'}, 'w')) {
	$self->{'error_message'} =
	    "failed to open the file, $ERRNO: " . $self->{'file_name'};
	$self->close_internal();
	return 0;
    }

    return 1;
}

#
# 書式:
#	close()
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	単語ファイルを閉じる。開いていなければ、何もしない。
# 戻り値:
#	常に 1 を返す。
#
sub close {
    my $self = shift;

    $self->close_internal();
    return 1;
}

#
# 書式:
#	close_internal()
# メソッドの区分:
# 	private インスタンスメソッド。
# 説明:
#       close() の内部処理用メソッド。
#
sub close_internal {
    my $self = shift;

    if ($self->{'handle'}->fileno()) {
	$self->{'handle'}->close();
    }
}

#
# 書式:
#	add_entry(word, heading_position, heading_file_name,
#		  text_position, text_file_name)
#           word
#		単語
#           heading_position
# 		見出しの位置
#           heading_file_name
# 		見出しのファイル名
#           text_position
# 		本文の位置
#           text_file_name
# 		本文のファイル名
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	単語ファイルに単語を一つ追加する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_entry {
    my $self = shift;
    my ($word, $heading_position, $heading_file_name, $text_position,
	$text_file_name) = @ARG;

    #
    # 単語を正規化する。
    #
    my @unpacked_word = unpack('C*', $word);
    my $fixed_word = '';

    my $i = 0;
    while ($i < scalar(@unpacked_word)) {
	my $c0 = $unpacked_word[$i];
	if ($c0 == 0x20 || $c0 == 0x27 || $c0 == 0x2d) {
	    #
	    # ` ', `\'', `-' は削除。
	    #
	    $i++;
	} elsif (0x21 <= $c0 && $c0 <= 0x7e) {
            #
            # US-ASCII は JIS X 0208 に変換して記録。
	    # さらに、英小文字は大文字に変換して記録。
            #
	    if (0x61 <= $c0 && $c0 <= 0x7a) {
		$c0 -= 0x20;
	    }
            $fixed_word .= $ascii_to_jisx0208_table->[$c0 - 0x20];
            $i++;

	} elsif (0xa1 <= $c0 && $c0 <= 0xfe) {
            #
            # JIS X 0208 文字...
            #
            my $c1 = $unpacked_word[$i + 1];
            if (!defined($c1) || $c1 < 0xa1 || 0xfe < $c1) {
                $self->{'error_message'} =
		    sprintf("invalid character: \\x%02x", $c0);
		$self->close_internal();
                return 0;
            }

	    if ($c0 == 0xa3 && 0xc1 <= $c1 && $c1 <= 0xda) {
		#
		# 英小文字は大文字に変換して記録。
		#
		$fixed_word .= pack("CC", $c0 & 0x7f, ($c1 - 0x20) & 0x7f);
	    } elsif ($c0 == 0xa1 && ($c1 == 0xa1 || $c1 == 0xc7 || $c1 == 0xdd
				     || $c1 == 0xa6 || $c1 == 0xbe)) {
		#
		# `　', `’', `−', `・', `‐' は削除。
		#
	    } else {
		#
		# それ以外はそのまま記録。
		#
		$fixed_word .= pack("CC", $c0 & 0x7f, $c1 & 0x7f);
	    }
	    $i += 2;

	} elsif ($c0 == 0x8e) {
            #
            # SS2 を用いた JIS X 0201 カナは JIS X 0208 カナに変換して記録。
            #
            my $c1 = $unpacked_word[$i + 1];
            if (!defined($c1) || $c1 < 0xa1 || 0xfe < $c1) {
		$self->{'error_message'} =
		    sprintf("invalid character: \\x%02x", $c0);
		$self->close_internal();
                return 0;
            }
	    $fixed_word .= $jisx0201_to_jisx0208_table->[$c1 - 0xa0];
	    $i += 2;
	} else {
	    $self->{'error_message'} =
		sprintf("invalid character: \\x%02x", $c0);
	    $self->close_internal();
	    return 0;
	}
    }

    #
    # 正規化した単語の長さをチェックする。
    #
    if (length($fixed_word) == 0) {
	$self->{'error_message'} = "word is empty";
	$self->close_internal();
	return 0;
    }

    #
    # インデックスの方向が負だったら、単語の前後をひっくり返す。
    #
    if ($self->{'direction'} == $endword_direction) {
	$fixed_word = pack("n*", reverse(unpack("n*", $fixed_word)));
    }

    #
    # 単語が長すぎる場合は切り詰める。
    #
    if ($max_word_length < length($fixed_word)) {
      $fixed_word = substr($fixed_word, 0, $max_word_length);
    }

    #
    # 単語エントリをファイルへ書き込む。
    #
    if (!$self->{'handle'}
	->printf("%s\t%010x\t%s\t%010x\t%s\n", $fixed_word, $heading_position,
		 $heading_file_name, $text_position, $text_file_name)) {
	$self->{'error_message'} =
	    "failed to write the file, $ERRNO: " . $self->{'file_name'};
	$self->close_internal();
	return 0;
    }
    $self->{'entry_count'}++;

    #
    # 単語にカタカナが含まれている場合は、ひらがなに直す。
    # (ただし、「ヴ」「ヵ」「ヶ」を含む場合は直さない。)
    #
    my @unpacked_fixed_word = unpack('C*', $fixed_word);
    my $katakana_flag = 0;
    my $i = 0;
    while ($i + 1 < scalar(@unpacked_fixed_word)) {
	if ($unpacked_fixed_word[$i] == 0x25) {
	    if ($unpacked_fixed_word[$i + 1] >= 0x74) {
		$katakana_flag = 0;
		last;
	    }
	    $unpacked_fixed_word[$i] = 0x24;
	    $katakana_flag = 1;
	}
	$i += 2;
    }

    #
    # 単語に含まれていたカタカナをひらがなに直した場合は、直したエン
    # トリも合わせて書き込む。
    #
    if ($katakana_flag) {
	$fixed_word = pack("C*", @unpacked_fixed_word);
	if (!$self->{'handle'}
	    ->printf("%s\t%010x\t%s\t%010x\t%s\n", $fixed_word,
		     $heading_position, $heading_file_name, $text_position,
		     $text_file_name)) {
	    $self->{'error_message'} =
		"failed to write the file, $ERRNO: " . $self->{'file_name'};
	    $self->close_internal();
	    return 0;
	}
	$self->{'entry_count'}++;
    }

    return 1;
}

######################################################################
# <インスタンス変数の値を返すメソッド群>
#
# 書式:
#	インスタンス変数名()
# メソッドの区分:
# 	public インスタンスメソッド。
# 戻り値:
#	インスタンス変数の値を返す。
#
sub file_name {
    my $self = shift;
    return $self->{'file_name'};
}

sub entry_count {
    my $self = shift;
    return $self->{'entry_count'};
}

sub error_message {
    my $self = shift;
    return $self->{'error_message'};
}

1;
