// $Id: JGSpoolDB.cpp,v 1.4 2003/03/16 14:51:01 fukasawa Exp $

//=============================================================================
/**
 *  @file    JGSpoolDB.h
 *
 *  @author  Fukasawa Mitsuo
 *
 *
 *    Copyright (C) 2001-2003 BEE Co.,Ltd. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
//=============================================================================

#define BEE_BUILD_DLL

#include "JGEquipment.h"
#include "JGVariable.h"
#include "JGSpoolDB.h"
#include "JGMemoryMappedFile.h"
#include "JGFile.h"

static char * _revision = {"JYUGEM-SPOOL-1.0.0"};

//-----------------------------------------------------------------------------
// Constructor/Destoractor
//-----------------------------------------------------------------------------
JGSpoolDB::JGSpoolDB() : JGMemoryMappedFile()
{
    m_fname = _TX("gem");
    m_ext = _TX("spool");
}

//-----------------------------------------------------------------------------
// Initial physical program area
//-----------------------------------------------------------------------------
int JGSpoolDB::init(JGEquipment * equip, const string& dir, size_t maxsize,
                    bool ow)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolDB::init");
    ACE_UNUSED_ARG(equip);

    SpoolHeader sfhead;
    bool isExist;
    m_dir = dir;
    size_t length = SPOOLED_DATA_TOP;
    length += maxsize * sizeof(SpoolBlock);

    // TRACE_DEBUG((_TX("%s %d %s\n"), dir.c_str(), maxsize,
    //                                 (ow) ? _TX("overwrite") : _TX("limit")));

    //
    // Open map file
    //
    int result = this->readHeader(sfhead);
    if (result < 0)
    {
        isExist = false;
    }
    else
    {   // Not support
        // isExist = (sfhead.m_cursize > 0) ? true : false;
        isExist = false;
    }

    if (isExist)
    {
        m_max = SPOOLED_DATA_TOP + (sfhead.m_blockCount * sfhead.m_blockSize);
        result = this->JGMemoryMappedFile::map(false);
        if (result < 0)
        {
            return -1;
        }
    }
    else
    {
        m_max = length;
        ACE_OS::strcpy(sfhead.m_revision, _revision);
        sfhead.m_blockCount = maxsize;
        sfhead.m_blockSize = sizeof(SpoolBlock);
        sfhead.m_getnum = 0;             // top position
        sfhead.m_putnum = 0;             // end position
        sfhead.m_cursize = 0;
        sfhead.m_overwrite = ow;
        result = this->JGMemoryMappedFile::map(true);
        if (result < 0)
        {
            return -1;
        }
        memmove(this->top(), &sfhead, SPOOLED_DATA_TOP);   // flush header
        this->flush();
    }

    // assigned data area
    m_top = (SpoolBlock *)(this->top() + SPOOLED_DATA_TOP);
    m_shd = (SpoolHeader *)this->top();

    //
    // Make reference table by database.
    //
    SpoolBlock * current = m_top;
    for (int i = 0; i < m_shd->m_blockCount; i++)
    {   // Clear spooled block vector
        m_blockTable.push_back(current);
        current++;
    }

    return 0;
}

//------------------------------------------------------------------------------
// Read header from mmap.
//------------------------------------------------------------------------------
int JGSpoolDB::readHeader(SpoolHeader& buf)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolDB::readHeader");

    m_max = SPOOLED_DATA_TOP;
    int result = this->JGMemoryMappedFile::map();
    if (result < 0)
    {
        return -1;
    }
    if (_tcscmp(_revision, (char *)this->top()) == 0)
    {
        memmove(&buf, this->top(), SPOOLED_DATA_TOP);
        result = 0;    // Open spool file
    }
    else
    {
        result = -1;   // New spool file
    }
    m_mmap.close();
    return result;
}

//------------------------------------------------------------------------------
// Finish
//------------------------------------------------------------------------------
void JGSpoolDB::fini()
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolDB::fini");

    BEE_GUARD_LOOP(ACE_Thread_Mutex, ace_mon, this->m_splock);

    m_mmap.sync();
    m_mmap.close();
    m_blockTable.clear();
    return ;
}

//------------------------------------------------------------------------------
// Spool data
//------------------------------------------------------------------------------
int JGSpoolDB::put(BYTE * datap, size_t size)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolDB::put");

    BEE_GUARD_LOOP(ACE_Thread_Mutex, ace_mon, this->m_splock);
    int result;
    if (m_shd->m_overwrite)
    {
        result = overwrite_put(datap, size);
    }
    else
    {
        result = sequential_put(datap, size);
    }
    if (result < 0)
    {
        return result;
    }

    this->flush();
    return 0;
}

//------------------------------------------------------------------------------
// Spool data
//------------------------------------------------------------------------------
int JGSpoolDB::overwrite_put(BYTE * datap, size_t size)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolDB::overwrite_put");

    if (m_shd->m_putnum >= m_shd->m_blockCount)
    {   // putting position back to origin
        m_shd->m_putnum = 0;
    }

    string msgName;
    SpoolBlock * curptr = m_blockTable[m_shd->m_putnum];

    // Remove the recent spooled file
    // if (! curptr->deleted())
    // {
    //     curptr->getMessageName(msgName);
    //     JGFile * oldfile = new JGFile(m_dir, msgName, _TX("msg"));
    //     int result = oldfile->open(O_TRUNC);
    //     if (result < 0)
    //     {
    //         TRACE_ERROR((_TX("Can't remove the spool message(%s)\n"), msgName.c_str()));
    //         delete oldfile;
    //         return -1;
    //     }
    //     oldfile->remove();
    //     delete oldfile;
    // }

    this->makeSpoolBlock(m_shd->m_putnum, datap, size, curptr);
    curptr->getMessageName(msgName);
    JGFile * msgfile = new JGFile(m_dir, msgName, _TX("msg"));
    int result = msgfile->open(O_TRUNC);
    if (result < 0)
    {
        TRACE_ERROR((_TX("Can't write spool message(%s)\n"), msgName.c_str()));
        delete msgfile;
        return -1;
    }
    msgfile->write(datap, size);
    msgfile->close();
    delete msgfile;

    m_shd->m_cursize++;
    m_shd->m_putnum++;

    if (m_shd->m_cursize >= m_shd->m_blockCount)
    {   // records limit over
        m_shd->m_getnum = m_shd->m_putnum;    // go ahead getting position
        if (m_shd->m_getnum >= m_shd->m_blockCount)
        {
            m_shd->m_getnum = 0;
        }
        m_shd->m_cursize = m_shd->m_blockCount;
    }
    return 0;
}

//------------------------------------------------------------------------------
// Spool data (Sequential)
//------------------------------------------------------------------------------
int JGSpoolDB::sequential_put(BYTE * datap, size_t size)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolDB::sequential_put");

    if (m_shd->m_cursize >= m_shd->m_blockCount)
    {   // Full spool area
        return -1;
    }

    // exist space records
    string msgName;
    SpoolBlock * curptr = m_blockTable[m_shd->m_putnum];

    this->makeSpoolBlock(m_shd->m_putnum, datap, size, curptr);
    curptr->getMessageName(msgName);
    JGFile * msgfile = new JGFile(m_dir, msgName, _TX("msg"));
    int result = msgfile->open(O_TRUNC);
    if (result < 0)
    {
        TRACE_ERROR((_TX("Can't write spool message(%s)\n"), msgName.c_str()));
        delete msgfile;
        return -1;
    }
    msgfile->write(datap, size);
    msgfile->close();
    delete msgfile;

    m_shd->m_cursize++;
    m_shd->m_putnum++;

    return 0;
}

//------------------------------------------------------------------------------
// Get spooled data
//------------------------------------------------------------------------------
int JGSpoolDB::get(BYTE*& datap, size_t& size)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolDB::get");

    BEE_GUARD_LOOP(ACE_Thread_Mutex, ace_mon, this->m_splock);
    if (m_shd->m_cursize == 0)
    {
        return -1;
    }

    string msgName;
    SpoolBlock * curptr = m_blockTable[m_shd->m_getnum];

    curptr->getMessageName(msgName);
    JGFile * msgfile = new JGFile(m_dir, msgName, _TX("msg"));
    int result = msgfile->open();
    if (result < 0)
    {
        TRACE_ERROR((_TX("Can't read spool message(%s)\n"), msgName.c_str()));
        delete msgfile;
        return -1;
    }
    BYTE * buf = (BYTE *)(new BYTE[curptr->m_size + 32]);
    msgfile->read(buf, curptr->m_size + 32);
    msgfile->remove();
    delete msgfile;

    datap = buf;                     // Set return value
    size = curptr->m_size;
    curptr->erase();

    // Update counter and pointers.
    m_shd->m_cursize--;
    if (m_shd->m_cursize <= 0)
    {
        m_shd->m_cursize = 0;
        m_shd->m_putnum = 0;
        m_shd->m_getnum = 0;
    }
    else
    {
        m_shd->m_getnum++;
        if (m_shd->m_getnum >= m_shd->m_blockCount)
        {
            m_shd->m_getnum = 0;
        }
    }
    if (size == 0)
    {
        return -1;
    }
    return 0;
}

//------------------------------------------------------------------------------
// Purge spooled data.
//------------------------------------------------------------------------------
int JGSpoolDB::purge()
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolDB::purge");

    BEE_GUARD_LOOP(ACE_Thread_Mutex, ace_mon, this->m_splock);
    for (size_t i = 0; i < m_blockTable.size(); i++)
    {
        SpoolBlock * curptr = m_blockTable[m_shd->m_getnum];
        if (curptr->deleted())
        {
            continue;
        }
        string msgName;
        curptr->getMessageName(msgName);
        JGFile * msgfile = new JGFile(m_dir, msgName, _TX("msg"));
        int result = msgfile->open();
        if (result < 0)
        {
            TRACE_ERROR((_TX("Can't find spool message(%s)\n"), msgName.c_str()));
        }
        else
        {
            msgfile->remove();
        }
        delete msgfile;
        curptr->erase();
    }
    m_shd->m_cursize = 0;
    m_shd->m_putnum = 0;
    m_shd->m_getnum = 0;
    this->flush();
    return 0;
}

//------------------------------------------------------------------------------
// Purge spooled data.
//------------------------------------------------------------------------------
int JGSpoolDB::makeSpoolBlock(int putnum, BYTE * datap, size_t size,
                              SpoolBlock * spool)
{
    BS2BlockHeader * msgHeader = (BS2BlockHeader *)datap;
    BCHAR buf[256];
    ACE_OS::sprintf(buf, _TX("%d.S%dF%d"), putnum, msgHeader->getStreamNum(),
                                           msgHeader->getFunctionNum());
    buf[SpoolBlock::NAME_LENGTH - 1] = '\0';
    ACE_OS::strcpy(spool->m_fname, buf);

    JGLogTime now;
    spool->m_time = now;
    spool->m_size = size;
    spool->m_mhead = *msgHeader;
    return 0;
}

//------------------------------------------------------------------------------
// Print out the number of records in the database.
//------------------------------------------------------------------------------
void JGSpoolDB::dump() const
{
    ACE_DEBUG((LM_DEBUG, ACE_TEXT("* SPOOLED DATABASE *\n")));
    ACE_DEBUG((LM_DEBUG, ACE_TEXT("max = %d, current = %d, put = %d, get = %d\n"),
                         m_shd->m_blockCount, m_shd->m_cursize,
                         m_shd->m_putnum, m_shd->m_getnum));

    for (size_t i = 0; i < m_blockTable.size(); i++)
    {
        SpoolBlock * curptr = m_blockTable[i];
        if (curptr->deleted())
        {
            continue;
        }
        ACE_DEBUG((LM_DEBUG, ACE_TEXT("[%s] %s %d\n"),
                             curptr->m_time.toString().c_str(),
                             curptr->m_fname, curptr->m_size));
        curptr->m_mhead.dump();
    }
}
