/******************************************************************************
 * Copyright (C) 2007 Tetsuya Kimata <kimata@acapulco.dyndns.org>
 *
 * All rights reserved.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any
 * damages arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any
 * purpose, including commercial applications, and to alter it and
 * redistribute it freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must
 *    not claim that you wrote the original software. If you use this
 *    software in a product, an acknowledgment in the product
 *    documentation would be appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must
 *    not be misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source
 *    distribution.
 *
 * $Id: ZipFileWriter.cpp.in 2694 2007-11-18 12:22:26Z svn $
 *****************************************************************************/

#ifndef TEMPLATE_INSTANTIATION
#include "Environment.h"
#endif

#include <zlib.h>

#include "ZipFileWriter.h"
#include "File.h"
#include "SourceInfo.h"

#ifndef TEMPLATE_INSTANTIATION
SOURCE_INFO_ADD("$Id: ZipFileWriter.cpp.in 2694 2007-11-18 12:22:26Z svn $");
#endif

template<class W> const apr_byte_t
ZipFileWriter<W>::HEADER[]  = {
    0x1F, 0x8B, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 0x03
};


/******************************************************************************
 * public メソッド
 *****************************************************************************/
template<class W>
ZipFileWriter<W>::ZipFileWriter(apr_pool_t *pool, apr_file_t *file,
                                apr_size_t offset)
  : writer_impl_(pool, file, offset),
    is_closed_(false)
{
    int state;

    crc_ = crc32(0, Z_NULL, 0);

    APR_PALLOC(z_state_, z_stream *, pool, sizeof(z_stream));
    z_state_->zalloc = Z_NULL;
    z_state_->zfree  = Z_NULL;
    z_state_->opaque = Z_NULL;

    state = deflateInit2(z_state_, Z_BEST_COMPRESSION, Z_DEFLATED, -15, 9,
                         Z_DEFAULT_STRATEGY);
    if (state != Z_OK) {
        THROW(MESSAGE_SYS_MEMORY_ALLOC_FAILED);
    }

    z_state_->next_out = buffer_;
    z_state_->avail_out = sizeof(buffer_);

    write_wrapper(HEADER, sizeof(HEADER));
}

template<class W>
void ZipFileWriter<W>::close()
{
    apr_byte_t buffer[8];

    if (is_closed_) {
        return;
    }

    flush_buffer();

    buffer[0] = static_cast<apr_byte_t>((crc_ >>  0) & 0xff);
    buffer[1] = static_cast<apr_byte_t>((crc_ >>  8) & 0xff);
    buffer[2] = static_cast<apr_byte_t>((crc_ >> 16) & 0xff);
    buffer[3] = static_cast<apr_byte_t>((crc_ >> 24) & 0xff);
    buffer[4] = static_cast<apr_byte_t>((z_state_->total_in >>  0) & 0xff);
    buffer[5] = static_cast<apr_byte_t>((z_state_->total_in >>  8) & 0xff);
    buffer[6] = static_cast<apr_byte_t>((z_state_->total_in >> 16) & 0xff);
    buffer[7] = static_cast<apr_byte_t>((z_state_->total_in >> 24) & 0xff);

    write_wrapper(buffer, sizeof(buffer));

    writer_impl_.close();
    is_closed_ = true;
}


/******************************************************************************
 * private メソッド
 *****************************************************************************/
template<class W>
apr_size_t ZipFileWriter<W>::write_impl(const void *buffer, apr_size_t size)
{
    crc_ = crc32(crc_, reinterpret_cast<const Bytef *>(buffer), size);

    z_state_->next_in = reinterpret_cast<Bytef *>(const_cast<void *>(buffer));
    z_state_->avail_in = size;

    deflate(z_state_, Z_NO_FLUSH);

    update_buffer();

    return size - z_state_->avail_in;
}

template<class W>
void ZipFileWriter<W>::update_buffer()
{
    while (1) {
        if (z_state_->avail_out == 0) {
            write_wrapper(buffer_, sizeof(buffer_));
            z_state_->next_out = buffer_;
            z_state_->avail_out = sizeof(buffer_);
        }

        if (z_state_->avail_in == 0) {
            break;
        }

        deflate(z_state_, Z_NO_FLUSH);
    }
}

template<class W>
void ZipFileWriter<W>::flush_buffer()
{
    apr_size_t status;

    while (1) {
        status = deflate(z_state_, Z_FINISH);

        if (sizeof(buffer_) != z_state_->avail_out) {
            write_wrapper(buffer_, sizeof(buffer_) - z_state_->avail_out);
            z_state_->next_out = buffer_;
            z_state_->avail_out = sizeof(buffer_);
        }
        if (status == Z_STREAM_END) {
            break;
        }
    }

    deflateEnd(z_state_);
}

template<class W>
void ZipFileWriter<W>::write_wrapper(const void *buffer, apr_size_t size)
{
    writer_impl_.write(buffer, size);
}

// テンプレートのインスタンス化
#define TEMPLATE_INSTANTIATION
#include "@ORIG_WRITER_NAME@.cpp"

template class ZipFileWriter<@ORIG_WRITER_CLASS@>;


/******************************************************************************
 * テスト
 *****************************************************************************/
#ifdef DEBUG_ZipFileWriter

#include "TestRunner.h"

static const apr_size_t BUFFER_SIZE     = 4096;

void show_usage(const char *prog_name)
{

    cerr << "Usage: " << prog_name << " <ENCODED_FILE> <DECODED_FILE>" << endl;
}

void run_copy(apr_pool_t *pool,
              const char *plain_file_path, const char *zipped_file_path)
{
    volatile double start_time;
    volatile double end_time;
    apr_mmap_t *plain_file_map;
    const char *plain_file_data;
    apr_size_t write_size;
    apr_size_t remain_size;

    show_test_name("copy");

    File plain_file(pool, plain_file_path);
    plain_file.open(APR_READ|APR_BINARY);
    plain_file_map = plain_file.mmap();

    File zipped_file(pool, zipped_file_path);
    zipped_file.open(APR_CREATE|APR_TRUNCATE|APR_READ|APR_WRITE|APR_BINARY);
    ZipFileWriter<> zipped_file_writer(pool, zipped_file.get_handle());

    remain_size = static_cast<apr_size_t>(plain_file.get_size()); // 4G の壁
    plain_file_data = AS_CONST_CHAR(plain_file_map->mm);
    start_time = get_usage_sec();
    while (remain_size > 0) {
        write_size = (remain_size > BUFFER_SIZE) ? BUFFER_SIZE : remain_size;

        zipped_file_writer.write(plain_file_data, write_size);

        plain_file_data += write_size;
        remain_size -= write_size;
    }
    zipped_file_writer.close();
    end_time = get_usage_sec();

    show_item("copy", (end_time - start_time)*1000, " msec");
}

void run_all(apr_pool_t *pool, int argc, const char * const *argv)
{
    const char *plain_file_path;
    const char *zipped_file_path;

    if (argc != 3) {
        THROW(MESSAGE_ARGUMENT_INVALID);
    }

    plain_file_path = argv[1];
    zipped_file_path = argv[2];

    if (!File::is_exist(pool, plain_file_path)) {
        THROW(MESSAGE_FILE_NOT_FOUND);
    }

    show_item("plain_file_path", plain_file_path);
    show_item("zipped_file_path", zipped_file_path);

    show_line();
    run_copy(pool, plain_file_path, zipped_file_path);
}

#endif

// Local Variables:
// mode: c++
// coding: utf-8-dos
// End:
