#                                                         -*- 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.
#

#
# 書籍管理情報を収めたファイルを生成するためのクラス。
#
package FreePWING::Control;

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

use vars qw(@ISA
	    @EXPORT
	    @EXPORT_OK
	    $block_length);

@ISA = qw(Exporter);

#
# ブロックの長さ (バイト数)
#
$block_length = 2048;

#
# 書式:
#	new()
# メソッドの区分:
# 	public クラスメソッド。
# 説明:
# 	新しいオブジェクトを作る。
# 戻り値:
# 	作成したオブジェクトへのリファレンスを返す。
#
sub new {
    my $type = shift;
    my $new = {
	'handle' => FileHandle->new(),
	'file_name' => '',
	'entry_count' => 0,
	'reference' => FreePWING::Reference->new(),
	'error_message' => '',
    };
    return bless($new, $type);
}

#
# 書式:
#	open(file_name, reference_file_name)
#           file_name
#		書籍管理情報ファイルの名前。
#           reference_file_name
#		参照情報ファイルの名前。
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	書き込み用に書籍管理情報ファイルを開く。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub open {
    my $self = shift;
    my ($file_name, $reference_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'};
	return 0;
    }
    binmode($self->{'handle'});

    #
    # 参照情報ファイルを開く。
    #
    if (!$self->{'reference'}->open($reference_file_name)) {
	$self->{'error_message'} = $self->{'reference'}->error_message();
	$self->{'handle'}->close();
        return 0;
    }

    #
    # 先頭 16 バイト (書籍構成要素数〜予備領域 2) を 0x00 で埋める。
    #
    if (!$self->{'handle'}->print("\0" x 16)) {
	$self->{'error_message'} = 
	    "failed to write the file, $ERRNO: " . $self->{'file_name'};
	return 0;
    }

    return 1;
}

#
# 書式:
#	close()
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	書籍管理情報ファイルを閉じる。開いていなければ、何もしない。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub close {
    my $self = shift;

    if (!$self->{'handle'}->fileno()) {
	return 1;
    }

    #
    # 半端なブロックの後方を "\0" で埋める。
    #
    my $pad_length = $block_length - $self->{'handle'}->tell() % $block_length;
    if ($pad_length < $block_length
	&& !$self->{'handle'}->print("\0" x $pad_length)) {
	$self->{'error_message'} = 
	    "failed to write the file, $ERRNO: " . $self->{'file_name'};
	return 0;
    }

    #
    # 書籍構成要素数を書き込む。
    #
    if (!$self->{'handle'}->seek(0, FileHandle->SEEK_SET)) {
	$self->{'error_message'} = 
	    "failed to seek the file, $ERRNO: " . $self->{'file_name'};
	return 0;
    }
    if (!$self->{'handle'}->print(pack('n', $self->{'entry_count'}))) {
	$self->{'error_message'} = 
	    "failed to write the file, $ERRNO: " . $self->{'file_name'};
	return 0;
    }
	
    #
    # ファイルを閉じる。
    #
    $self->close_internal();

    return 1;
}

#
# 書式:
#	close_internal()
# メソッドの区分:
# 	private インスタンスメソッド。
# 説明:
# 	他のメソッド処理中に異常が起きた場合の処理を行う。
#
sub close_internal {
    my $self = shift;

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

#
# 書式:
#	add_entry(id, parameter, file_name...)
#	    id
#		書籍構成要素の ID 値 (例: メニューなら 0x20)
#           parameter
#		書籍構成要素のパラメタ
#           file_name
# 		ファイル名
# メソッドの区分:
# 	private インスタンスメソッド。
# 説明:
# 	書籍管理情報ファイルにエントリを一つ追加する。引数の最初
#	のファイル名の先頭位置がエントリの先頭アドレスになり、引
#	数の全ファイルのサイズの合計がエントリの領域サイズになる。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_entry {
    my $self = shift;
    my ($id, $parameter, @file_names) = @ARG;
    my $length = 0;

    #
    # $file_name のサイズ (ブロック数) を得る。
    #
    my $f;
    foreach $f (@file_names) {
	my $size = -s $f;
	if (!defined($size)) {
	    $self->{'error_message'} = "failed to stat the file, $ERRNO: $f";
	    $self->close_internal();
	    return 0;
	}
	$length += ($size + $block_length - 1) / $block_length;
    }

    #
    # エントリ (16 バイト) を書き込む。
    #
    if (!$self->{'handle'}->print(pack('CCNNNn', $id, "\0", 0,
				       $length, $parameter, 0))) {
	$self->{'error_message'} = 
	    "failed to write the file, $ERRNO: " . $self->{'file_name'};
	$self->close_internal();
	return 0;
    }

    #
    # 先頭アドレス (先ほどの pack の最初の 'N' の位置) に参照情報を
    # 書き込む。
    #
    my $position = $self->{'handle'}->tell() - 14;
    if (!$self->{'reference'}->add_block_entry($position, 0, $file_names[0])) {
	$self->{'error_message'} = $self->{'reference'}->error_message();
	$self->close_internal();
	return 0;
    }

    $self->{'entry_count'}++;

    return 1;
}

######################################################################
# <書籍構成要素を追加するメソッド群>
#
# 書式:
#	メソッド名(file_name...)
#           file_name
# 		ファイル名
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	書籍管理情報ファイルに特定の書籍構成要素を追加し、ファイル名
#       と結び付ける。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#

#
# 本文データ
#
sub add_text_entry {
    my $self = shift;
    my ($file_name) = @ARG;
    return $self->add_entry(0x00, 0x02000000, $file_name);
}

#
# 見出し
#
sub add_heading_entry {
    my $self = shift;
    my ($file_name) = @ARG;
    return $self->add_entry(0x05, 0x02000000, $file_name)
	&& $self->add_entry(0x07, 0x02000000, $file_name);
}

#
# メニュー表示用データ
#
sub add_menu_entry {
    my $self = shift;
    my ($file_name) = @ARG;
    return $self->add_entry(0x01, 0x02000000, $file_name);
}

#
# 著作権表示データ
#
sub add_copyright_entry {
    my $self = shift;
    my ($file_name) = @ARG;
    return $self->add_entry(0x02, 0x02000000, $file_name);
}

#
# 後方一致インデックス
#
sub add_endindex_entry {
    my $self = shift;
    my (@file_names) = @ARG;
    return $self->add_entry(0x71, 0x02415554, @file_names);
}

#
# 条件検索インデックス
#
sub add_keyindex_entry {
    my $self = shift;
    my (@file_names) = @ARG;
    return $self->add_entry(0x80, 0x02415554, @file_names);
}

#
# 前方一致インデックス
#
sub add_index_entry {
    my $self = shift;
    my (@file_names) = @ARG;
    return $self->add_entry(0x91, 0x02415554, @file_names);
}

#
# カラー図版
#
sub add_color_graphic_entry {
    my $self = shift;
    my (@file_names) = @ARG;
    return $self->add_entry(0xd2, 0x00000000, @file_names);
}

#
# 音声
#
sub add_sound_entry {
    my $self = shift;
    my (@file_names) = @ARG;
    return $self->add_entry(0xd8, 0x00000000, @file_names);
}

######################################################################
# <インスタンス変数の値を返すメソッド群>
#
# 書式:
#	インスタンス変数名()
# メソッドの区分:
# 	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;

