/*                                                            -*- 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,
      ((off_t) 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 = ((off_t) 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,
      ((off_t) 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 = ((off_t) 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 = ((off_t) start_position->page - 1) * EB_SIZE_PAGE
    + start_position->offset;
  end_location   = ((off_t) 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,
      ((off_t) 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 = ((off_t) 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_file_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);
}


