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

//=============================================================================
/**
 *  @file    JGTimeModule.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 "JGTimeModule.h"
#include "JGEquipment.h"
#include "JGInfoManager.h"
#include "JGLimitManager.h"
#include "JGTaskTrigger.h"
#include "sac/JGIODevManager.h"
#include "BS2Message.h"
#include "BS2ACKMessage.h"
#include "BS2ListItem.h"
#include "BS2DeclAtoms.h"
#include "BS2Driver.h"


extern int ascii2tm(const BCHAR * timestr, struct tm * outm);
extern int ascii2tv(const BCHAR * timestr, BS2TimeValue& outv);

static JGTimeModule * _timerModule = NULL;

static const BCHAR * _tm_format = _TX("%Y%m%d%H%M%S00");

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

    virtual void handle_time_out(const ACE_Time_Value &tv,
                                 const void * arg)
    {
        ACE_UNUSED_ARG(tv);

        JGTimeModule * tmod = (JGTimeModule *)arg;
        tmod->time_svc();
    }
};

static PollingTimerHandler _polling;


//------------------------------------------------------------------------------
// Constructor/Destructor
//------------------------------------------------------------------------------
JGTimeModule::JGTimeModule(bool usingNetTime) : JGManager(CATEGORY_CLOCK)
{
    TRACE_FUNCTION(TRL_CONSTRUCT, "JGTimeModule::JGTimeModule");
    ACE_UNUSED_ARG(usingNetTime);

    m_correctTime = ACE_Time_Value::zero;          // Initial correct host time
    m_plctimer = false;

}

//------------------------------------------------------------------------------
JGTimeModule::~JGTimeModule()
{
    TRACE_FUNCTION(TRL_CONSTRUCT, "JGTimeModule::~JGTimeModule");
}

//-----------------------------------------------------------------------------
// Return own.
//-----------------------------------------------------------------------------
JGTimeModule * JGTimeModule::instance()
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::instance");
    if (_timerModule == NULL)
    {
        _timerModule = new JGTimeModule;
    }
    return _timerModule;
}

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

    JGvalue val = m_equipment->value(VAR_TIME_FORMAT);
                             // Meaningless as has not been initialized yet.
    m_timeformat = val.getInt();
    m_correctTime = 0;                 // Initial correct host time

    this->linkAction();                // Create trigger table

    // Read plc register (initialize current value)
    JGTriggerInfos::iterator iter = m_trginfos.begin();
    for ( ; iter != m_trginfos.end(); ++iter)
    {
        JGTriggerInfo * sub = *iter;
        sub->init();
    }

    m_iodev = (JGIODevManager *)m_equipment->findManager(CATEGORY_IODEVICE);
    BEEAssert(m_iodev != NULL);

    const string& plctimer_p = m_equipment->getConf(CONF_USE_PLCTIMER);
    if (plctimer_p.size() > 0)
    {
        if (plctimer_p == _TX("true") || plctimer_p == _TX("TRUE"))
        {
            m_plctimer = true;
        }
    }

    return 0;
}

//-----------------------------------------------------------------------------
// Link trigger and function pointer
//-----------------------------------------------------------------------------
void JGTimeModule::linkAction()
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::linkAction");

    JGInfoManager * infoManager =
                    (JGInfoManager *)m_equipment->findManager(CATEGORY_INFO);
    JGVariableTable * vars = infoManager->varTable();
    JGVariableTable::iterator read_it = vars->begin();
    for ( ; read_it != vars->end(); ++read_it)
    {
        JGVariable * var = &((*read_it).second);

        // Set event action table to the variable
        if (var->trgid().size() > 0)
        {
            string actid = var->trgid();
            JGTriggerSubTable::iterator iter = m_trgsubs.find(actid);
            if (iter == m_trgsubs.end())
            {
                var->trginfo(NULL);
                TRACE_ERROR((_TX("\"%s\" trigger is not found.\n"),
                             actid.c_str()));
            }
            else
            {
                JGTriggerInfo * trginfo;
                JGTriggerSubject * trgsub = (*iter).second;
                if (trgsub->isGroup())
                {   // Trigger info. is 1 : N
                    trginfo = trgsub->entity();
                    var->trginfo(trginfo);
                    trginfo->add(var);
                    if (! trginfo->entry())
                    {
                        trginfo->variable(var);          // for BITSET
                        trginfo->copyRelation();         // for PLURAL
                        m_trginfos.push_back(trginfo);   // queued observed with i/o device
                        trginfo->entry(true);
                    }
                }
                else
                {   // Trigger info. is 1 : 1
                    trginfo = trgsub->factory();
                    var->trginfo(trginfo);
                    trginfo->variable(var);
                    m_trginfos.push_back(trginfo);   // queued observed with i/o device
                }
            }
        }
    }
    return ;
}

//-----------------------------------------------------------------------------
// Open and import database.
//-----------------------------------------------------------------------------
int JGTimeModule::open(void * parm)
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::open");
    ACE_UNUSED_ARG(parm);

    if (this->activate(THR_NEW_LWP | THR_DETACHED) == -1)
        ACE_ERROR_RETURN((LM_ERROR, ACE_TEXT("%p\n"), ACE_TEXT("spawn")), -1);

    ACE_Time_Value tv(1);  // 1 second
    m_timerid = ACE_Proactor::instance()->schedule_repeating_timer(_polling, this, tv);
    if (m_timerid == -1)
    {
        TRACE_ERROR((_TX("%p\n"), ACE_TEXT("schedule_repeating_timer")));
        return BEE_ERROR;
    }

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Get trigger subject by trigger id
//-----------------------------------------------------------------------------
JGTriggerSubject * JGTimeModule::triggerSubject(const BCHAR * trgid)
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::triggerSubject");
    string strid = trgid;
    JGTriggerSubTable::iterator iter = m_trgsubs.find(strid);
    JGTriggerSubject * trgsub;
    if (iter == m_trgsubs.end())
    {
        trgsub = NULL;
    }
    else
    {
        trgsub = (*iter).second;
    }
    return trgsub;
}

//------------------------------------------------------------------------------
// Set system clock
//------------------------------------------------------------------------------
int JGTimeModule::ascii2tm(const BCHAR * timestr, struct tm * outm)
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::ascii2tm");

    return ::ascii2tm(timestr, outm);
}

//------------------------------------------------------------------------------
// Date and Time
//------------------------------------------------------------------------------
void JGTimeModule::getDateAndTime(string& time)
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::getDateAndTime");

    BCHAR timebuf[128];
    size_t maxsize = sizeof(timebuf);
    if (m_plctimer)
    {
        struct tm timeval;
        m_iodev->getClock(timeval);
        _tcsftime(timebuf, maxsize, _tm_format, &timeval);
    }
    else
    {
        BS2TimeValue machine_time = m_correctTime + ACE_OS::gettimeofday();
        time_t t = machine_time.sec();
        struct tm * tmvptr = localtime(&t);
        _tcsftime(timebuf, maxsize, _tm_format, tmvptr);
    }
    time = timebuf;
}

//------------------------------------------------------------------------------
extern int ascii2tm(const BCHAR * timestr, struct tm * outm);

void JGTimeModule::setDateAndTime(const BCHAR * time)
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::setDateAndTime");
    if (m_plctimer)
    {
        struct tm uptime;
        ::ascii2tm(time, &uptime);
        m_iodev->setClock(uptime);
    }

    BS2TimeValue host_time;
    ::ascii2tv(time, host_time);
    m_correctTime = host_time - ACE_OS::gettimeofday();

}

//------------------------------------------------------------------------------
// System time
//------------------------------------------------------------------------------
void JGTimeModule::getSystemClock(string& retime)
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::getSystemClock");
    string sysclock;
    struct tm * now;
    BCHAR  timebuf[24];

    if (m_plctimer)
    {
        struct tm plctm;
        m_iodev->getClock(plctm);
        now = &plctm;
    }
    else
    {
        time_t lt;
        time(&lt);
        now = localtime(&lt);
    }

    // Convert semi time format
    if (this->getTimestampFormat() == 0)
    {   // 12 bytes format
        _stprintf(timebuf, _TX("%02d%02d%02d%02d%02d%02d"),
                           (now->tm_year + 1900) % 100,
                           now->tm_mon + 1,
                           now->tm_mday,
                           now->tm_hour,
                           now->tm_min,
                           now->tm_sec);
    }
    else
    {   // 16 bytes format
        _stprintf(timebuf, _TX("%d%02d%02d%02d%02d%02d00"),
                           now->tm_year + 1900,
                           now->tm_mon + 1,
                           now->tm_mday,
                           now->tm_hour,
                           now->tm_min,
                           now->tm_sec);
    }
    retime = timebuf;
    return ;
}

//------------------------------------------------------------------------------
// Formatted Date and Time
//------------------------------------------------------------------------------
int JGTimeModule::getTimestampFormat()
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::getTimestampFormat");

    JGvalue val = m_equipment->value(VAR_TIME_FORMAT);
    m_timeformat = val.getInt();
    return m_timeformat;
}


//------------------------------------------------------------------------------
// Formatted Date and Time
//------------------------------------------------------------------------------
string JGTimeModule::clock()
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::clock");
    string now;
    getFormattedDateAndTime(now);
    return now;
}

//------------------------------------------------------------------------------
void JGTimeModule::getFormattedDateAndTime(string& time)
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::getFormattedDateAndTime");

    BCHAR timebuf[128];
    size_t maxsize = sizeof(timebuf);
    struct tm * timeval;

    if (m_plctimer)
    {
        struct tm plctm;
        m_iodev->getClock(plctm);
        timeval = &plctm;
    }
    else
    {
        BS2TimeValue machine_time =  m_correctTime + ACE_OS::gettimeofday();
        time_t t = machine_time.sec();
        timeval = localtime(&t);
    }
    _tcsftime(timebuf, maxsize, _tm_format, timeval);
    time = timebuf;
}

//------------------------------------------------------------------------------
void JGTimeModule::setFormattedDateAndTime(const BCHAR * time)
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::setFormattedDateAndTime");
    setDateAndTime(time);
}

//------------------------------------------------------------------------------
// Set system clock
//------------------------------------------------------------------------------
int JGTimeModule::changeClock(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::changeClock");

    BS2TimeValue host_time;

    BS2Atom * atom = msg->getAtom(_TX("TIME"));
    if (atom == NULL)
    {
        return BEE_ERROR;
    }
    if (! atom->isAscii())
    {
        return BEE_ERROR;
    }
    string reqtm = ((BS2Ascii *)atom)->value();
    const BCHAR * tmstr = reqtm.c_str();

    if (m_plctimer)
    {
        struct tm plctime;
        ::ascii2tm(tmstr, &plctime);
        m_iodev->setClock(plctime);
    }

    // Save correct time value
    ::ascii2tv(tmstr, host_time);
    m_correctTime = host_time - ACE_OS::gettimeofday();

    return BEE_SUCCESS;
}

//------------------------------------------------------------------------------
// Set system clock : S2F31 --> S2F32
//------------------------------------------------------------------------------
BS2Message * JGTimeModule::setDateAndTime(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::setDateAndTime");

    BYTE ack = (this->changeClock(msg) == BEE_SUCCESS) ? ACK_OK : ACK_NG;

    BS2Message * replymsg = BS2Message::response(msg, ack, _TX("TIACK"));
    BS2Assert(replymsg != NULL);

    return replymsg;
}

//------------------------------------------------------------------------------
// Get system clock
//------------------------------------------------------------------------------
BS2Message * JGTimeModule::getClock(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::getClock");

    struct tm * now;
    BCHAR  timebuf[24];

    if (m_plctimer)
    {
        struct tm plctime;
        m_iodev->getClock(plctime);
        now = &plctime;
    }
    else
    {
        time_t lt;
#if 1
        BS2TimeValue machine_time = m_correctTime +  ACE_OS::gettimeofday();
        lt = machine_time.sec();
#else
        time(&lt);
#endif
        now = localtime(&lt);
    }

    // Convert semi time format
    if (this->getTimestampFormat() == 0)
    {   // 12 bytes format
        _stprintf(timebuf, _TX("%02d%02d%02d%02d%02d%02d"),
                           (now->tm_year + 1900) % 100,
                           now->tm_mon + 1,
                           now->tm_mday,
                           now->tm_hour,
                           now->tm_min,
                           now->tm_sec);
    }
    else
    {   // 16 bytes format
        _stprintf(timebuf, _TX("%d%02d%02d%02d%02d%02d00"),
                           now->tm_year + 1900,
                           now->tm_mon + 1,
                           now->tm_mday,
                           now->tm_hour,
                           now->tm_min,
                           now->tm_sec);
    }

    BS2Atom * atom = new BS2Ascii(timebuf);
    BS2Item * item = BS2Item::factory(_TX("TIME"), atom);
    BS2Message * replymsg = BS2Message::response(msg);
    BS2Assert(replymsg != NULL);
    replymsg->add(item);

    return replymsg;
}


//------------------------------------------------------------------------------
// Create clock reoprt of S6F11
//------------------------------------------------------------------------------
BS2Item * JGTimeModule::clockItem()
{
    TRACE_FUNCTION(TRL_LOW, "JGTimeModule::clockItem");
    BS2ListItem * rptlist = new BS2ListItem();
    BS2ListItem * mbrlist = new BS2ListItem();
    BS2Atom * atom;
    BS2Item * item;

    string now;
    JGTimeModule::instance()->getFormattedDateAndTime(now);

    atom = new BS2Ascii(now);
    item = BS2Item::factory(_TX("V"), atom);
    mbrlist->add(item);

    atom = new BS2Ascii(_TX("Clock"));
    item = BS2Item::factory(_TX("RPTID"), atom);
    rptlist->add(item);
    rptlist->add(mbrlist);
    return (BS2Item *)rptlist;
}

//------------------------------------------------------------------------------
// Dump
//------------------------------------------------------------------------------
void JGTimeModule::dump() const
{
    return ;
}

//-----------------------------------------------------------------------------
// Thread of received message event.
//-----------------------------------------------------------------------------
int JGTimeModule::time_svc()
{
    //TRACE_FUNCTION(TRL_LOW, "JGTimeModule::time_svc");
    int result = 0;

    JGTriggerInfos::iterator iter = m_trginfos.begin();
    for ( ; iter != m_trginfos.end(); ++iter)
    {
        JGTriggerInfo * sub = *iter;
        sub->sense();                    // Sense and Issue trigger
    }

    JGInfoManager::instance()->monitor();
    JGLimitManager::instance()->monitor();

    return result;
}

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

    if (msg->sf() == SFCODE(2,31))
    {
        replymsg = this->setDateAndTime(msg);
    }
    else if (msg->sf() == SFCODE(2,17))
    {
        replymsg = this->getClock(msg);
    }
    else if (msg->sf() == SFCODE(2,18))
    {
        this->changeClock(msg);
    }
    else
    {   // Unexpected message
        replymsg = this->unrecognized(msg);
    }

    return replymsg;
}



