/*                                                            -*- C -*-
 * Copyright (c) 2001-2006  Motoyuki Kasahara
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "build-pre.h"
#include "eb.h"
#include "error.h"
#include "binary.h"
#include "build-post.h"

/*
 * Unexported function.
 */
static EB_Error_Code eb_read_binary_generic(EB_Book *book,
    size_t binary_max_length, char *binary, ssize_t *binary_length);
static EB_Error_Code eb_read_binary_wave(EB_Book *book,
    size_t binary_max_length, char *binary, ssize_t *binary_length);
static EB_Error_Code eb_read_binary_mono_graphic(EB_Book *book,
    size_t binary_max_length, char *binary, ssize_t *binary_length);
static EB_Error_Code eb_read_binary_gray_graphic(EB_Book *book,
    size_t binary_max_length, char *binary, ssize_t *binary_length);


/*
 * Initialize binary context of `book'.
 */
void
eb_initialize_binary_context(EB_Book *book)
{
    LOG(("in: eb_initialize_binary_context(book=%d)", (int)book->code));

    book->binary_context.code = EB_BINARY_INVALID;
    book->binary_context.zio = NULL;
    book->binary_context.location = -1;
    book->binary_context.size = 0;
    book->binary_context.cache_length = 0;
    book->binary_context.cache_offset = 0;
    book->binary_context.width = 0;

    LOG(("out: eb_initialize_binary_context()"));
}


/*
 * Finalize binary context of `book'.
 */
void
eb_finalize_binary_context(EB_Book *book)
{
    LOG(("in+out: eb_finalize_binary_context(book=%d)", (int)book->code));

    /* nothing to be done */
}


/*
 * Reset binary context of `book'.
 */
void
eb_reset_binary_context(EB_Book *book)
{
    LOG(("in: eb_reset_binary_context(book=%d)", (int)book->code));

    eb_initialize_binary_context(book);

    LOG(("out: eb_reset_binary_context()"));
}


/*
 * Template of BMP preamble for 2 colors monochrome graphic.
 */
#define MONO_BMP_PREAMBLE_LENGTH	62

static const unsigned char mono_bmp_preamble[] = {
    /* Type. */
    'B', 'M',

    /* File size. (set at run time) */
    0x00, 0x00, 0x00, 0x00,

    /* Reserved. */
    0x00, 0x00, 0x00, 0x00,

    /* Offset of bitmap bits part. */
    0x3e, 0x00, 0x00, 0x00,

    /* Size of bitmap info part. */
    0x28, 0x00, 0x00, 0x00,

    /* Width. (set at run time) */
    0x00, 0x00, 0x00, 0x00,

    /* Height. (set at run time) */
    0x00, 0x00, 0x00, 0x00,

    /* Planes. */
    0x01, 0x00,

    /* Bits per pixels. */
    0x01, 0x00,

    /* Compression mode. */
    0x00, 0x00, 0x00, 0x00,

    /* Size of bitmap bits part. (set at run time) */
    0x00, 0x00, 0x00, 0x00,

    /* X Pixels per meter. */
    0x6d, 0x0b, 0x00, 0x00,

    /* Y Pixels per meter. */
    0x6d, 0x0b, 0x00, 0x00,

    /* Colors */
    0x02, 0x00, 0x00, 0x00,

    /* Important colors */
    0x02, 0x00, 0x00, 0x00,

    /* RGB quad of color 0   RGB quad of color 1 */
    0xff, 0xff, 0xff, 0x00,  0x00, 0x00, 0x00, 0x00,
};

/*
 * Set monochrome bitmap picture as the current binary data.
 */
EB_Error_Code
eb_set_binary_mono_graphic(EB_Book *book, const EB_Position *position,
    int width, int height)
{
    EB_Error_Code error_code;
    EB_Binary_Context *context;
    EB_Position real_position;
    unsigned char *buffer_p;
    size_t line_pad_length;
    size_t data_size;
    size_t file_size;

    eb_lock(&book->lock);
    LOG(("in: eb_set_binary_mono_graphic(book=%d, position={%d,%d}, \
width=%d, height=%d)",
	(int)book->code, position->page, position->offset, width, height));

    eb_reset_binary_context(book);

    /*
     * Current subbook must have been set.
     */
    if (book->subbook_current == NULL) {
	error_code = EB_ERR_NO_CUR_SUB;
	goto failed;
    }

    /*
     * Current subbook must have a graphic file.
     */
    if (zio_file(&book->subbook_current->text_zio) < 0) {
	error_code = EB_ERR_NO_SUCH_BINARY;
	goto failed;
    }

    /*
     * If both width and height are 0,
     * we get real width, height and position of the graphic data.
     */
    if (position->page <= 0 || position->offset < 0) {
	error_code = EB_ERR_FAIL_SEEK_BINARY;
	goto failed;
    }

    if (width == 0 && height == 0) {
	char buffer[22];

	if (zio_lseek(&book->subbook_current->text_zio,
	    (position->page - 1) * EB_SIZE_PAGE + position->offset,
	    SEEK_SET) < 0) {
	    error_code = EB_ERR_FAIL_SEEK_BINARY;
	    goto failed;
	}

	if (zio_read(&book->subbook_current->text_zio, buffer, 22) != 22) {
	    error_code = EB_ERR_FAIL_READ_BINARY;
	    goto failed;
	}
	if (eb_uint2(buffer) != 0x1f45 || eb_uint2(buffer + 4) != 0x1f31) {
	    error_code = EB_ERR_UNEXP_BINARY;
	    goto failed;
	}
	width = eb_bcd2(buffer + 8);
	height = eb_bcd2(buffer + 10);

	if (eb_uint2(buffer + 12) == 0x1f51) {
	    real_position.page = eb_bcd4(buffer + 14);
	    real_position.offset = eb_bcd2(buffer + 18);
	} else if (eb_uint2(buffer + 14) == 0x1f51) {
	    real_position.page = eb_bcd4(buffer + 16);
	    real_position.offset = eb_bcd2(buffer + 20);
	} else {
	    error_code = EB_ERR_UNEXP_BINARY;
	    goto failed;
	}

	position = &real_position;
    }

    if (width <= 0 || height <= 0) {
	error_code = EB_ERR_NO_SUCH_BINARY;
	goto failed;
    }

    /*
     * BMP requires that the number of bytes in a line must be multiple
     * of 4.  If not, 0x00 must be padded to end of each line.
     * `line_pad_length' (0...3) is the number of bytes to be padded.
     *
     * In case of EB_BINARY_MONO_GRAPHIC, a pixel is represented with
     * a bit.
     */
    if (width % 32 == 0)
	line_pad_length = 0;
    else if (width % 32 <= 8)
	line_pad_length = 3;
    else if (width % 32 <= 16)
	line_pad_length = 2;
    else if (width % 32 <= 24)
	line_pad_length = 1;
    else
	line_pad_length = 0;

    data_size = (width / 8 + line_pad_length) * height;
    file_size = data_size + MONO_BMP_PREAMBLE_LENGTH;

    /*
     * Set binary context.
     */
    context = &book->binary_context;
    context->code = EB_BINARY_MONO_GRAPHIC;
    context->zio = &book->subbook_current->text_zio;
    context->location = (position->page - 1) * EB_SIZE_PAGE + position->offset
	+ (width + 7) / 8 * (height - 1);
    context->size = (width + 7) / 8 * height;
    context->offset = 0;
    context->cache_offset = 0;
    context->width = width;

    /*
     * Set BMP preamble.
     */
    context->cache_length = MONO_BMP_PREAMBLE_LENGTH;
    memcpy(context->cache_buffer, mono_bmp_preamble, MONO_BMP_PREAMBLE_LENGTH);

    buffer_p = (unsigned char *)context->cache_buffer + 2;
    *buffer_p++ =  file_size        & 0xff;
    *buffer_p++ = (file_size >> 8)  & 0xff;
    *buffer_p++ = (file_size >> 16) & 0xff;
    *buffer_p++ = (file_size >> 24) & 0xff;

    buffer_p = (unsigned char *)context->cache_buffer + 18;
    *buffer_p++ =  width        & 0xff;
    *buffer_p++ = (width >> 8)  & 0xff;
    *buffer_p++ = (width >> 16) & 0xff;
    *buffer_p++ = (width >> 24) & 0xff;

    *buffer_p++ =  height        & 0xff;
    *buffer_p++ = (height >> 8)  & 0xff;
    *buffer_p++ = (height >> 16) & 0xff;
    *buffer_p++ = (height >> 24) & 0xff;

    buffer_p = (unsigned char *)context->cache_buffer + 34;
    *buffer_p++ =  data_size        & 0xff;
    *buffer_p++ = (data_size >> 8)  & 0xff;
    *buffer_p++ = (data_size >> 16) & 0xff;
    *buffer_p++ = (data_size >> 24) & 0xff;

    /*
     * Seek graphic file.
     */
    if (zio_lseek(context->zio, context->location, SEEK_SET) < 0) {
	error_code = EB_ERR_FAIL_SEEK_BINARY;
	goto failed;
    }

    LOG(("out: eb_set_binary_mono_graphic() = %s",
	eb_error_string(EB_SUCCESS)));
    eb_unlock(&book->lock);

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    eb_reset_binary_context(book);
    LOG(("out: eb_set_binary_mono_graphic() = %s",
	eb_error_string(error_code)));
    eb_unlock(&book->lock);
    return error_code;
}


/*
 * Template of BMP preamble for gray scale graphic.
 */
#define GRAY_BMP_PREAMBLE_LENGTH	118

static const unsigned char gray_bmp_preamble[] = {
    /* Type. */
    'B', 'M',

    /* File size. (set at run time) */
    0x00, 0x00, 0x00, 0x00,

    /* Reserved. */
    0x00, 0x00, 0x00, 0x00,

    /* Offset of bitmap bits part. */
    0x3e, 0x00, 0x00, 0x00,

    /* Size of bitmap info part. */
    0x28, 0x00, 0x00, 0x00,

    /* Width. (set at run time) */
    0x00, 0x00, 0x00, 0x00,

    /* Height. (set at run time) */
    0x00, 0x00, 0x00, 0x00,

    /* Planes. */
    0x01, 0x00,

    /* Bits per pixels. */
    0x04, 0x00,

    /* Compression mode. */
    0x00, 0x00, 0x00, 0x00,

    /* Size of bitmap bits part. (set at run time) */
    0x00, 0x00, 0x00, 0x00,

    /* X Pixels per meter. */
    0x6d, 0x0b, 0x00, 0x00,

    /* Y Pixels per meter. */
    0x6d, 0x0b, 0x00, 0x00,

    /* Colors */
    0x10, 0x00, 0x00, 0x00,

    /* Important colors */
    0x10, 0x00, 0x00, 0x00,

    /* RGB quad of color 0x0   RGB quad of color 0x1 */
    0x00, 0x00, 0x00, 0x00,    0x11, 0x11, 0x11, 0x00,

    /* RGB quad of color 0x2   RGB quad of color 0x3 */
    0x22, 0x22, 0x22, 0x00,    0x33, 0x33, 0x33, 0x00,

    /* RGB quad of color 0x4   RGB quad of color 0x5 */
    0x44, 0x44, 0x44, 0x00,    0x55, 0x55, 0x55, 0x00,

    /* RGB quad of color 0x6   RGB quad of color 0x7 */
    0x66, 0x66, 0x66, 0x00,    0x77, 0x77, 0x77, 0x00,

    /* RGB quad of color 0x8   RGB quad of color 0x9 */
    0x88, 0x88, 0x88, 0x00,    0x99, 0x99, 0x99, 0x00,

    /* RGB quad of color 0xa   RGB quad of color 0xb */
    0xaa, 0xaa, 0xaa, 0x00,    0xbb, 0xbb, 0xbb, 0x00,

    /* RGB quad of color 0xc   RGB quad of color 0xd */
    0xcc, 0xcc, 0xcc, 0x00,    0xdd, 0xdd, 0xdd, 0x00,

    /* RGB quad of color 0xe   RGB quad of color 0xf */
    0xee, 0xee, 0xee, 0x00,    0xff, 0xff, 0xff, 0x00,
};

/*
 * Set monochrome bitmap picture as the current binary data.
 */
EB_Error_Code
eb_set_binary_gray_graphic(EB_Book *book, const EB_Position *position,
    int width, int height)
{
    EB_Error_Code error_code;
    EB_Binary_Context *context;
    EB_Position real_position;
    unsigned char *buffer_p;
    size_t line_pad_length;
    size_t data_size;
    size_t file_size;

    eb_lock(&book->lock);
    LOG(("in: eb_set_binary_gray_graphic(book=%d, position={%d,%d}, \
width=%d, height=%d)",
	(int)book->code, position->page, position->offset, width, height));

    eb_reset_binary_context(book);

    /*
     * Current subbook must have been set.
     */
    if (book->subbook_current == NULL) {
	error_code = EB_ERR_NO_CUR_SUB;
	goto failed;
    }

    /*
     * Current subbook must have a graphic file.
     */
    if (zio_file(&book->subbook_current->text_zio) < 0) {
	error_code = EB_ERR_NO_SUCH_BINARY;
	goto failed;
    }

    /*
     * If both width and height are 0,
     * we get real width, height and position of the graphic data.
     */
    if (position->page <= 0 || position->offset < 0) {
	error_code = EB_ERR_FAIL_SEEK_BINARY;
	goto failed;
    }

    if (width == 0 && height == 0) {
	char buffer[22];

	if (zio_lseek(&book->subbook_current->text_zio,
	    (position->page - 1) * EB_SIZE_PAGE + position->offset,
	    SEEK_SET) < 0) {
	    error_code = EB_ERR_FAIL_SEEK_BINARY;
	    goto failed;
	}

	if (zio_read(&book->subbook_current->text_zio, buffer, 22) != 22) {
	    error_code = EB_ERR_FAIL_READ_BINARY;
	    goto failed;
	}
	if (eb_uint2(buffer) != 0x1f45
	    || eb_uint2(buffer + 4) != 0x1f31
	    || eb_uint2(buffer + 12) != 0x1f51
	    || eb_uint2(buffer + 20) != 0x1f65) {
	    error_code = EB_ERR_UNEXP_BINARY;
	    goto failed;
	}

	width = eb_bcd2(buffer + 8);
	height = eb_bcd2(buffer + 10);
	real_position.page = eb_bcd4(buffer + 14);
	real_position.offset = eb_bcd2(buffer + 18);
	position = &real_position;
    }

    if (width <= 0 || height <= 0) {
	error_code = EB_ERR_NO_SUCH_BINARY;
	goto failed;
    }

    /*
     * BMP requires that the number of bytes in a line must be multiple
     * of 4.  If not, 0x00 must be padded to end of each line.
     * `line_pad_length' (0...3) is the number of bytes to be padded.
     *
     * In case of EB_BINARY_GRAY_GRAPHIC, a pixel is represented with
     * 4 bits.
     */
    if (width % 8 == 0)
	line_pad_length = 0;
    else if (width % 8 <= 2)
	line_pad_length = 3;
    else if (width % 8 <= 4)
	line_pad_length = 2;
    else if (width % 8 <= 6)
	line_pad_length = 1;
    else
	line_pad_length = 0;

    data_size = (width / 2 + line_pad_length) * height;
    file_size = data_size + MONO_BMP_PREAMBLE_LENGTH;

    /*
     * Set binary context.
     */
    context = &book->binary_context;

    context->code = EB_BINARY_GRAY_GRAPHIC;
    context->zio = &book->subbook_current->text_zio;
    context->location = (position->page - 1) * EB_SIZE_PAGE + position->offset
	+ (width + 1) / 2 * (height - 1);
    context->size = (width + 1) / 2 * height;
    context->offset = 0;
    context->cache_offset = 0;
    context->width = width;

    /*
     * Set BMP preamble.
     */
    context->cache_length = GRAY_BMP_PREAMBLE_LENGTH;
    memcpy(context->cache_buffer, gray_bmp_preamble,
	GRAY_BMP_PREAMBLE_LENGTH);

    buffer_p = (unsigned char *)context->cache_buffer + 2;
    *buffer_p++ =  file_size        & 0xff;
    *buffer_p++ = (file_size >> 8)  & 0xff;
    *buffer_p++ = (file_size >> 16) & 0xff;
    *buffer_p++ = (file_size >> 24) & 0xff;

    buffer_p = (unsigned char *)context->cache_buffer + 18;
    *buffer_p++ =  width        & 0xff;
    *buffer_p++ = (width >> 8)  & 0xff;
    *buffer_p++ = (width >> 16) & 0xff;
    *buffer_p++ = (width >> 24) & 0xff;

    *buffer_p++ =  height        & 0xff;
    *buffer_p++ = (height >> 8)  & 0xff;
    *buffer_p++ = (height >> 16) & 0xff;
    *buffer_p++ = (height >> 24) & 0xff;

    buffer_p = (unsigned char *)context->cache_buffer + 34;
    *buffer_p++ =  data_size        & 0xff;
    *buffer_p++ = (data_size >> 8)  & 0xff;
    *buffer_p++ = (data_size >> 16) & 0xff;
    *buffer_p++ = (data_size >> 24) & 0xff;

    /*
     * Seek graphic file.
     */
    if (zio_lseek(context->zio, context->location, SEEK_SET) < 0) {
	error_code = EB_ERR_FAIL_SEEK_BINARY;
	goto failed;
    }

    LOG(("out: eb_set_binary_gray_graphic() = %s",
	eb_error_string(EB_SUCCESS)));
    eb_unlock(&book->lock);

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    eb_reset_binary_context(book);
    LOG(("out: eb_set_binary_gray_graphic() = %s",
	eb_error_string(error_code)));
    eb_unlock(&book->lock);
    return error_code;
}


/*
 * Set WAVE sound as the current binary data.
 */
EB_Error_Code
eb_set_binary_wave(EB_Book *book, const EB_Position *start_position,
    const EB_Position *end_position)
{
    EB_Error_Code error_code;
    EB_Binary_Context *context;
    off_t start_location;
    off_t end_location;
    char temporary_buffer[4];

    eb_lock(&book->lock);
    LOG(("in: eb_set_binary_wave(book=%d, start_position={%d,%d}, \
end_position={%d,%d})",
	(int)book->code, start_position->page, start_position->offset,
	end_position->page, end_position->offset));

    eb_reset_binary_context(book);

    /*
     * Current subbook must have been set.
     */
    if (book->subbook_current == NULL) {
	error_code = EB_ERR_NO_CUR_SUB;
	goto failed;
    }

    /*
     * Current subbook must have a sound file.
     */
    if (zio_file(&book->subbook_current->sound_zio) < 0) {
	error_code = EB_ERR_NO_SUCH_BINARY;
	goto failed;
    }

    /*
     * Set binary context.
     */
    if (start_position->page <= 0 || start_position->offset < 0) {
	error_code = EB_ERR_FAIL_SEEK_BINARY;
	goto failed;
    }
    if (end_position->page <= 0 || end_position->offset < 0) {
	error_code = EB_ERR_FAIL_SEEK_BINARY;
	goto failed;
    }

    start_location = (start_position->page - 1) * EB_SIZE_PAGE
	+ start_position->offset;
    end_location   = (end_position->page - 1)   * EB_SIZE_PAGE
	+ end_position->offset;

    context = &book->binary_context;

    context->code = EB_BINARY_WAVE;
    context->zio = &book->subbook_current->sound_zio;
    context->location = start_location;
    if (start_location < end_location)
	context->size = end_location - start_location + 1;
    else {
	error_code = EB_ERR_UNEXP_BINARY;
	goto failed;
    }
    context->offset = 0;

    /*
     * Read 4bytes from the sound file to check whether the sound
     * data contains a header part or not.
     *
     * If the read data is "fmt ", the wave data has a header part.
     * Otherwise, we must read a header in another location.
     *
     * The wave data consists of:
     *
     *     "RIFF" wave-size(4bytes) "WAVE" header-fragment(28bytes)
     *     data-part-size(4bytes) data
     *
     * wave-size      = "WAVE" + header-fragment + data-part-size + data
     *                = 4 + 28 + 4 + data
     *                = 36 + data
     * data-part-size = length(data)
     */
    if (zio_lseek(context->zio, context->location, SEEK_SET) < 0) {
	error_code = EB_ERR_FAIL_SEEK_BINARY;
	goto failed;
    }
    if (zio_read(context->zio, temporary_buffer, 4) != 4) {
	error_code = EB_ERR_FAIL_READ_BINARY;
	goto failed;
    }

    if (memcmp(temporary_buffer, "fmt ", 4) == 0) {
	memcpy(context->cache_buffer + 12, temporary_buffer, 4);
	if (zio_read(context->zio, context->cache_buffer + 16, 28) != 28) {
	    error_code = EB_ERR_FAIL_READ_BINARY;
	    goto failed;
	}
	if (context->size >= 32)
	    context->size -= 32;
	else
	    context->size = 0;
    } else {
	if (zio_lseek(context->zio,
	    (book->subbook_current->sound.start_page - 1) * EB_SIZE_PAGE + 32,
	    SEEK_SET) < 0) {
	    error_code = EB_ERR_FAIL_SEEK_BINARY;
	    goto failed;
	}
	if (zio_read(context->zio, context->cache_buffer + 12, 28) != 28) {
	    error_code = EB_ERR_FAIL_SEEK_BINARY;
	    goto failed;
	}

	*(unsigned char *)(context->cache_buffer + 40)
	    = (context->size)       & 0xff;
	*(unsigned char *)(context->cache_buffer + 41)
	    = (context->size >> 8)  & 0xff;
	*(unsigned char *)(context->cache_buffer + 42)
	    = (context->size >> 16) & 0xff;
	*(unsigned char *)(context->cache_buffer + 43)
	    = (context->size >> 24) & 0xff;


	/*
	 * Seek sound file, again.
	 */
	if (zio_lseek(context->zio, context->location, SEEK_SET) < 0) {
	    error_code = EB_ERR_FAIL_SEEK_BINARY;
	    goto failed;
	}
    }
    context->cache_length = 44;

    /*
     * Read and compose a WAVE header.
     */
    memcpy(context->cache_buffer, "RIFF", 4);

    *(unsigned char *)(context->cache_buffer + 4)
	= (context->size + 36)         & 0xff;
    *(unsigned char *)(context->cache_buffer + 5)
	= ((context->size + 36) >> 8)  & 0xff;
    *(unsigned char *)(context->cache_buffer + 6)
	= ((context->size + 36) >> 16) & 0xff;
    *(unsigned char *)(context->cache_buffer + 7)
	= ((context->size + 36) >> 24) & 0xff;

    memcpy(context->cache_buffer + 8, "WAVE", 4);

    LOG(("out: eb_set_binary_wave() = %s", eb_error_string(EB_SUCCESS)));
    eb_unlock(&book->lock);

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    eb_reset_binary_context(book);
    LOG(("out: eb_set_binary_wave() = %s", eb_error_string(error_code)));
    eb_unlock(&book->lock);
    return error_code;
}


/*
 * Length of the color graphic header.
 */
#define EB_COLOR_GRAPHIC_HEADER_LENGTH	8

/*
 * Set color graphic (BMP or JPEG) as the current binary data.
 */
EB_Error_Code
eb_set_binary_color_graphic(EB_Book *book, const EB_Position *position)
{
    EB_Error_Code error_code;
    EB_Binary_Context *context;
    char buffer[EB_COLOR_GRAPHIC_HEADER_LENGTH];

    eb_lock(&book->lock);
    LOG(("in: eb_set_binary_color_graphic(book=%d, position={%d,%d})",
	(int)book->code, position->page, position->offset));

    eb_reset_binary_context(book);

    /*
     * Current subbook must have been set.
     */
    if (book->subbook_current == NULL) {
	error_code = EB_ERR_NO_CUR_SUB;
	goto failed;
    }

    /*
     * Current subbook must have a graphic file.
     */
    if (zio_file(&book->subbook_current->graphic_zio) < 0) {
	error_code = EB_ERR_NO_SUCH_BINARY;
	goto failed;
    }

    /*
     * Set binary context.
     */
    if (position->page <= 0 || position->offset < 0) {
	error_code = EB_ERR_FAIL_SEEK_BINARY;
	goto failed;
    }

    context = &book->binary_context;
    context->code = EB_BINARY_COLOR_GRAPHIC;
    context->zio = &book->subbook_current->graphic_zio;
    context->location = (position->page - 1) * EB_SIZE_PAGE + position->offset;
    context->offset = 0;
    context->cache_length = 0;
    context->cache_offset = 0;

    /*
     * Seek graphic file.
     */
    if (zio_lseek(context->zio, context->location, SEEK_SET) < 0) {
	error_code = EB_ERR_FAIL_SEEK_BINARY;
	goto failed;
    }

    /*
     * Read header of the graphic data.
     * Note that EB* JPEG file lacks the header.
     */
    if (zio_read(context->zio, buffer, EB_COLOR_GRAPHIC_HEADER_LENGTH)
	!= EB_COLOR_GRAPHIC_HEADER_LENGTH) {
	error_code = EB_ERR_FAIL_READ_BINARY;
	goto failed;
    }

    if (memcmp(buffer, "data", 4) == 0) {
	context->size = eb_uint4_le(buffer + 4);
	context->location += EB_COLOR_GRAPHIC_HEADER_LENGTH;
    } else {
	context->size = 0;
	if (zio_lseek(context->zio, context->location, SEEK_SET) < 0) {
	    error_code = EB_ERR_FAIL_SEEK_BINARY;
	    goto failed;
	}
    }

    LOG(("out: eb_set_binary_color_graphic() = %s",
	eb_error_string(EB_SUCCESS)));
    eb_unlock(&book->lock);

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    eb_reset_binary_context(book);
    LOG(("out: eb_set_binary_color_graphic() = %s",
	eb_error_string(error_code)));
    eb_unlock(&book->lock);
    return error_code;
}


/*
 * Set MPEG movie as the current binary data.
 */
EB_Error_Code
eb_set_binary_mpeg(EB_Book *book, const unsigned int *argv)
{
    /*
     * `movie_file_name' is base name, and `movie_path_name' is absolute
     * path of the movie.
     */
    char movie_file_name[EB_MAX_FILE_NAME_LENGTH + 1];
    char movie_path_name[EB_MAX_PATH_LENGTH + 1];
    EB_Error_Code error_code;
    EB_Subbook *subbook;
    Zio_Code zio_code;

    eb_lock(&book->lock);
    LOG(("in: eb_set_binary_mpeg(book=%d)", (int)book->code));

    eb_reset_binary_context(book);

    /*
     * Current subbook must have been set.
     */
    subbook = book->subbook_current;
    if (subbook == NULL) {
	error_code = EB_ERR_NO_CUR_SUB;
	goto failed;
    }

    /*
     * Open the movie file and set binary context.
     */
    if (eb_compose_movie_file_name(argv, movie_file_name) != EB_SUCCESS) {
	error_code = EB_ERR_NO_SUCH_BINARY;
	goto failed;
    }
    LOG(("aux: eb_set_binary_mpeg(): movie_file_name=%s", movie_file_name));

    if (eb_find_file_name3(book->path, subbook->directory_name,
	subbook->movie_directory_name, movie_file_name, movie_path_name)
	!= EB_SUCCESS) {
	error_code = EB_ERR_NO_SUCH_BINARY;
	goto failed;
    }
    eb_compose_path_name3(book->path, subbook->directory_name,
	subbook->movie_directory_name, movie_file_name, movie_path_name);
    eb_path_name_zio_code(movie_path_name, ZIO_PLAIN, &zio_code);

    if (zio_open(&subbook->movie_zio, movie_path_name, zio_code) < 0) {
	subbook = NULL;
	error_code = EB_ERR_FAIL_OPEN_BINARY;
	goto failed;
    }

    book->binary_context.code = EB_BINARY_MPEG;
    book->binary_context.zio = &book->subbook_current->movie_zio;
    book->binary_context.location = 0;
    book->binary_context.size = 0;
    book->binary_context.offset = 0;
    book->binary_context.cache_length = 0;
    book->binary_context.cache_offset = 0;

    LOG(("out: eb_set_binary_mpeg() = %s", eb_error_string(EB_SUCCESS)));
    eb_unlock(&book->lock);

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    eb_reset_binary_context(book);
    LOG(("out: eb_set_binary_mpeg() = %s", eb_error_string(error_code)));
    eb_unlock(&book->lock);
    return error_code;
}


/*
 * Read binary data.
 */
EB_Error_Code
eb_read_binary(book, binary_max_length, binary, binary_length)
    EB_Book *book;
    size_t binary_max_length;
    char *binary;
    ssize_t *binary_length;

{
    EB_Error_Code error_code;

    eb_lock(&book->lock);
    LOG(("in: eb_read_binary(book=%d, binary_max_length=%ld)",
	(int)book->code, (long)binary_max_length));

    /*
     * Current subbook must have been set.
     */
    if (book->subbook_current == NULL) {
	error_code = EB_ERR_NO_CUR_SUB;
	goto failed;
    }

    /*
     * Return immediately if `binary_max_length' is 0.
     */
    *binary_length = 0;

    switch (book->binary_context.code) {
    case EB_BINARY_COLOR_GRAPHIC:
    case EB_BINARY_MPEG:
	error_code = eb_read_binary_generic(book, binary_max_length, binary,
	    binary_length);
	break;
    case EB_BINARY_WAVE:
	error_code = eb_read_binary_wave(book, binary_max_length,
	    binary, binary_length);
	break;
    case EB_BINARY_MONO_GRAPHIC:
	error_code = eb_read_binary_mono_graphic(book, binary_max_length,
	    binary, binary_length);
	break;
    case EB_BINARY_GRAY_GRAPHIC:
	error_code = eb_read_binary_gray_graphic(book, binary_max_length,
	    binary, binary_length);
	break;
    default:
	error_code = EB_ERR_NO_CUR_BINARY;
	goto failed;
    }
    if (error_code != EB_SUCCESS)
	goto failed;

    LOG(("out: eb_read_binary(binary_length=%ld) = %s", (long)*binary_length,
	eb_error_string(EB_SUCCESS)));
    eb_unlock(&book->lock);

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    *binary_length = -1;
    eb_reset_binary_context(book);
    LOG(("out: eb_read_binary() = %s", eb_error_string(EB_SUCCESS)));
    eb_unlock(&book->lock);
    return error_code;
}


/*
 * Read generic binary data.
 * This function is used for reading JPEG or BMP picture, and data part
 * of WAVE sound.
 */
static EB_Error_Code
eb_read_binary_generic(EB_Book *book, size_t binary_max_length,
    char *binary, ssize_t *binary_length)
{
    EB_Error_Code error_code;
    EB_Binary_Context *context;
    char *binary_p = binary;
    size_t read_length = 0;
    ssize_t read_result;

    LOG(("in: eb_read_binary_generic(book=%d, binary_max_length=%ld)",
	(int)book->code, (long)binary_max_length));

    *binary_length = 0;
    context = &book->binary_context;

    /*
     * Return immediately if `binary_max_length' is 0.
     */
    if (binary_max_length == 0)
	goto succeeded;

    /*
     * Read binary data if it is remained.
     * If context->size is 0, the binary data size is unknown.
     */
    if (0 < context->size && context->size <= context->offset)
	goto succeeded;

    if (context->size == 0)
	read_length = binary_max_length - *binary_length;
    else if (binary_max_length - *binary_length
	< context->size - context->offset)
	read_length = binary_max_length - *binary_length;
    else
	read_length = context->size - context->offset;

    read_result = zio_read(context->zio, binary_p, read_length);
    if ((0 < context->size && read_result != read_length) || read_result < 0) {
	error_code = EB_ERR_FAIL_READ_BINARY;
	goto failed;
    }

    *binary_length += read_result;
    context->offset += read_result;

  succeeded:
    LOG(("out: eb_read_binary_generic(binary_length=%ld) = %s",
	(long)*binary_length, eb_error_string(EB_SUCCESS)));

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    LOG(("out: eb_read_binary_generic() = %s", eb_error_string(error_code)));
    return error_code;
}


/*
 * Read WAVE sound data.
 */
static EB_Error_Code
eb_read_binary_wave(EB_Book *book, size_t binary_max_length, char *binary,
    ssize_t *binary_length)
{
    EB_Error_Code error_code;
    EB_Binary_Context *context;
    char *binary_p = binary;
    size_t copy_length = 0;

    LOG(("in: eb_read_binary_wave(book=%d, binary_max_length=%ld)",
	(int)book->code, (long)binary_max_length));

    *binary_length = 0;
    context = &book->binary_context;

    /*
     * Return immediately if `binary_max_length' is 0.
     */
    if (binary_max_length == 0)
	goto succeeded;

    /*
     * Copy cached data (header part) to `binary' if exists.
     */
    if (0 < context->cache_length) {
	if (binary_max_length < context->cache_length - context->cache_offset)
	    copy_length = binary_max_length;
	else
	    copy_length = context->cache_length - context->cache_offset;

	memcpy(binary_p, context->cache_buffer + context->cache_offset,
	    copy_length);
	binary_p += copy_length;
	context->cache_offset += copy_length;

	if (context->cache_length <= context->cache_offset)
	    context->cache_length = 0;

	if (binary_max_length <= *binary_length)
	    goto succeeded;
    }

    error_code = eb_read_binary_generic(book, binary_max_length - copy_length,
	binary_p, binary_length);
    if (error_code !=EB_SUCCESS)
	goto failed;
    *binary_length += copy_length;

  succeeded:
    LOG(("out: eb_read_binary_wave(binary_length=%ld) = %s",
	(long)*binary_length, eb_error_string(EB_SUCCESS)));

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    LOG(("out: eb_read_binary_wave() = %s", eb_error_string(error_code)));
    return error_code;
}


/*
 * Read monochrome graphic data.
 * The function also convert the graphic data to BMP.
 */
static EB_Error_Code
eb_read_binary_mono_graphic(EB_Book *book, size_t binary_max_length,
    char *binary, ssize_t *binary_length)
{
    EB_Error_Code error_code;
    EB_Binary_Context *context;
    unsigned char *binary_p = (unsigned char *)binary;
    size_t copy_length = 0;
    size_t read_length = 0;
    size_t line_length;
    size_t line_pad_length;

    LOG(("in: eb_read_binary_mono_graphic(book=%d, binary_max_length=%ld)",
	(int)book->code, (long)binary_max_length));

    *binary_length = 0;
    context = &book->binary_context;

    line_length = (context->width + 7) / 8;

    if (context->width % 32 == 0)
	line_pad_length = 0;
    else if (context->width % 32 <= 8)
	line_pad_length = 3;
    else if (context->width % 32 <= 16)
	line_pad_length = 2;
    else if (context->width % 32 <= 24)
	line_pad_length = 1;
    else
	line_pad_length = 0;

    /*
     * Return immediately if `binary_max_length' is 0.
     */
    if (binary_max_length == 0)
	goto succeeded;

    for (;;) {
	/*
	 * Copy cached data to `binary' if exists.
	 */
	if (0 < context->cache_length) {
	    if (binary_max_length - *binary_length
		< context->cache_length - context->cache_offset)
		copy_length = binary_max_length - *binary_length;
	    else
		copy_length = context->cache_length - context->cache_offset;

	    memcpy(binary_p, context->cache_buffer + context->cache_offset,
		copy_length);
	    binary_p += copy_length;
	    *binary_length += copy_length;
	    context->cache_offset += copy_length;

	    if (context->cache_length <= context->cache_offset)
		context->cache_length = 0;

	    if (binary_max_length <= *binary_length)
		goto succeeded;
	}

	/*
	 * Read binary data if it is remained.
	 * If padding is needed, read each line.
	 */
	read_length = line_length - context->offset % line_length;
	if (context->size - context->offset < read_length)
	    read_length = context->size - context->offset;
	if (binary_max_length - *binary_length < read_length)
	    read_length = binary_max_length - *binary_length;
	if (read_length == 0)
	    goto succeeded;

	/*
	 * Read binary data.
	 */
	if (context->offset != 0
	    && context->offset % line_length == 0
	    && zio_lseek(context->zio, (off_t)line_length * -2, SEEK_CUR)
	    < 0) {
	    error_code = EB_ERR_FAIL_SEEK_BINARY;
	    goto failed;
	}
	if (zio_read(context->zio, (char *)binary_p, read_length)
	    != read_length) {
	    error_code = EB_ERR_FAIL_READ_BINARY;
	    goto failed;
	}

	*binary_length += read_length;
	context->offset += read_length;
	binary_p += read_length;

	/*
	 * Pad 0x00 to BMP if needed.
	 */
	if (context->offset % line_length == 0) {
	    if (0 < line_pad_length) {
		if (binary_max_length - *binary_length < line_pad_length) {
		    memset(context->cache_buffer, 0, line_pad_length);
		    context->cache_length = line_pad_length;
		    context->cache_offset = 0;
		} else {
		    memset(binary_p, 0, line_pad_length);
		    binary_p += line_pad_length;
		    *binary_length += line_pad_length;
		}
	    }
	}
    }

  succeeded:
    LOG(("out: eb_read_binary_mono_graphic(binary_length=%ld) = %s",
	(long)*binary_length, eb_error_string(EB_SUCCESS)));

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    LOG(("out: eb_read_binary_mono_graphic() = %s",
	eb_error_string(error_code)));
    return error_code;
}


/*
 * Read gray scale graphic data.
 * The function also convert the graphic data to BMP.
 */
static EB_Error_Code
eb_read_binary_gray_graphic(EB_Book *book, size_t binary_max_length,
    char *binary, ssize_t *binary_length)
{
    EB_Error_Code error_code;
    EB_Binary_Context *context;
    unsigned char *binary_p = (unsigned char *)binary;
    size_t copy_length = 0;
    size_t read_length = 0;
    size_t line_length;
    size_t line_pad_length;

    LOG(("in: eb_read_binary_gray_graphic(book=%d, binary_max_length=%ld)",
	(int)book->code, (long)binary_max_length));

    *binary_length = 0;
    context = &book->binary_context;

    line_length = (context->width + 1) / 2;

    if (context->width % 8 == 0)
	line_pad_length = 0;
    else if (context->width % 8 <= 2)
	line_pad_length = 3;
    else if (context->width % 8 <= 4)
	line_pad_length = 2;
    else if (context->width % 8 <= 6)
	line_pad_length = 1;
    else
	line_pad_length = 0;

    /*
     * Return immediately if `binary_max_length' is 0.
     */
    if (binary_max_length == 0)
	goto succeeded;

    for (;;) {
	/*
	 * Copy cached data to `binary' if exists.
	 */
	if (0 < context->cache_length) {
	    if (binary_max_length - *binary_length
		< context->cache_length - context->cache_offset)
		copy_length = binary_max_length - *binary_length;
	    else
		copy_length = context->cache_length - context->cache_offset;

	    memcpy(binary_p, context->cache_buffer + context->cache_offset,
		copy_length);
	    binary_p += copy_length;
	    *binary_length += copy_length;
	    context->cache_offset += copy_length;

	    if (context->cache_length <= context->cache_offset)
		context->cache_length = 0;

	    if (binary_max_length <= *binary_length)
		goto succeeded;
	}

	/*
	 * Read binary data if it is remained.
	 * If padding is needed, read each line.
	 */
	read_length = line_length - context->offset % line_length;
	if (context->size - context->offset < read_length)
	    read_length = context->size - context->offset;
	if (binary_max_length - *binary_length < read_length)
	    read_length = binary_max_length - *binary_length;
	if (read_length == 0)
	    goto succeeded;

	/*
	 * Read binary data.
	 */
	if (context->offset != 0
	    && context->offset % line_length == 0
	    && zio_lseek(context->zio, (off_t)line_length * -2, SEEK_CUR)
	    < 0) {
		error_code = EB_ERR_FAIL_SEEK_BINARY;
		goto failed;
	}
	if (zio_read(context->zio, (char *)binary_p, read_length)
	    != read_length) {
	    error_code = EB_ERR_FAIL_READ_BINARY;
	    goto failed;
	}

	*binary_length += read_length;
	context->offset += read_length;
	binary_p += read_length;

	/*
	 * Pad 0x00 to BMP if needed.
	 */
	if (context->offset % line_length == 0) {
	    if (0 < line_pad_length) {
		if (binary_max_length - *binary_length < line_pad_length) {
		    memset(context->cache_buffer, 0, line_pad_length);
		    context->cache_length = line_pad_length;
		    context->cache_offset = 0;
		} else {
		    memset(binary_p, 0, line_pad_length);
		    binary_p += line_pad_length;
		    *binary_length += line_pad_length;
		}
	    }
	}
    }

  succeeded:
    LOG(("out: eb_read_binary_gray_graphic(binary_length=%ld) = %s",
	(long)*binary_length, eb_error_string(EB_SUCCESS)));

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    LOG(("out: eb_read_binary_gray_graphic() = %s",
	eb_error_string(error_code)));
    return error_code;
}


/*
 * Unset current binary.
 */
void
eb_unset_binary(EB_Book *book)
{
    eb_lock(&book->lock);
    LOG(("in: eb_unset_binary(book=%d)", (int)book->code));

    eb_reset_binary_context(book);

    LOG(("out: eb_unset_binary()"));
    eb_unlock(&book->lock);
}


