#                                                         -*- Perl -*-
# Copyright (c) 2001  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.
#

#
# BMP カラー画像を収めたファイルを生成するクラス
#
package FreePWING::ColorGraphic;

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

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

@ISA = qw(Exporter);

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

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

	# バイナリファイル名
	'file_name' => '',

	# 書き込んだエントリ数
	'entry_count' => 0,

	# バイナリファイルのオフセット (これまでに書き込んだバイト数)
	'position' => 0,

	# タグ
	'tag' => FreePWING::Tag->new(),

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

#
# 書式:
#	open(file_name, [tag_file_name])
#           file_name
#		テキストファイルの名前。
#           tag_file_name
#		タグファイルの名前。
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	書き込み用のテキストファイルを開く。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub open {
    my $self = shift;
    my ($file_name, $tag_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;
    }
    binmode($self->{'handle'});

    #
    # タグファイルを開く。
    #
    if (defined($tag_file_name)
	&& !$self->{'tag'}->open($tag_file_name)) {
	$self->{'error_message'} = $self->{'tag'}->error_message();
	$self->close_internal();
        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;
    }
	
    #
    # ファイルを閉じる。
    #
    $self->close_internal();

    return 1;
}

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

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

#
# 書式:
#	add_data(tag, file_name)
#	    tag
#		タグ名
#	    file_name
#		追加するバイナリファイル名
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	与えられたファイルの内容を、データとして追加する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_data {
    my $self = shift;
    my ($tag, $file_name) = @ARG;

    my ($start_position) = $self->{'position'};

    #
    # タグを登録する。
    #
    if (!$self->{'tag'}->add_entry('cgraph:' . $tag, $start_position)) {
	$self->{'error_message'} = $self->{'tag'}->error_message();
	$self->close_internal();
	return 0;
    }

    #
    # ヘッダを書き込む。
    #
    my $file_size = -s $file_name;
    my $header = pack('a4V', 'data', $file_size);
    if (!$self->{'handle'}->print($header)) {
	$self->{'error_message'} =
	    "failed to write the file, $ERRNO: " . $self->{'file_name'};
	$self->close_internal();
	return 0;
    }
    $self->{'position'} += length($header);

    #
    # 指定されたファイルを開く。
    #
    my $handle = FileHandle->new();
    if (!$handle->open($file_name, 'r')) {
	$self->{'error_message'} =
	    "failed to open the file, $ERRNO: " . $file_name;
	$self->close_internal();
	return 0;
    }
    binmode($handle);

    #
    # 指定されたファイルを読み込んで、バイナリファイルに書き込む。
    #
    my ($data, $data_length);
    for (;;) {
	$data_length = $handle->read($data, $block_length);
	if (!defined($data_length)) {
	    $self->{'error_message'} =
		"failed to read the file, $ERRNO: " . $file_name;
	    $self->close_internal();
	    return 0;
	} elsif ($data_length == 0) {
	    last;
	}
	if (!$self->{'handle'}->print($data)) {
	    $self->{'error_message'} =
		"failed to write the file, $ERRNO: " . $self->{'file_name'};
	    $self->close_internal();
	    return 0;
	}

	$self->{'position'} += $data_length;
    }

    #
    # 指定されたファイルを閉じる。
    #
    $handle->close();
    $self->{'entry_count'}++;

    #
    # 終了タグを登録する。
    #
    if (defined($self->{'end_tag_prefix'})) {
	my ($end_tag) = $self->{'end_tag_prefix'} . ':' . $tag;
	my ($end_position) = $self-{'position'} - 1;
	if ($end_position < $start_position) {
	    $end_position = $start_position;
	}

	if (!$self->{'tag'}->add_entry($end_tag, $end_position)) {
	    $self->{'error_message'} = $self->{'tag'}->error_message();
	    $self->close_internal();
	    return 0;
	}
    }

    return 1;
}

#
# 書式:
#	add_binary(tag, binary)
#	    tag
#		タグ名
#	    binary
#		追加するバイナリデータ
# メソッドの区分:
# 	public インスタンスメソッド。
# 説明:
# 	与えられたバイナリデータの内容を、データとして追加する。
# 戻り値:
#	成功すれば 1 を返す。失敗すれば 0 を返す。
#
sub add_binary {
    my $self = shift;
    my ($tag, $binary) = @ARG;

    my ($start_position) = $self->{'position'};

    #
    # タグを登録する。
    #
    if (!$self->{'tag'}->add_entry('cgraph:' . $tag, $start_position)) {
	$self->{'error_message'} = $self->{'tag'}->error_message();
	$self->close_internal();
	return 0;
    }

    #
    # ヘッダを書き込む。
    #
    my $file_size = length($binary);
    my $header = pack('a4V', 'data', $file_size);
    if (!$self->{'handle'}->print($header)) {
	$self->{'error_message'} =
	    "failed to write the file, $ERRNO: " . $self->{'file_name'};
	$self->close_internal();
	return 0;
    }
    $self->{'position'} += length($header);

    #
    # バイナリデータを書き込む。
    #
    if (!$self->{'handle'}->print($binary)) {
	$self->{'error_message'} =
	    "failed to write the file, $ERRNO: " . $self->{'file_name'};
	$self->close_internal();
	return 0;
    }
    $self->{'position'} += $file_size;

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

    #
    # 終了タグを登録する。
    #
    if (defined($self->{'end_tag_prefix'})) {
	my ($end_tag) = $self->{'end_tag_prefix'} . ':' . $tag;
	my ($end_position) = $self-{'position'} - 1;
	if ($end_position < $start_position) {
	    $end_position = $start_position;
	}

	if (!$self->{'tag'}->add_entry($end_tag, $end_position)) {
	    $self->{'error_message'} = $self->{'tag'}->error_message();
	    $self->close_internal();
	    return 0;
	}
    }

    return 1;
}

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

sub tag_file_name {
    my $self = shift;
    return $self->{'tag'}->file_name();
}

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

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

1;
