// $Id: JGCommManager.cpp,v 1.10 2003/03/16 14:51:00 fukasawa Exp $

//=============================================================================
/**
 *  @file    JGCommManager.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 "JGCommManager.h"
#include "JGStateTask.h"
#include "JGEquipment.h"
#include "JGInfoManager.h"
#include "JGEventManager.h"
#include "JGControlManager.h"
#include "JGSpoolManager.h"
#include "JGTaskTrigger.h"
#include "JGCommDevice.h"
#include "BS2ACKMessage.h"
#include "BS2ErrorMessage.h"
#include "BS2ListItem.h"
#include "BS2DeclAtoms.h"
#include "BS2Driver.h"

/*
 * Communication Manager's State Class
 */
MANAGER_STATE(JGCommState, JGCommManager, CATEGORY_COMM)
    CONDITION_STATE(CondComm);
    EDGE_STATE(WaitCrFromHost);
    EDGE_STATE(WaitDelay);
    EDGE_STATE(WaitCra);
    EDGE_STATE(Communicating);
    EDGE_STATE(Disabled);

    // Define transit and state functions
    const BCHAR * toCommunicatingByEquipment(JGTrigger * trigger);
    const BCHAR * toCommunicatingByHost(JGTrigger * trigger);
    int   updateState(int state, int selector);
    int   startTimer();
    int   cancelTimer();

    //
    long m_tmid;

END_ROOT_STATE();

//------------------------------------------------------------------------------
// State
//------------------------------------------------------------------------------
#define STATE_COMM                 _TX("/COMMUNICATIONS")
#define STATE_DISABLED             _TX("/COMMUNICATIONS/DISABLED")
#define STATE_ENABLED              _TX("/COMMUNICATIONS/ENABLED")
#define STATE_COMMUNICATING        _TX("/COMMUNICATIONS/ENABLED/COMMUNICATING")
#define STATE_NOT_COMMUNICATING    _TX("/COMMUNICATIONS/ENABLED/NOT_COMMUNICATING")
#define STATE_HOST_INITIATED       _TX("/COMMUNICATIONS/ENABLED/NOT_COMMUNICATING/HOST_INITIATED")
#define STATE_EQUIPMENT_INITIATED  _TX("/COMMUNICATIONS/ENABLED/NOT_COMMUNICATING/EQUIPMENT_INITIATED")
#define STATE_WAIT_CR_FROM_HOST    _TX("/COMMUNICATIONS/ENABLED/NOT_COMMUNICATING/HOST_INITIATED/WAIT_CR_FROM_HOST")
#define STATE_WAIT_DELAY           _TX("/COMMUNICATIONS/ENABLED/NOT_COMMUNICATING/EQUIPMENT_INITIATED/WAIT_DELAY")
#define STATE_WAIT_CRA             _TX("/COMMUNICATIONS/ENABLED/NOT_COMMUNICATING/EQUIPMENT_INITIATED/WAIT_CRA")

//------------------------------------------------------------------------------
static AndState  _st_not_communicating(STATE_NOT_COMMUNICATING);
static FixState  _st_host_initiated(STATE_HOST_INITIATED, STATE_WAIT_CR_FROM_HOST);
static FixState  _st_equipment_initiated(STATE_EQUIPMENT_INITIATED, STATE_WAIT_CRA);
static FixState  _st_enabled(STATE_ENABLED, STATE_NOT_COMMUNICATING);
static JGCommState::CondComm        _st_communications(STATE_COMM);
static JGCommState::Disabled        _st_disabled(STATE_DISABLED);
static JGCommState::WaitCrFromHost  _st_wait_cr_from_host(STATE_WAIT_CR_FROM_HOST);
static JGCommState::WaitDelay       _st_wait_delay(STATE_WAIT_DELAY);
static JGCommState::WaitCra         _st_wait_cra(STATE_WAIT_CRA);
static JGCommState::Communicating   _st_communicating(STATE_COMMUNICATING);

//-----------------------------------------------------------------------------
//
// Declare Transition Data of Communication Manager
//
//-----------------------------------------------------------------------------
DEF_ACTION(JGCommState, toCommunicatingByHost);
DEF_ACTION(JGCommState, toCommunicatingByEquipment);

//------------------------------------------------------------------------------
BEGIN_STATIC_STATE_TABLE(JGCommState);
    NEXT_TRANSITION(&JGStateTask::Root, TRIGGER_START, STATE_COMM, _TX("NULL"));
    NODE_TRANSITION(&_st_communications,      _TX("to COMMUNICATIONS"));
    NODE_TRANSITION(&_st_enabled,             _TX("to ENABLED"));
    NODE_TRANSITION(&_st_not_communicating,   _TX("to NOT_COMMUNICATING"));
    NODE_TRANSITION(&_st_host_initiated,      _TX("to HOST_INITIATED"));
    NODE_TRANSITION(&_st_equipment_initiated, _TX("to EQUIPMENT_INITIATED"));
    NEXT_TRANSITION(&_st_disabled,
                    TRIGGER_OPE_ENABLE,
                    STATE_ENABLED,
                    _TX("DISABLED to ENABLED"));
    NEXT_TRANSITION(&_st_enabled,
                    TRIGGER_OPE_DISABLE,
                    STATE_DISABLED,
                    _TX("ENABLED to DISABLED"));
    NEXT_TRANSITION(&_st_wait_cra,
                    TRIGGER_TRANSACTION_FAIL,
                    STATE_WAIT_DELAY,
                    _TX("WAIT_CRA to WAIT_DELAY"));
    NEXT_TRANSITION(&_st_wait_delay,
                    TRIGGER_TIMEOUT,
                    STATE_WAIT_CRA,
                    _TX("WAIT_DELAY to WAIT_CRA"));
    NEXT_TRANSITION(&_st_wait_delay,
                    TRIGGER_NOT_CONFIRM_MESSAAGE,
                    STATE_WAIT_CRA,
                    _TX("WAIT_DELAY to WAIT_CRA"));
    ACT_TRANSITION(&_st_wait_cra,
                    TRIGGER_S1F14,
                    toCommunicatingByEquipment,
                    _TX("WAIT_CRA to COMMUNICATING"));
    NEXT_TRANSITION(&_st_communicating,
                    TRIGGER_NOT_COMMUNICATING,
                    STATE_NOT_COMMUNICATING,
                    _TX("COMMUNICATING to NOT_COMMUNICATING"));
    NEXT_TRANSITION(&_st_not_communicating,
                    TRIGGER_COMMUNICATING,
                    STATE_WAIT_CRA,
                    _TX("NOT_COMMUNICATING to COMMUNICATING"));
    ACT_TRANSITION(&_st_wait_cr_from_host,
                    TRIGGER_S1F13,
                    toCommunicatingByHost,
                    _TX("WAIT_CR_FROM_HOST to COMMUNICATING"));
END_STATE_TABLE();


//-----------------------------------------------------------------------------
// Timer
//-----------------------------------------------------------------------------
class EstablishTimerHandler : public ACE_Handler
{
public:
    EstablishTimerHandler() : ACE_Handler() {}

    virtual void handle_time_out(const ACE_Time_Value &tv,
                                 const void * arg)
    {
        ACE_UNUSED_ARG(tv);
        JGCommState * comm = (JGCommState *)arg;
        comm->notify(TRIGGER_TIMEOUT);
    }
};

static EstablishTimerHandler timer;

//-----------------------------------------------------------------------------
// Control Timer
//-----------------------------------------------------------------------------
int JGCommState::startTimer()
{
    TRACE_FUNCTION(TRL_LOW, "JGCommState::startTimer");

    JGvalue delaytm(m_manager->m_estime->getv());
    int tm = delaytm.getInt();
    ACE_Time_Value tv(tm);
    // ACE_Time_Value expire_at = ACE_OS::gettimeofday() + tv;
    m_tmid = ACE_Proactor::instance()->schedule_timer(timer, this, tv);
    if (m_tmid == -1)
    {
        TRACE_ERROR((_TX("%p\n"), ACE_TEXT("schedule_timer")));
        return BEE_ERROR;
    }
    return BEE_SUCCESS;
}
//-----------------------------------------------------------------------------
int JGCommState::cancelTimer()
{
    ACE_Proactor::instance()->cancel_timer(this->m_tmid);
    return BEE_SUCCESS;
}


//-----------------------------------------------------------------------------
// Action: Wait CR From Host
//-----------------------------------------------------------------------------
int JGCommState::actWaitCrFromHost(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommState::actWaitCrFromHost");
    ACE_UNUSED_ARG(prev);
    ACE_UNUSED_ARG(trigger);

    this->updateState(JGCommManager::NOT_COMMUNICATING, JGCommManager::ENABLE);
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Action: Wait Delay
//-----------------------------------------------------------------------------
int JGCommState::actWaitDelay(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommState::actWaitDelay");
    ACE_UNUSED_ARG(prev);
    ACE_UNUSED_ARG(trigger);

    // !!! Clear send queue And Start spool !!!
    JGSpoolManager * spool = (JGSpoolManager *)m_equipment->findManager(CATEGORY_SPOOL);
    if (spool != NULL)
    {
        spool->startSpool();
    }

    // Establish communications timer
    this->startTimer();
    this->updateState(JGCommManager::NOT_COMMUNICATING, JGCommManager::ENABLE);

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Action: Wait Cra State
//-----------------------------------------------------------------------------
int JGCommState::actWaitCra(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommState::actWaitCra");
    ACE_UNUSED_ARG(prev);
    ACE_UNUSED_ARG(trigger);

    if (trigger->name() != TRIGGER_NOT_COMMUNICATING)
    {   // Disable -> Enable or Power-ON
        this->cancelTimer();
        int result = m_manager->sendS1F13();
        if (result < 0)
        {
            m_manager->m_device->getDriver()->enable();
            this->updateState(JGCommManager::NOT_COMMUNICATING, JGCommManager::DISABLE);
            return BEE_SUCCESS;
        }
    }
    this->updateState(JGCommManager::NOT_COMMUNICATING, JGCommManager::ENABLE);
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Action: CommunicatingState
//-----------------------------------------------------------------------------
int JGCommState::actCommunicating(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommState::actCommunicating");
    ACE_UNUSED_ARG(prev);
    ACE_UNUSED_ARG(trigger);

    // Establish communications timer cancel
    this->cancelTimer();

    this->updateState(JGCommManager::COMMUNICATING, JGCommManager::ENABLE);

    // Start control (ONLINE)
    JGControlManager * control =
            (JGControlManager *)m_equipment->findManager(CATEGORY_CONTROL);
    control->start();
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Action: Disabled
//-----------------------------------------------------------------------------
int JGCommState::actDisabled(JGState * prev, JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommState::actDistabledState");
    ACE_UNUSED_ARG(prev);
    ACE_UNUSED_ARG(trigger);

    this->updateState(JGCommManager::NOT_COMMUNICATING, JGCommManager::DISABLE);

    // Stop control (ROOT)
    JGControlManager * control =
            (JGControlManager *)m_equipment->findManager(CATEGORY_CONTROL);
    control->stop();

    return BEE_SUCCESS;
}

//------------------------------------------------------------------------------
// Condition node actions
//------------------------------------------------------------------------------
const BCHAR * JGCommState::actCondComm(JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommState::actCondComm");
    ACE_UNUSED_ARG(trigger);

    JGState * nextState = NULL;
    BCHAR * nextName;
    JGvalue defaultState(m_manager->m_sysdefault->getv());
    int defstate = defaultState.getInt();
    if (defstate == 1)
    {   // Request Communication enable
        nextName = STATE_ENABLED;
    }
    else
    {
        nextName = STATE_DISABLED;
    }
    nextState = this->state(nextName);
    if (nextState == NULL)
    {
        TRACE_ERROR((_TX("Illegal default control state = %s\n"),
                     nextName));
    }
    return nextName;
}

//-----------------------------------------------------------------------------
// Save current state.
//-----------------------------------------------------------------------------
int JGCommState::updateState(int state, int selector)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommState::updateState");
    JGvalue commstate(state);
    JGvalue commselect(selector);
    m_manager->m_commstate->setv(commstate);
    m_manager->m_commsel->setv(commselect);
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// ACTION: communicating
//-----------------------------------------------------------------------------
const BCHAR * JGCommState::toCommunicatingByEquipment(JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommState::toCommunicatingByEquipment");
    ACE_UNUSED_ARG(trigger);

    BS2Message * msg = ((JGMessageTrigger *)trigger)->message();
    const BCHAR * next;
    int ack = -1;
    BS2Atom * atom = msg->getAtom(_TX("COMMACK"));
    if (atom != NULL)        // don't delete atom
    {
        JGvalue ack_val(*atom);
        ack = ack_val.getInt();
    }
    if (ack == ACK_OK)
    {
        next = STATE_COMMUNICATING;

        // Stop spool
        // JGSpoolManager * spool = (JGSpoolManager *)m_equipment->findManager(CATEGORY_SPOOL);
        // if (spool != NULL)
        // {
        //     spool->stopSpool();
        // }
    }
    else
    {
        next = STATE_WAIT_DELAY;
        this->startTimer();             // Establish communications timer
    }
    return next;
}

//-----------------------------------------------------------------------------
// ACTION: communicating
//-----------------------------------------------------------------------------
const BCHAR * JGCommState::toCommunicatingByHost(JGTrigger * trigger)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommState::toCommunicatingByHost");
    ACE_UNUSED_ARG(trigger);

    const BCHAR * next = STATE_COMMUNICATING;
    const BS2Message * primsg = ((JGMessageTrigger *)trigger)->message();
    m_manager->sendS1F14(primsg, ACK_OK);

    this->cancelTimer();             // Establish communications timer

    // Stop spool
    // JGSpoolManager * spool = (JGSpoolManager *)m_equipment->findManager(CATEGORY_SPOOL);
    // if (spool != NULL)
    // {
    //     spool->stopSpool();
    // }

    return next;
}

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

    if (trigger->name() == TRIGGER_S1F13)
    {
        const BS2Message * primsg = ((JGMessageTrigger *)trigger)->message();
        JGvalue commsel(m_manager->m_commsel->getv());
        int sel = commsel.getInt();
        if (sel == JGCommManager::DISABLE)
        {
            m_manager->sendS1F14(primsg, ACK_NG);
        }
        else
        {
            m_manager->sendS1F14(primsg, ACK_OK);
        }
    }
    else
    {
        TRACE_ERROR((_TX("state(%s) : event(%s)\n"),
                  this->current().c_str(), trigger->name().c_str()));
    }
    return BEE_ERROR;
}

//
//
// C O M M U N I C A T I O N
//
//
//-----------------------------------------------------------------------------
// Return own.
//-----------------------------------------------------------------------------
static JGCommManager * _manager = NULL;

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

//-----------------------------------------------------------------------------
// Constructor/Destoructor
//-----------------------------------------------------------------------------
JGCommManager::JGCommManager() : JGManager(CATEGORY_COMM)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommManager::JGCommManager");
}


//-----------------------------------------------------------------------------
// Initialize
//-----------------------------------------------------------------------------
int JGCommManager::init(void * parm)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommManager::init");
    ACE_UNUSED_ARG(parm);

    // Get Variables of Communications
    m_commstate = m_equipment->variable(VAR_COMM_STATE);
    m_commsel = m_equipment->variable(VAR_COMM_STATE_SELECTOR);
    m_sysdefault = m_equipment->variable(VAR_COMM_SYSTEM_DEFAULT);
    m_estime = m_equipment->variable(VAR_ESTABLISH_TIMEOUT);

    JGvalue commstate((int)NOT_COMMUNICATING);
    m_commstate->setv(commstate);
    JGvalue commselect((int)DISABLE);
    m_commsel->setv(commselect);

    // Initial State Model
    m_commState = _JGCommState::instance();
    m_commState->manager(this);
    m_commState->equipment(m_equipment);
    m_commState->init();         // initialized transitions
    // m_commState->open();      // start thread

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Finish communication driver.
//-----------------------------------------------------------------------------
int JGCommManager::finish()
{
    TRACE_FUNCTION(TRL_LOW, "JGCommManager::finish");

    return m_device->close(-1);
}

//-----------------------------------------------------------------------------
// Check ready or not of the communication driver.
//-----------------------------------------------------------------------------
bool JGCommManager::isReadyDriver() const
{
    TRACE_FUNCTION(TRL_LOW, "JGCommManager::isReadyDriver");

    return m_device->getDriver()->canSend();
}

//-----------------------------------------------------------------------------
// Get Communications state
//-----------------------------------------------------------------------------
bool JGCommManager::isCommunicating()
{
    TRACE_FUNCTION(TRL_LOW, "JGCommManager::isCommunicating");

    JGvalue commstate(m_commstate->getv());
    int st = commstate.getInt();
    JGvalue commsel(m_commsel->getv());
    int sel = commsel.getInt();

    return (sel == ENABLE && st == COMMUNICATING);
}

//-----------------------------------------------------------------------------
// Enable/Disable Communications
//-----------------------------------------------------------------------------
int JGCommManager::changeCommunicating(bool flag)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommManager::changeCommunicating");

    BS2Driver * drv = m_device->getDriver();
    JGTrigger * trigger = NULL;
    int result = 0;
    if (flag)
    {
        if ((result = drv->enable()) == BEE_SUCCESS)
        {
            trigger = new JGNotifyTrigger(TRIGGER_OPE_ENABLE);
        }
    }
    else
    {   // Disconnected by host or operator
        result = drv->disable();
        trigger = new JGNotifyTrigger(TRIGGER_OPE_DISABLE);
    }
    if (trigger != NULL)
    {
        this->notify(trigger);
    }
    return result;
}

//-----------------------------------------------------------------------------
// MESSAGE: Establish Communication Request
//-----------------------------------------------------------------------------
int JGCommManager::sendS1F13()
{
    TRACE_FUNCTION(TRL_LOW, "JGCommManager::sendS1F13");

    BS2ListItem * listitem = this->makeEquipModel();
    BS2Message * msg = BS2Message::factory(SFCODE(1,13));
    msg->add(listitem);

    return this->send(msg);
}

//-----------------------------------------------------------------------------
// MESSAGE: Establish Communication Request Acknowledge
//-----------------------------------------------------------------------------
int JGCommManager::sendS1F14(const BS2Message * primsg, int ack)
{
    TRACE_FUNCTION(TRL_LOW, "JGCommManager::sendS1F14");
    BS2Assert(primsg->sf() == SFCODE(1,13));
    BS2ListItem * rootlist = new BS2ListItem;
    BS2ListItem * listitem = this->makeEquipModel();

    BS2Atom * atom = new BS2Binary((BYTE)ack);
    BS2Item * item = BS2Item::factory(_TX("COMMACK"), atom);
    rootlist->add(item);
    rootlist->add(listitem);                    // MDLN & SOFTREV

    BS2Message * msg = BS2Message::response(primsg);
    msg->add(rootlist);
    return this->send(msg);
}

//-----------------------------------------------------------------------------
// MESSAGE: Establish Communication Request
//-----------------------------------------------------------------------------
BS2ListItem * JGCommManager::makeEquipModel()
{
    TRACE_FUNCTION(TRL_LOW, "JGCommManager::makeEquipModel");

    BS2Item * item;
    BS2ListItem * listitem = new BS2ListItem;

    JGvalue mdln(m_equipment->value(VAR_EQUOP_MODEL));
    item = BS2Item::factory(_TX("MDLN"), mdln);
    listitem->add(item);

    JGvalue softrev(m_equipment->value(VAR_EQUIP_SOFTREV));
    item = BS2Item::factory(_TX("SOFTREV"), softrev);
    listitem->add(item);

    return listitem;
}

//-----------------------------------------------------------------------------
// Check S1F13's format
//-----------------------------------------------------------------------------
bool JGCommManager::checkMdlnAndSoftrev(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "BS2Message::checkMdlnAndSoftrev");

    if (msg->getAtom(_TX("MDLN")) != NULL)
    {
       return false;
    }
    if (msg->getAtom(_TX("MDLN")) != NULL)
    {
       return false;
    }
    return true;
}

//-----------------------------------------------------------------------------
//
// Thread of received message event.
//
//-----------------------------------------------------------------------------
BS2Message * JGCommManager::msg_svc(JGMessageTrigger * trigger, BS2Message * msg)
{
    BS2Message * replymsg = NULL;
    if (msg->sf() == SFCODE(1,13) || msg->sf() == SFCODE(1,14) )
    {
        if (checkMdlnAndSoftrev(msg))
        {
            m_commState->accept(trigger);       // dispatch
        }
        else
        {   // illegal message format from host
            // replymsg = new BS2S9F7Message(msg);

            // Case SELETE, COMMACK is not 0.
            this->sendS1F14(msg, ACK_NG);
        }
    }
    else
    {   // Unexpected message
        replymsg = this->unrecognized(msg);
    }

    return replymsg;
}

//-----------------------------------------------------------------------------
// I/O device event.
//-----------------------------------------------------------------------------
int JGCommManager::device_svc(JGDeviceTrigger * trigger)
{
    ACE_DEBUG((LM_DEBUG, ACE_TEXT("%s: device changes status[%s].\n"),
                          this->charName(), trigger->charName()));

    this->enable();
    while (! this->isReadyDriver())
    {
        ACE_OS::sleep(1);
    }

    int result = BEE_SUCCESS;
    if (m_hsmTask != NULL)
    {
        result = m_hsmTask->accept(trigger);
    }
    return result;
}



