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

//=============================================================================
/**
 *  @file    JGSpoolManager.cpp
 *
 *  @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 "JGSpoolManager.h"
#include "JGEquipment.h"
#include "JGInfoManager.h"
#include "JGCommManager.h"
#include "JGTimeModule.h"
#include "JGTaskTrigger.h"
#include "JGFile.h"
#include "BS2ACKMessage.h"
#include "BS2ErrorMessage.h"
#include "BS2MessageDictionary.h"
#include "BS2ListItem.h"
#include "BS2DeclAtoms.h"
#include "BS2Driver.h"
#include "BS2Stream.h"
#include "ace/Dirent.h"

/*
 *  Spooling State Task
 */
MANAGER_STATE(JGSpoolState, JGSpoolManager, CATEGORY_SPOOL)
    CONDITION_STATE(CondSpool);  // Conndition state node
    EDGE_STATE(PowerOff);
    EDGE_STATE(Inactive);
    EDGE_STATE(NoOutput);
    EDGE_STATE(Purge);
    EDGE_STATE(Transmit);
    EDGE_STATE(NotFull);
    EDGE_STATE(Full);

    // Define transit functions
    const BCHAR * toOutput(JGTrigger * trigger);

END_ROOT_STATE();

//------------------------------------------------------------------------------
// State
//------------------------------------------------------------------------------
#define STATE_SPOOL       _TX("/SPOOL")
#define STATE_POWER_OFF   _TX("/SPOOL/POWER_OFF")
#define STATE_POWER_ON    _TX("/SPOOL/POWER_ON")
#define STATE_INACTIVE    _TX("/SPOOL/POWER_ON/INACTIVE")
#define STATE_ACTIVE      _TX("/SPOOL/POWER_ON/ACTIVE")
#define STATE_LOAD        _TX("/SPOOL/POWER_ON/ACTIVE/LOAD")
#define STATE_NOT_FULL    _TX("/SPOOL/POWER_ON/ACTIVE/LOAD/NOT_FULL")
#define STATE_FULL        _TX("/SPOOL/POWER_ON/ACTIVE/LOAD/FULL")
#define STATE_UNLOAD      _TX("/SPOOL/POWER_ON/ACTIVE/UNLOAD")
#define STATE_OUTPUT      _TX("/SPOOL/POWER_ON/ACTIVE/UNLOAD/OUTPUT")
#define STATE_TRANSMIT    _TX("/SPOOL/POWER_ON/ACTIVE/UNLOAD/OUTPUT/TRANSMIT")
#define STATE_PURGE       _TX("/SPOOL/POWER_ON/ACTIVE/UNLOAD/OUTPUT/PURGE")
#define STATE_NO_OUTPUT   _TX("/SPOOL/POWER_ON/ACTIVE/UNLOAD/NO_OUTPUT")

static FixState   _st_spool(STATE_SPOOL,       STATE_POWER_ON);
static HistState  _st_power_on(STATE_POWER_ON, STATE_INACTIVE);
static AndState   _st_active(STATE_ACTIVE);
static FixState   _st_load(STATE_LOAD,         STATE_NOT_FULL);
static FixState   _st_unload(STATE_UNLOAD,     STATE_NO_OUTPUT);
static JGSpoolState::CondSpool _st_output(STATE_OUTPUT);
static JGSpoolState::PowerOff  _st_power_off(STATE_POWER_OFF);
static JGSpoolState::Inactive  _st_inactive(STATE_INACTIVE);
static JGSpoolState::Purge     _st_purge(STATE_PURGE);
static JGSpoolState::Transmit  _st_transmit(STATE_TRANSMIT);
static JGSpoolState::NoOutput  _st_no_output(STATE_NO_OUTPUT);
static JGSpoolState::NotFull   _st_not_full(STATE_NOT_FULL);
static JGSpoolState::Full      _st_full(STATE_FULL);

//-----------------------------------------------------------------------------
//
// Declare Transition Data of Spool Sate Task
//
//-----------------------------------------------------------------------------
DEF_ACTION(JGSpoolState, toOutput);

//------------------------------------------------------------------------------
BEGIN_STATIC_STATE_TABLE(JGSpoolState);
    NEXT_TRANSITION(&JGStateTask::Root, TRIGGER_START, STATE_SPOOL, _TX("NULL"));
    NODE_TRANSITION(&_st_spool,    _TX("to SPOOL"));
    NODE_TRANSITION(&_st_power_on, _TX("to POWER ON"));
    NODE_TRANSITION(&_st_active,   _TX("to ACTIVE"));
    NODE_TRANSITION(&_st_unload,   _TX("to UNLOAD"));
    NODE_TRANSITION(&_st_load,     _TX("to LOAD"));
    NODE_TRANSITION(&_st_output,   _TX("to OUTPUT"));
    NEXT_TRANSITION(&_st_inactive,
                    TRIGGER_NOT_COMMUNICATING,
                    STATE_ACTIVE,
                    _TX("SPOOL INACTIVE to SPOOL ACTIVE"));
    NEXT_TRANSITION(&_st_inactive,
                    TRIGGER_WAIT_DELAY,
                    STATE_ACTIVE,
                    _TX("SPOOL INACTIVE to SPOOL ACTIVE"));
    NEXT_TRANSITION(&_st_not_full,
                    TRIGGER_FULL,
                    STATE_FULL,
                    _TX("SPOOL NOT FULL to SPOOL FULL"));
    NEXT_TRANSITION(&_st_output,
                    TRIGGER_EMPTY,
                    STATE_INACTIVE,
                    _TX("NO SPOOL OUTPUT to SPOOL INACTIVE"));
    NEXT_TRANSITION(&_st_no_output,
                    TRIGGER_PURGE,
                    STATE_PURGE,
                    _TX("NO SPOOL OUTPUT to PURGE"));
    ACT_TRANSITION(&_st_no_output,
                    TRIGGER_S6F23,
                    toOutput,
                    _TX("NO SPOOL OUTPUT to PURGE/TRANSMIT SPOOL"));
    NEXT_TRANSITION(&_st_transmit,
                    TRIGGER_MAX_TRANSMIT,
                    STATE_NO_OUTPUT,
                    _TX("TRANSMIT SPOOL to NO SPOOL OUTPUT"));
    NEXT_TRANSITION(&_st_power_on,
                    TRIGGER_POWER_OFF,
                    STATE_POWER_OFF,
                    _TX("POWER ON to POWER OFF"));
    NEXT_TRANSITION(&_st_power_off,
                    TRIGGER_POWER_ON,
                    STATE_SPOOL,
                    _TX("POWER OFF to POWER ON"));
    ACT_TRANSITION(&_st_inactive,
                    TRIGGER_S6F23,
                    toOutput,
                    _TX("SPOOL INACTIVE"));
END_STATE_TABLE();

//-----------------------------------------------------------------------------
// Action: Power off
//-----------------------------------------------------------------------------
int JGSpoolState::actPowerOff(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolState::actPowerOff");
    ACE_UNUSED_ARG(prev);
    ACE_UNUSED_ARG(trigger);

    // Save spool data & status

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Action: Spool inactive
//-----------------------------------------------------------------------------
int JGSpoolState::actInactive(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolState::actInactive");
    ACE_UNUSED_ARG(trigger);

    if (prev->name() == b_context::Root.name() || prev->name() == STATE_SPOOL)
    {   // Not send event when power on
        m_manager->initStreams();
        return BEE_SUCCESS;
    }
    // Send event
    m_manager->sendEvent(EVT_SPOOL_DEACTIVATED);

    // Spool end --> CONSOLE

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Action: Purge spool
//-----------------------------------------------------------------------------
int JGSpoolState::actPurge(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolState::actPurge");
    ACE_UNUSED_ARG(prev);
    ACE_UNUSED_ARG(trigger);

    m_manager->m_spooldb.purge();

    m_manager->m_actual->setInt(0);
    m_manager->m_startv = ACE_Time_Value::zero;

    m_manager->notify(TRIGGER_EMPTY);

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Action: Transmit spool
//-----------------------------------------------------------------------------
int JGSpoolState::actTransmit(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolState::actTransmit");
    ACE_UNUSED_ARG(prev);
    ACE_UNUSED_ARG(trigger);

    BS2Message * msg;
    u_int tranum = m_manager->m_maxtransmit->getv().getUInt();

    if (tranum == 0)
    {   // Send all spooled message
        msg = m_manager->output();             // Spool strage --> message
        while (msg != NULL)
        {
            m_manager->send(msg);
            ACE_Time_Value tv(0, 800*1000);   // 800 msec
            ACE_OS::sleep(tv);
            msg = m_manager->output();
        }
        m_manager->notify(TRIGGER_EMPTY);
    }
    else
    {
        int is_end = false;
        for (u_int i = 0; i < tranum; i++)
        {
            msg = m_manager->output();             // Spool strage --> message
            if (msg == NULL)
            {   // empty spool
                is_end = true;
            }
            m_manager->send(msg);
            ACE_Time_Value tv(0, 800*1000);   // 800 msec
            ACE_OS::sleep(tv);
        }
        if (is_end)
        {
            m_manager->notify(TRIGGER_EMPTY);
        }
        else
        {   // Pause transmit
            m_manager->notify(TRIGGER_MAX_TRANSMIT);
        }
    }
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Action: No spool output
//-----------------------------------------------------------------------------
int JGSpoolState::actNoOutput(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolState::actNoOutput");
    ACE_UNUSED_ARG(prev);
    ACE_UNUSED_ARG(trigger);

    if (trigger->name() == TRIGGER_COMM_FAILURE)
    {
        // Failure spool

        // Send event
        m_manager->sendEvent(EVT_SPOOL_FAILURE);

        return BEE_SUCCESS;
    }

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Action: Spool not full
//-----------------------------------------------------------------------------
int JGSpoolState::actNotFull(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolState::actNotFull");
    ACE_UNUSED_ARG(prev);
    ACE_UNUSED_ARG(trigger);

    if (m_manager->m_enable->getv().getBool() == false)
    {   // Not EnableSpooling
        return BEE_SUCCESS;             // ignore
    }

    // Set spool start time
    string startime;
    JGTimeModule::instance()->getDateAndTime(startime);
    JGvalue timrval(startime);
    m_manager->m_startime->setv(timrval);

    // initialize counter
    m_manager->m_actual->setInt(0);   // not zero is that spool active
    m_manager->m_total->setInt(0);    // the total number of primary message

    // Send event
    m_manager->sendEvent(EVT_SPOOL_ACTIVATED);

    // Spool start --> CONSOLE

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Action: Spool full
//-----------------------------------------------------------------------------
int JGSpoolState::actFull(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolState::actFull");
    ACE_UNUSED_ARG(prev);
    ACE_UNUSED_ARG(trigger);

    // Set Spool full time
    string fulltime;
    JGTimeModule::instance()->getDateAndTime(fulltime);
    JGvalue timrval(fulltime);
    m_manager->m_fulltime->setv(timrval);

    // Spool full --> CONSOLE

    return BEE_SUCCESS;
}


//------------------------------------------------------------------------------
// Condition node actions
//------------------------------------------------------------------------------
const char * JGSpoolState::actCondSpool(JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolState::actCondSpool");
    ACE_UNUSED_ARG(trigger);

    const char * nextState = NULL;
    TRACE_ERROR((_TX("Illegal procedure call\n")));
    return nextState;
}

//-----------------------------------------------------------------------------
// Unexpected event
//-----------------------------------------------------------------------------
int JGSpoolState::unexpect(b_trigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolState::unexpect");

    if (trigger->name() == TRIGGER_S6F23)
    {
        const BS2Message * primsg = ((JGMessageTrigger *)trigger)->message();
        if (this->current() == STATE_INACTIVE)
        {
            m_manager->sendS6F24(primsg, 2);   // spooled data does not exist
        }
        else
        {
            m_manager->sendS6F24(primsg, 1);   // busy, retry later
        }
    }
    else
    {
        this->b_context::unexpect(trigger);
    }

    return BEE_ERROR;
}

//-----------------------------------------------------------------------------
// ACTION : output (Trigger is S6F23)
//-----------------------------------------------------------------------------
const BCHAR * JGSpoolState::toOutput(JGTrigger * trig)
{
    TRACE_FUNCTION(TRL_MIDDLE, "JGSpoolState::toOutput");
    const BCHAR * nextState;
    BS2Message * msg = ((JGMessageTrigger *)trig)->message();
    BS2Atom * atom = msg->getAtom(_TX("RSDC"));
    BS2Assert(atom != NULL);
    JGvalue rsdc(*atom);
    bool transfer = (rsdc.getInt() == 0) ? true : false;
    bool empty = m_manager->isEmpty();
    int  ack = (transfer && empty) ? 2 : 0;

    m_manager->sendS6F24(msg, ack);

    if (transfer && (! empty))
    {   // transmit spooled messages
        nextState = STATE_TRANSMIT;
    }
    else
    {   // purge spooled messages
        nextState = STATE_PURGE;
    }

    return nextState;
}

////////////////////////////////////////////////////////////////////////////////
//
// S P O O L   M A N A G E R
//
//

static int _MaxFunctions[MAX_STREAM] = {0};

//-----------------------------------------------------------------------------
// Return own.
//-----------------------------------------------------------------------------
static JGSpoolManager * _manager = NULL;

JGSpoolManager * JGSpoolManager::instance()
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::instance");
    if (_manager == NULL)
    {
        _manager = new JGSpoolManager;
    }
    return _manager;
}

//-----------------------------------------------------------------------------
void JGSpoolManager::clear()
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::clear");

    string dirName = JGEquipment::instance()->rootDirectory();
    dirName += DIR_SEPARATOR_STRING;
    dirName += _TX("spool");
    ACE_Dirent dir(dirName.c_str());
    dirent * directory;
    while ((directory = dir.read ()) != NULL)
    {
        string fname = directory->d_name;
        if (fname == _TX(".") || fname == _TX(".."))
        {
            continue;
        }
        JGFile * file = new JGFile(dirName, directory->d_name, _TX(""));
        int result = file->open();
        if (result < 0)
        {
            TRACE_ERROR((_TX("It can't open file(%s).\n"), directory->d_name));
        }
        else
        {
            file->remove();
        }
        delete file;
    }

    JGSpoolManager::instance()->m_spooldb.fini();  // Clear mmap data

    JGSpoolManager::instance()->initSpool();
}

//-----------------------------------------------------------------------------
// Constructor/Destoructor
//-----------------------------------------------------------------------------
JGSpoolManager::JGSpoolManager() : JGManager(CATEGORY_SPOOL),
                                   m_sfbits(MAX_STREAM * 0x100)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::JGSpoolManager");
}

//-----------------------------------------------------------------------------
// Initialize variables for spool.
//-----------------------------------------------------------------------------
int JGSpoolManager::init(void * parm)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::init");
    ACE_UNUSED_ARG(parm);

    m_spoolState = _JGSpoolState::instance();
    m_spoolState->manager(this);
    m_spoolState->equipment(m_equipment);
    m_spoolState->init();         // initialized transitions
    // m_spoolSatae->open();         // start thread

    this->initSpool();

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Initial spool area.
//-----------------------------------------------------------------------------
int JGSpoolManager::initSpool()
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::initSpool");

    // Load spooling information
    m_enable = m_equipment->variable(VAR_ENABLE_SPOOL);
    m_total = m_equipment->variable(VAR_SPOOL_COUNT_TOTAL);
    m_actual = m_equipment->variable(VAR_SPOOL_COUNT_ACTUAL);
    m_overwrite = m_equipment->variable(VAR_OVERWRITE_SPOOL);
    m_maxtransmit = m_equipment->variable(VAR_MAX_SPOOL_TRANSMIT);
    m_startime = m_equipment->variable(VAR_SPOOL_START_TIME);
    m_fulltime = m_equipment->variable(VAR_SPOOL_FULL_TIME);

    BS2value tmval(m_startime->getv());
    m_startv = tmval.getTimeValue();
    tmval = m_fulltime->getv();
    m_fulltv = tmval.getTimeValue();

    m_dir = m_equipment->rootDirectory();
    m_dir += DIR_SEPARATOR_STRING;
    m_dir += _TX("spool");

    // OBSOLETE: m_fname += m_equipment->getConf(CONF_SPOOL_FILENAME);
    //           file name is "gem.spool"
    const string& maxspool = m_equipment->getConf(CONF_MAX_SPOOL_COUNT);
    m_maxcount = _tcstol(maxspool.c_str(), NULL, 10);

    bool   ow = m_overwrite->getv().getBool();
    m_spooldb.init(m_equipment, m_dir, m_maxcount, ow);

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Initial stream-function of spooling.
//-----------------------------------------------------------------------------
void JGSpoolManager::initStreams()
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::initStreams");

    StreamFunctionNumbers sfnums;

    memset(_MaxFunctions, 0, sizeof(_MaxFunctions));
    m_sfbits.clear();
    BS2MessageDictionary::instance()->getSpoolingStreams(sfnums);

    for (size_t i = 0; i < sfnums.size(); i++)
    {
        int sf = sfnums[i];
        int stmNum = STREAMCODE(sf);
        int funcNum = FUNCCODE(sf);

        // Set max function number in each stream
        if (_MaxFunctions[stmNum] < funcNum)
        {
            _MaxFunctions[stmNum] = funcNum;
        }

        if (stmNum == 1 || stmNum == 9 || (funcNum & 1) == 0)
        {
            continue;
        }
        m_sfbits.set(sf);
    }
}

//-----------------------------------------------------------------------------
// Check spool active
//-----------------------------------------------------------------------------
bool JGSpoolManager::isActive()
{
    TRACE_FUNCTION(TRL_MIDDLE, "JGSpoolManager::isActive");
    bool result = false;

    JGState * cur = m_spoolState->currentState();
    if (cur->name() == STATE_NOT_FULL || cur->name() == STATE_FULL ||
        cur->name() == STATE_OUTPUT   || cur->name() == STATE_NO_OUTPUT ||
        cur->name() == STATE_PURGE    || cur->name() == STATE_TRANSMIT)
    {
        result = this->isEnabled();
    }
    return result;
}

//-----------------------------------------------------------------------------
// Check exist spool data
//-----------------------------------------------------------------------------
bool JGSpoolManager::isEmpty()
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::isEmpty");
    int count = m_actual->getv().getInt();
    return (count == 0);
}

//-----------------------------------------------------------------------------
// Checking spool or not
//-----------------------------------------------------------------------------
bool JGSpoolManager::isEnabled()
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::isEnabled");
    bool result = m_enable->getv().getBool();
    return result;
}

//-----------------------------------------------------------------------------
// Checking spool or not
//-----------------------------------------------------------------------------
void JGSpoolManager::setEnabled(bool tf)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::setEnabled");
    JGvalue enabled(tf);
    m_enable->setv(enabled);
}

//-----------------------------------------------------------------------------
// Get spooled count
//-----------------------------------------------------------------------------
size_t JGSpoolManager::actualCount()
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::actualCount");
    size_t count = m_actual->getv().getUInt();
    return count;
}

//-----------------------------------------------------------------------------
size_t JGSpoolManager::totalCount()
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::totalCount");
    size_t count = m_total->getv().getUInt();
    return count;
}


//-----------------------------------------------------------------------------
// Input message to spool area (Caller must be delete message)
//-----------------------------------------------------------------------------
int JGSpoolManager::input(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_HIGH, "JGSpoolManager::input");

    ACE_MT(ACE_GUARD_RETURN(ACE_Thread_Mutex, _input, this->m_lock, 0));
    int result = BEE_SUCCESS;

    if (m_enable->getv().getBool() == false)
    {
        return result;          // Disable spool
    }

    if (! IS_SPOOL_NUMBER(msg->sf()))
    {   // not spooling message
        return result;
    }

    if (! m_sfbits.get(msg->sf()))
    {
        return result;
    }

    m_total->inc();             // primary message count up
    if (isFull())
    {
        return result;
    }

    // Convert message to buffer (serialize)
    BS2OStream * buff = new BS2OStream;
    if (buff->set(msg) == true)
    {
        m_spooldb.put((BYTE *)buff->ptop(), buff->pcount());  // Write to strage
    }
    delete buff;                        // *

    // actual count up
    m_actual->setInt(m_spooldb.cursize());

    return result;
}

//-----------------------------------------------------------------------------
// Output message from spool area
//-----------------------------------------------------------------------------
BS2Message * JGSpoolManager::output()
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::output");

    ACE_MT(ACE_GUARD_RETURN(ACE_Thread_Mutex, _output, this->m_lock, NULL));

    if (m_spooldb.cursize() <= 0)
    {
        return NULL;                    // Spooling data is nothing
    }

    BS2OStream * buff = new BS2OStream;
    BYTE * retbuf;
    size_t datasize;
    int result = m_spooldb.get(retbuf, datasize);  // Read from strage
    while (result < 0 && m_spooldb.cursize() > 0)
    {
        // As name in spool control that does not found, get next
        m_spooldb.decsize();
        result = m_spooldb.get(retbuf, datasize);
    }
    if (result < 0)
    {
        delete buff;
        return NULL;
    }
    // actual count down
    m_actual->setInt(m_spooldb.cursize());

    // copy !
    buff->write((const char *)retbuf, datasize);
    delete[] retbuf;

    BS2Message * msg = BS2MessageDictionary::instance()->make(buff);
    if (msg == NULL)
    {
        TRACE_DEBUG((_TX("Spooled message deserialize error.\n")));
        delete buff;
        return NULL;                    // Error illegal message
    }
    if (msg->noise())
    {
        TRACE_DEBUG((_TX("Spooled message deserialize error.\n")));
        delete buff;
        delete msg;
        return NULL;                    // Error illegal message
    }

    msg->wait(false);    // Wait bit off (No reply)
    msg->spooled(true);  // Set spooled message flag

    delete buff;
    return msg;
}

//-----------------------------------------------------------------------------
// Entry spooling stream-function number
//-----------------------------------------------------------------------------
int JGSpoolManager::entry(int sfnum)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::entry");

    int streamNum = STREAMCODE(sfnum);
    if (streamNum == 1 || streamNum == 9)
    {   // Ignore streams
        return BEE_ERROR;
    }

    int maxFuncNum = _MaxFunctions[streamNum];

    if (FUNCCODE(sfnum) == 0)
    {   // all functions for the associated stream
        for (int funcNum = 1; funcNum < maxFuncNum; funcNum++)
        {
            if ((funcNum & 1) == 0)
            {
                continue;
            }
            m_sfbits.set(SFCODE(streamNum, funcNum));
        }
    }
    else
    {
        if ((sfnum & 1) == 0 || maxFuncNum <= FUNCCODE(sfnum))
        {   // Secondary message
            return BEE_ERROR;
        }
        m_sfbits.set(sfnum);
    }
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Release spooling stream-function number
//-----------------------------------------------------------------------------
int JGSpoolManager::release(int sfnum)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::release");

    if (STREAMCODE(sfnum) == 1 || STREAMCODE(sfnum) == 9)
    {   // Ignore streams
        return BEE_ERROR;
    }

    if (FUNCCODE(sfnum) == 0)
    {   // all functions for the associated stream
        for (int funcNum = 1; funcNum < 256; funcNum++)
        {
            m_sfbits.reset(SFCODE(sfnum, funcNum));
        }
    }
    else
    {
        if ((sfnum & 1) == 0)
        {   // Secondary message
            return BEE_ERROR;
        }
        m_sfbits.reset(sfnum);
    }
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Get spooling status stream-function number
//-----------------------------------------------------------------------------
bool JGSpoolManager::status(int sfnum)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::status");

    if (STREAMCODE(sfnum) == 1 || STREAMCODE(sfnum) == 9)
    {   // Ignore streams
        return false;
    }

    if (FUNCCODE(sfnum) == 0)
    {   // all functions for the associated stream
        return false;
    }
    return m_sfbits.get(sfnum);
}

//-----------------------------------------------------------------------------
// Enable/Disable Stream-Function (S2F43 -> S2F44)
//-----------------------------------------------------------------------------
class ResetSpoolingParam : public BS2Traverser
{
public:
    ResetSpoolingParam(BS2ListItem * nacklist)
            : BS2Traverser(), m_lastStream(0), m_lastFuncq(0), m_rspack(0),
              m_strack(0), m_nackList(nacklist), m_fcnList(NULL) {}
    virtual ~ResetSpoolingParam() {}

    int  m_lastStream;
    int  m_lastFuncq;
    BYTE m_rspack;
    BYTE m_strack;
    vector<int> m_sfnums;
    BS2ListItem * m_nackList;
    BS2ListItem * m_fcnList;

    // Make strid nack
    void make_strack(BYTE strack, int streamNum)
    {
        BS2ListItem * strlist = new BS2ListItem;
        BS2Item * item;

        m_fcnList = new BS2ListItem;
        m_rspack = 1;        // Set error code
        m_strack = strack;   // Save

        item = BS2Item::factory(_TX("STRID"), new BS2UInt1((BYTE)streamNum));
        strlist->add(item);
        item = BS2Item::factory(_TX("STRACK"), new BS2Binary(strack));
        strlist->add(item);
        strlist->add(m_fcnList);
        m_nackList->add(strlist);
    }

    // Make fcnid nack
    void make_fcnack(int fcnid)
    {
        m_rspack = 1;   // Set error code
        if (m_strack == 0)
        {   // New error list
            make_strack(((fcnid & 1) == 0) ? 4 : 3, m_lastStream);
        }

        BS2Item * item = BS2Item::factory(_TX("FCNID"), new BS2UInt1((BYTE)fcnid));
        m_fcnList->add(item);
    }

    // Parse Spooling Streams and Functions
    virtual int parseItem(BS2Item * item)
    {
        TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::ResetSpoolingParam::parseItem");
        BS2Atom * atom = item->atom();
        if (item->name() == _TX("STRID"))
        {
            if (m_lastStream > 0)
            {
                if (m_lastFuncq == 0)
                {
                    m_sfnums.push_back(SFCODE(m_lastStream, 0));
                }
            }

            if (! atom->isUInt1())
            {
                make_strack(2, atom->getInt());
                TRACE_ERROR((_TX("STRID is illegal format(%d).\n"), atom->format()));
                return -1;
            }
            int strid = ((BS2UInt1 *)atom)->value();
            if (strid == 1)
            {
                make_strack(1, strid);
                TRACE_ERROR((_TX("STRID is 1.\n")));
                return 0;
            }
            if (_MaxFunctions[strid] == 0)
            {
                make_strack(2, strid);
                TRACE_ERROR((_TX("Undefined stream(%d).\n"), strid));
                return 0;
            }
            m_lastStream = strid;
            m_lastFuncq = 0;
            m_strack = 0;         // Clear error info.
            m_fcnList = NULL;
        }
        else if (item->name() == _TX("FCNID"))
        {
            if (! atom->isUInt1())
            {
                make_fcnack(atom->getInt());
                TRACE_DEBUG((_TX("FCNID is illegal format.\n"), atom->format()));
                return 0;
            }

            int fcnid = ((BS2UInt1 *)atom)->value();
            if ((fcnid & 1) == 0 || _MaxFunctions[m_lastStream] < fcnid)
            {   //  Function is secondary
                make_fcnack(fcnid);
                TRACE_ERROR((_TX("Illegal FUNCID (S%dF%d).\n"), m_lastStream, fcnid));
                return 0;
            }
            if (m_strack != 0)
            {   // As strid has been error, ignore fcnid
                return 0;
            }
            m_sfnums.push_back(SFCODE(m_lastStream, fcnid));
            m_lastFuncq++;               // function count up
        }
        return 0;
    }
};


//-----------------------------------------------------------------------------
BS2Message * JGSpoolManager::resetSpooling(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::resetSpooling");

    BYTE ack = ACK_OK;
    int  result;
    BS2Message  * replymsg = BS2Message::response(msg);
    BS2ListItem * rootlist = new BS2ListItem;
    replymsg->add(rootlist);
    BS2ListItem * nacklist = new BS2ListItem;

    ResetSpoolingParam rsp(nacklist);
    result = msg->traverse(&rsp);    // parse S2F43
    if (result < 0 || rsp.m_rspack != 0)
    {
        rootlist->add(BS2Item::factory(_TX("RSPACK"), new BS2Binary(rsp.m_rspack)));
        rootlist->add(nacklist);  // STRID + STRACK + { FUNCID }*
        return replymsg;
    }

    if (rsp.m_lastStream != 0 && rsp.m_lastFuncq == 0)
    {   // Set the last STRID
        rsp.m_sfnums.push_back(SFCODE(rsp.m_lastStream, 0));
    }

    // Update spooling data
    if (rsp.m_sfnums.size() == 0)
    {   // all stream functions are disable spooling
        this->setEnabled(false);
    }
    else
    {
        this->setEnabled(true);
        m_sfbits.clear();
        for (size_t i = 0; i < rsp.m_sfnums.size(); i++)
        {
            this->entry(rsp.m_sfnums[i]);
        }
    }
    rootlist->add(BS2Item::factory(_TX("RSPACK"), new BS2Binary(ack)));
    rootlist->add(nacklist);
    return replymsg;
}


//-----------------------------------------------------------------------------
// MESSAGE: Request Spooled Data Acknowledgement Send
//-----------------------------------------------------------------------------
int JGSpoolManager::sendS6F24(const BS2Message * primsg, int ack)
{
    TRACE_FUNCTION(TRL_LOW, "JGSpoolManager::sendS6F24");
    BYTE rsda = (BYTE)(ack & 0xFF);
    BS2Message * msg = BS2Message::response(primsg, rsda, _TX("RSDA"));
    return this->send(msg);
}

//-----------------------------------------------------------------------------
//
// Thread of received trigger event.
//
//-----------------------------------------------------------------------------
BS2Message * JGSpoolManager::msg_svc(JGMessageTrigger * trigger,
                                     BS2Message * msg)
{
    BS2Message * replymsg = NULL;

    if (msg->sf() == SFCODE(6,23))
    {
        if (this->isEmpty())
        {
             this->sendS6F24(msg, 2);     // spooled data does not exist
        }
        else
        {
            m_spoolState->accept(trigger);       // dispatch
        }
    }
    else if (msg->sf() == SFCODE(2,43))
    {
        replymsg = this->resetSpooling(msg);
    }
    else
    {
        if (m_equipment->isCommunicating())
        {   // Unexpected message
            replymsg = this->unrecognized(msg);
        }
        else
        {
            ACE_DEBUG((LM_DEBUG,
                  ACE_TEXT("Spool manager: spooling message (%s).\n"),
                  msg->charName()));
            if (msg->isPrimary())
            {   // request to spool message
                this->input(msg);
            }
        }
    }

    return replymsg;
}


//-----------------------------------------------------------------------------
// Dump
//-----------------------------------------------------------------------------
void JGSpoolManager::dump() const
{
    ACE_DEBUG((LM_DEBUG, ACE_TEXT("===== SPOOLED MESSAGES =====\n")));
    string enableStr = m_enable->getv().toString();
    string actualStr = m_actual->getv().toString();
    string totalStr =  m_total->getv().toString();
    string overwriteStr = m_overwrite->getv().toString();
    string maxtransmitStr = m_maxtransmit->getv().toString();

    ACE_DEBUG((LM_DEBUG,
        ACE_TEXT("enable = %s, actual = %s, total = %s, ow = %s, transmit = %s\n"),
                  enableStr.c_str(), actualStr.c_str(), totalStr.c_str(),
                  overwriteStr.c_str(), maxtransmitStr.c_str()));
    for (int stnum = 0; stnum < (MAX_STREAM * 0x100); stnum++)
    {
        bool enable = const_cast<JGBitVector&>(m_sfbits).get(stnum);
        if (enable)
        {
            ACE_DEBUG((LM_DEBUG, ACE_TEXT("S%dF%d, "), STREAMCODE(stnum),
                                                       FUNCCODE(stnum)));
        }
    }
    ACE_DEBUG((LM_DEBUG, ACE_TEXT("\n")));

    m_spooldb.dump();
}


