/******************************************************************************
 * Copyright (C) 2006 Tetsuya Kimata <kimata@acapulco.dyndns.org>
 *
 * All rights reserved.
 *
 * This software is provided 'as-is', without any express or without_lockied
 * 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: UploadItemManager.cpp 2173 2006-12-06 16:12:56Z svn $
 *****************************************************************************/

#include "Environment.h"

#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <memory>

#include "apr_time.h"
#include "apr_file_info.h"

#include "UploadItemManager.h"
#include "UploadItem.h"
#include "UploadItemList.h"
#include "ThumbnailList.h"
#include "TemporaryPool.h"
#include "Locker.h"
#include "ReadLocker.h"
#include "WriteLocker.h"
#include "Message.h"
#include "Macro.h"
#include "SourceInfo.h"

SOURCE_INFO_ADD("$Id: UploadItemManager.cpp 2173 2006-12-06 16:12:56Z svn $");

#ifdef DEBUG
#include <iostream>
#endif

#define AS_UITEM_MANAGER(pointer)   reinterpret_cast<UploadItemManager *>(pointer)

#ifdef DEBUG
#define REQUIRE_MANAGER_LOCKED()                                \
    do {                                                        \
        if (!ReadLocker::is_locked(manager_ref_count_)) {       \
            throw MESSAGE_MANAGER_SHOULD_LOCKED                 \
                " (" __FILE__ ":" APR_STRINGIFY(__LINE__) ")";  \
        }                                                       \
    } while (0)
#else
#define REQUIRE_MANAGER_LOCKED()
#endif

/******************************************************************************
 * public メソッド
 *****************************************************************************/
void UploadItemManager::init(UploadItemList *item_list,
                             ThumbnailList *thumbnail_list)
{
    clear_shm();

    item_list_ = item_list;
    thumbnail_list_ = thumbnail_list;

    init_shm();
}

apr_size_t UploadItemManager::add(apr_pool_t *pool,
                                  const char *file_name, const char *file_mime,
                                  apr_off_t file_size, const char *file_digest,
                                  const char *file_temp_path,
                                  const char *remove_pass,
                                  const char *download_pass,
                                  const char *comment, const char *ip_address)
{
    apr_size_t item_id;
    apr_size_t item_index;
    UploadItem *uitem;
    apr_time_t current_time;
    WriteLocker manager_locker(manager_ref_count_);

    create_item_id_index(&item_id, &item_index);
    current_time = apr_time_now();

    uitem = UploadItem::get_instance
        (pool, item_id, item_index, file_size, current_time, ip_address,
         file_name, file_mime, file_digest, remove_pass, download_pass,
         comment);

    // 古いアイテムを削除
    while (item_list_->is_full(file_size)) {
        UploadItem *old_uitem;

        old_uitem = AS_UITEM(item_list_->get_by_index(item_list_->size()-1));

        item_writer_.remove(pool, old_uitem);
        thumbnail_list_->remove(old_uitem->get_id());
        item_list_->remove(old_uitem->get_id());
    }

    if (item_writer_.write(uitem, file_temp_path)) {
        thumbnail_list_->add(uitem->get_id());
    }
    item_list_->add(uitem);

    *mtime_ = current_time;
    sync_all_item(pool);

    return item_id;
}

void UploadItemManager::remove(apr_pool_t *pool, UploadItem *uitem)
{
    WriteLocker manager_locker(manager_ref_count_);

    REQUIRE_MANAGER_LOCKED();

    // 処理の順番を変えるとまずい
    item_writer_.remove(pool, uitem);
    item_list_->remove(uitem->get_id());
    thumbnail_list_->remove(uitem->get_id());

    *mtime_ = apr_time_now();
    sync_all_item(pool);
}

void UploadItemManager::sync_all_item(apr_pool_t *pool)
{
    UploadItem::header_t *header;

    for (apr_size_t i = 0; i < item_list_->size(); i++) {
        header = item_list_->get_by_index(i);

        if (header->is_need_sync) {
            item_writer_.write_data(pool, header);
        }
    }
}

UploadItem *UploadItemManager::get_item(apr_pool_t *pool, apr_size_t item_id)
{
    UploadItem *uitem;
    ReadLocker manager_locker(manager_ref_count_);

    REQUIRE_MANAGER_LOCKED();

    APR_PALLOC(uitem, UploadItem *, pool, sizeof(UploadItem));
    memcpy(uitem, item_list_->get_by_id(item_id), sizeof(UploadItem));

    return uitem;
}

apr_file_t *UploadItemManager::get_item_file(apr_pool_t *pool,
                                             apr_size_t item_id)
{
    const char *file_path;
    apr_file_t *file;
    UploadItem *uitem;
    // カウンタを保護するだけなので読み出しロックで OK
    ReadLocker manager_locker(manager_ref_count_);

    REQUIRE_MANAGER_LOCKED();

    uitem = AS_UITEM(item_list_->get_by_id(item_id));

    file_path = UploadItemIO::get_file_path(pool, file_dir_path_, uitem);

    uitem->increment_download_count();

    apr_file_open(&file, file_path, APR_READ|APR_SENDFILE_ENABLED,
                  APR_OS_DEFAULT, pool);

    return file;
}

UploadItemManager *UploadItemManager::get_instance(apr_shm_t *shm,
                                                   apr_pool_t *pool,
                                                   const char *data_dir_path,
                                                   const char *file_dir_path,
                                                   const char *thumb_dir_path)
{
    UploadItemManager *item_manager;

#ifdef DEBUG
    if (apr_shm_size_get(shm) != get_memory_size()) {
        THROW(MESSAGE_SHM_SIZE_INVALID);
    }
#endif

    item_manager = AS_UITEM_MANAGER(apr_shm_baseaddr_get(shm));
    new(item_manager) UploadItemManager(pool, data_dir_path, file_dir_path,
                                        thumb_dir_path);

    return item_manager;
}

UploadItemManager *UploadItemManager::child_init(apr_shm_t *shm,
                                                 UploadItemList *item_list,
                                                 ThumbnailList *thumbnail_list)
{
    UploadItemManager *item_manager;

    item_manager = AS_UITEM_MANAGER(apr_shm_baseaddr_get(shm));
    item_manager->child_init_impl(item_list, thumbnail_list);

    return item_manager;
}

apr_size_t UploadItemManager::get_memory_size()
{
    return sizeof(UploadItemManager) + sizeof(apr_time_t) +
        sizeof(apr_atomic_t);
}

void UploadItemManager::check_finalize_state(UploadItemManager *item_manager)
{
    if (ReadLocker::is_locked(item_manager->manager_ref_count_)) {
        THROW(MESSAGE_MANAGER_SHOULD_NOT_LOCKED);
    }
}


/******************************************************************************
 * private メソッド
 *****************************************************************************/
UploadItemManager::UploadItemManager(apr_pool_t *pool,
                                     const char *data_dir_path,
                                     const char *file_dir_path,
                                     const char *thumb_dir_path)
  : pool_(pool),
    data_dir_path_(data_dir_path),
    file_dir_path_(file_dir_path),
    thumb_dir_path_(thumb_dir_path),
    item_writer_(pool_, data_dir_path, file_dir_path, thumb_dir_path)
{

}

void UploadItemManager::child_init_impl(UploadItemList *item_list,
                                        ThumbnailList *thumbnail_list)
{
    item_list_ = item_list;
    thumbnail_list_ = thumbnail_list;

    attach_shm();
}

void UploadItemManager::clear_shm()
{
    memset(this + 1, 0, get_memory_size() - sizeof(UploadItemManager));
}

void UploadItemManager::init_shm()
{
    mtime_ = AS_TIME(this + 1);
    manager_ref_count_ = AS_ATOMIC(mtime_ + 1);

#ifdef __INTEL_COMPILER
#pragma warning(disable:1684)
#endif
    ASSERT_EQUAL(AS_CHAR(manager_ref_count_ + 1),
                 AS_CHAR(mtime_) +
                 (get_memory_size() - sizeof(UploadItemManager)));
#ifdef __INTEL_COMPILER
#pragma warning(default:1684)
#endif
}

void UploadItemManager::attach_shm()
{
    init_shm();
}

void UploadItemManager::create_item_id_index(apr_size_t *item_id,
                                             apr_size_t *item_index) const
{
    UploadItem::header_t *header;

    *item_id = static_cast<apr_size_t>(apr_time_sec(apr_time_now()));

    if (item_list_->size() == 0) {
        *item_index = 1;
        return;
    }

    header = item_list_->get_by_index(0);
    while (*item_id < header->id) {
        (*item_id)++;
    }
    *item_index = header->index + 1;
}


/******************************************************************************
 * テスト
 *****************************************************************************/
#ifdef DEBUG_UploadItemManager
#include <string>

#include "apr_shm.h"

#include "TestRunner.h"
#include "UploadItemListReader.h"
#include "CleanPointer.h"
#include "Auxiliary.h"

// static const apr_size_t TARGET_ITEM_COUNT = 10;
// static const apr_size_t RUN_ITER_COUNT      = 5000;

void show_usage(const char *prog_name)
{
    cerr << "Usage: " << prog_name << " <DAT_DIR_PATH>" << endl;
}

// static void run_read_by_index(apr_pool_t *pool,
//                               UploadItemManager *item_manager,
//                               apr_size_t item_count,
//                               apr_size_t *ids_sort_by_view,
//                               apr_uint16_t *indexes_sort_by_view)
// {
//     volatile double start_time;
//     volatile double end_time;
//     BBSCommentIterator *bcomment_iter;

//     APR_PALLOC(bcomment_iter, BBSCommentIterator *, pool,
//                BBSCommentIterator::MEMORY_SIZE);

//     start_time = get_usage_sec();
//     for (apr_size_t i = 0; i < RUN_ITER_COUNT; i++) {
//         for (apr_size_t j = 0; j < item_count; j++) {
//             item_manager->get_item_by_index(pool,
//                                                 indexes_sort_by_view[j],
//                                                 ids_sort_by_view[j],
//                                                 bcomment_iter);
//             CleanPointer<BBSCommentIterator> clean_ptr(bcomment_iter);
//         }
//     }
//     end_time = get_usage_sec();

//     show_item("by index",
//               (end_time - start_time) * 1000/RUN_ITER_COUNT/item_count,
//               " msec");
// }

// static void run_read_by_id(apr_pool_t *pool, UploadItemManager *item_manager,
//                            apr_size_t item_count,
//                            apr_size_t *ids_sort_by_view)
// {
//     volatile double start_time;
//     volatile double end_time;
//     BBSCommentIterator *bcomment_iter;
//     UploadItem::bbs_comment_p_t bcomment_p;
//     apr_size_t total_message_size;

//     APR_PALLOC(bcomment_iter, BBSCommentIterator *, pool,
//                BBSCommentIterator::MEMORY_SIZE);

//     total_message_size = 0;

//     start_time = get_usage_sec();
//     for (apr_size_t i = 0; i < RUN_ITER_COUNT; i++) {
//         for (apr_size_t j = 0; j < item_count; j++) {
//             item_manager->get_item_by_id(pool, ids_sort_by_view[j],
//                                              bcomment_iter);

//             bcomment_iter->get_comment(&bcomment_p);
//             total_message_size += strlen(bcomment_p.message);

//             CleanPointer<BBSCommentIterator> clean_ptr(bcomment_iter);
//         }
//     }
//     end_time = get_usage_sec();

//     show_item("by id",
//               (end_time - start_time) * 1000/RUN_ITER_COUNT/item_count,
//               " msec");
// }

// static void run_all_read(apr_pool_t *pool, UploadItemManager *item_manager)
// {
//     apr_uint16_t item_count;
//     apr_size_t *ids_sort_by_view;
//     apr_uint16_t *indexes_sort_by_view;

//     show_test_name("read");

//     item_count = TARGET_ITEM_COUNT;
//     item_manager->get_item_info_sort_by_view(pool, &item_count,
//                                                  &ids_sort_by_view,
//                                                  &indexes_sort_by_view);

//     run_read_by_index(pool, item_manager, item_count,
//                       ids_sort_by_view, indexes_sort_by_view);
//     run_read_by_id(pool, item_manager, item_count, ids_sort_by_view);
// }

void run_all(apr_pool_t *pool, int argc, const char * const *argv)
{
    const char *data_dir_path;
    apr_shm_t *manager_shm;
//     apr_shm_t *list_shm;

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

    data_dir_path = argv[1];

    if (!is_exist(pool, data_dir_path)) {
        THROW(MESSAGE_DATA_DIR_NOT_FOUND);
    }

    manager_shm = create_shm(pool, UploadItemManager::get_memory_size());
//     list_shm = create_shm(pool, UploadItemList::get_memory_size());


//     UploadItemManager *item_manager
//         = UploadItemManager::get_instance(manager_shm, pool, data_dir_path);
//     UploadItemList *item_list = UploadItemListReader::read(pool, data_dir_path,
//                                                            list_shm);
//     item_manager->init(item_list);

//     show_item("data_dir", data_dir_path);
//     show_item("manager memory",
//               UploadItemManager::get_memory_size()/1024/static_cast<double>(1024),
//               " MB");
//     show_item("on memory thread", item_manager->get_item_count(), "");
//     show_line();

//     run_all_read(pool, item_manager);

//     // チェック
//     UploadItemManager::check_finalize_state(item_manager);
}

#endif

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