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

//=============================================================================
/**
 *  @file    JGArarmManager.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 "JGAlarmManager.h"
#include "JGEquipment.h"
#include "JGEventManager.h"
#include "JGInfoManager.h"
#include "JGTriggerInfo.h"
#include "JGTaskTrigger.h"
#include "JGLogFileManager.h"
#include "JGLogTime.h"
#include "BS2ACKMessage.h"
#include "BS2ErrorMessage.h"
#include "BS2ListItem.h"
#include "BS2DeclAtoms.h"

static JGAlarmManager * _manager = NULL;

static ACE_Atomic_Op<ACE_Thread_Mutex, int> current_readers, current_writers;

//-----------------------------------------------------------------------------
// Constructor/Destructor
//-----------------------------------------------------------------------------
JGAlarmManager::JGAlarmManager() : JGManager(CATEGORY_ALARM)
{
    TRACE_FUNCTION(TRL_CONSTRUCT, "JGAlarmManager::JGAlarmManager");
}

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

}

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

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

    this->linkAlarms();
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Link Alarm and Variable data
//-----------------------------------------------------------------------------
void JGAlarmManager::linkAlarms()
{
    // Convert ascii data to bool and Mapping vid address.
    JGvalue value;
    JGAlarmTable::iterator alm_it = m_alarmtbl.begin();
    for ( ; alm_it != m_alarmtbl.end(); ++alm_it)
    {
        JGAlarm * alarm = &((*alm_it).second);
        JGVariable * var = NULL;
        if (! alarm->vid().isNil())
        {
            var = m_equipment->variable(alarm->vid());
            alarm->variable(var);
                                       // insert alarm pointer by vid
            m_alarmMap.insert(JGAlarmPtrPair(alarm->vid(), alarm));

        }
        else
        {
            alarm->variable(var);
        }
    }
    return ;
}

//-----------------------------------------------------------------------------
// Init. alarms set
//-----------------------------------------------------------------------------
int JGAlarmManager::initAlarmsSet()
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::initAlarmsSet");

    JGVariable * var = NULL;
    JGAlarmTable::iterator iter = m_alarmtbl.begin();
    for ( ; iter != m_alarmtbl.end(); ++iter)
    {
        JGAlarm * alarm = &((*iter).second);
        var = alarm->variable();
        if (var != NULL)
        {
            bool initv = var->curval().getBool();

            alarm->status(initv);
            if (alarm->status())
            {
                TRACE_DEBUG((_TX("Alarm (%s) is detect.\n"),
                             var->charName()));
                m_alarms.push_back(alarm);
            }
        }
    }
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Find the alarm
//-----------------------------------------------------------------------------
JGAlarm * JGAlarmManager::find(JGid& alid)
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::find");

    JGAlarm * alarm;
    JGAlarmTable::iterator iter = m_alarmtbl.find(alid);
    if (iter == m_alarmtbl.end())
    {
        return NULL;
    }
    alarm = &((*iter).second);
    return alarm;
}

//-----------------------------------------------------------------------------
// Find the alarm
//-----------------------------------------------------------------------------
JGAlarm * JGAlarmManager::find(JGVariable * var)
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::find");

    JGAlarm * alarm;
    JGAlarmMap::iterator iter = m_alarmMap.find(var->vid());
    if (iter == m_alarmMap.end())
    {
        return NULL;
    }
    alarm = (*iter).second;
    return alarm;
}

//-----------------------------------------------------------------------------
// Accept trigger from the variable.
//-----------------------------------------------------------------------------
int JGAlarmManager::notify(const string& category, JGVariable * var,
                           void * arg)
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::notify");
    ACE_UNUSED_ARG(arg);

    TRACE_ERROR((_TX("%s : %s = %s\n"), category.c_str(), var->charName(),
                                        var->getv().toString().c_str()));

    if (category == TRID_ALARM || category == TRG_ALARM_SET ||
        category == TRG_ALARM_CLEAR)
    {
        JGAlarm * alarm = this->find(var);    // find alarm by variable
        if (alarm == NULL)
        {
            TRACE_ERROR((_TX("Alarm is not found by %s.\n"), var->charName()));
            return BEE_ERROR;
        }

        // Check alarm status (changed ?)
        JGid alid(alarm->alid());
        bool alstatus = alarm->status();       // before status

        JGVariable * alid_var = m_equipment->variable(VAR_ALARM_ID);
        JGvalue val(alid);
        alid_var->setv(val);

        // Compare alcd and current value
        bool curval;
        if (category == TRID_ALARM)
        {
            JGvalue value(var->curval());
            curval = value.getBool();
        }
        else if (category == TRG_ALARM_SET)
        {
            curval = true;
        }
        else /* if (category == TRG_ALARM_CLEAR) */
        {
            curval = false;
        }

        if (alstatus == curval)
        {
            TRACE_ERROR((_TX("Alarm status is not equal with the variable(%s).\n"),
                         var->charName()));
            return BEE_ERROR;
        }

        {   // *** LOGGING ***
            JGLogTime now;
            string  logstr = alid.toString();
            logstr.append(_TX(" "));
            logstr.append((curval) ? _TX("ON") : _TX("OFF"));
            JGLogFileManager::instance()->log(this->name(), now, logstr, var);
        }

        // Alarm
        BS2Message * msg = this->detectAlarm(alid, curval);
        if (msg != NULL)
        {   // Send alarm set or clear message(S5F1).
            this->send(msg);
        }

        // Set data item for S6F11
        if (curval)
        {   // Alarm detected.
            m_equipment->sendEvent(this, EVT_ALARM_DETECTED);
        }
        else
        {   // Alarm cleared.
            m_equipment->sendEvent(this, EVT_ALARM_CLEARED);
        }
    }
    else
    {
        TRACE_ERROR((_TX("Not implement: %s\n"), category.c_str()));
        return BEE_ERROR;
    }
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Clear Alarm
//-----------------------------------------------------------------------------
BS2Item * JGAlarmManager::createAlarmSet()
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::createAlarmSet");

    ACE_MT(ACE_GUARD_RETURN(ACE_Thread_Mutex, _alarm_set, this->m_lock, NULL));
    BS2ListItem * listitem = new BS2ListItem();

    JGAlarms::iterator iter = m_alarms.begin();
    for ( ; iter != m_alarms.end(); ++iter)
    {
        JGAlarm * alarm = (JGAlarm *)*iter;
        BS2Item * item = BS2Item::factory(_TX("ALID"), alarm->alid());
        listitem->add(item);
    }
    return (BS2Item *)listitem;
}

//-----------------------------------------------------------------------------
// Update AlarmSet
//-----------------------------------------------------------------------------
int JGAlarmManager::updateAlarmSet(JGid& id, bool status)
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::updateAlarmSet");

    ACE_MT(ACE_GUARD_RETURN(ACE_Thread_Mutex, _alarm_set, this->m_lock,
                            BEE_LOCKED));
    JGAlarm * alarm;
    int duplicate = 0;
    JGAlarms::iterator iter = m_alarms.begin();
    while (iter != m_alarms.end())
    {
        alarm = *iter;
        if (alarm->alid() == id)
        {
            if (status)
            {   // cheack duplicate the alarm
                duplicate++;
                TRACE_ERROR((_TX("alarm(%x) is duplicate.\n"), id.getUInt()));
                iter++;
            }
            else
            {   // clear the alarm
                iter = m_alarms.erase(iter);
            }
        }
        else
        {
            iter++;
        }
    }
    if (status && duplicate == 0)
    {   // Get the alarm info.
        JGAlarmTable::iterator at_iter = m_alarmtbl.find(id);
        if (at_iter == m_alarmtbl.end())
        {   // illegal id
            TRACE_ERROR((_TX("alarm(%x) is not found.\n"), id.getUInt()));
            return BEE_ERROR;
        }
        alarm = &((*at_iter).second);
        m_alarms.push_back(alarm);
    }
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Change the enable flag
//-----------------------------------------------------------------------------
int JGAlarmManager::changeEnabled(JGid& id, bool eflag)
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::changeEnabled");

    JGAlarmTable::iterator iter = m_alarmtbl.find(id);
    if (iter == m_alarmtbl.end())
    {
        return BEE_ERROR;
    }
    JGAlarm * alarm = &((*iter).second);
    alarm->enabled(eflag);
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Change all enable flags
//-----------------------------------------------------------------------------
int JGAlarmManager::changeEnabledAll(bool eflag)
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::changeEnabledAll");

    JGAlarmTable::iterator iter = m_alarmtbl.begin();
    for ( ; iter != m_alarmtbl.end(); ++iter)
    {
        JGAlarm * alarm = &((*iter).second);
        alarm->enabled(eflag);
    }
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Detect and Create Alarm Report message (S5F1)
//-----------------------------------------------------------------------------
BS2Message * JGAlarmManager::detectAlarm(JGid& id, bool status)
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::detectAlarm");

    JGAlarmTable::iterator iter = m_alarmtbl.find(id);
    if (iter == m_alarmtbl.end())
    {
        TRACE_ERROR((_TX("alarm(%s) is not found.\n"), id.toString().c_str()));
        return NULL;
    }
    JGAlarm * alarm = &((*iter).second);
    alarm->status(status);             // Update the condition to set or clear
    this->updateAlarmSet(id, status);  // Erase or Entry the alarm in AlarmSet.

    BS2Message * msg = NULL;
    if (alarm->enabled())
    {
        BS2Item * item = alarm->alarm();
        msg = BS2Message::factory(SFCODE(5,1));
        BS2Assert(msg != NULL);
        msg->add(item);
    }
    else
    {
        // TRACE_DEBUG((_TX("alarm(%s) is disabled.\n"), id.toString().c_str()));
    }
    return msg;
}

//-----------------------------------------------------------------------------
// List Alarm message (S5F6)
//-----------------------------------------------------------------------------
static int _parseAlid(void * clientData, BS2Item * item)
{
    BS2values * alids = (BS2values *)clientData;
    if ((! item->isList()) && item->name() == _TX("ALID"))
    {
        BS2Atom * atom = item->atom();
        if (atom->isArray())
        {
            atom->getStreamData((BYTE *)alids);
        }
        else
        {
            if (atom->haveData())
            {
                JGvalue v(*atom);
                alids->push_back(v);
            }
        }
    }
    return 0;
}

//-----------------------------------------------------------------------------
BS2Message * JGAlarmManager::alarmList(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::alarmList");

    BS2values vv;
    msg->traverse(_parseAlid, &vv);    // parse S5F5

    JGAlarm * alarm;
    BS2Item * item;
    BS2ListItem * rootitem = new BS2ListItem;
    if (vv.size() == 0)
    {   // all alarm report
        JGAlarmTable::iterator iter = m_alarmtbl.begin();
        for ( ; iter != m_alarmtbl.end(); ++iter)
        {
            alarm = &((*iter).second);
            item = alarm->alarm();
            rootitem->add(item);
        }
    }
    else
    {
        JGvalue val;
        BS2values::iterator v_iter = vv.begin();
        for ( ; v_iter != vv.end(); ++v_iter)
        {
            val = *v_iter;
            unsigned int id = val.getUInt();
            JGAlarmTable::iterator iter = m_alarmtbl.find(id);
            if (iter == m_alarmtbl.end())
            {   // the alarm is nothing
                BS2ListItem * listitem = new BS2ListItem();
                item = BS2Item::factory(_TX("ALCD"), new BS2Binary());
                listitem->add(item);

                item = BS2Item::factory(_TX("ALID"), new BS2UInt4(id));
                listitem->add(item);

                item = BS2Item::factory(_TX("ALTX"), new BS2Ascii());
                listitem->add(item);

                rootitem->add(listitem);
            }
            else
            {
                alarm = &((*iter).second);
                item = alarm->alarm();
                rootitem->add(item);
            }
        }
    }

    BS2Message * replymsg = BS2Message::response(msg);
    BS2Assert(replymsg != NULL);
    replymsg->add(rootitem);

    return replymsg;
}

//-----------------------------------------------------------------------------
// List Enabled Alarm message (S5F3 -> S5F4)
//-----------------------------------------------------------------------------
class _ParseAled : public BS2Traverser
{
    friend class JGAlarmManager;
public:
    _ParseAled() : m_aled(false), m_alid(_TX("")) {}
    virtual ~_ParseAled() {}

    bool  m_aled;
    JGid  m_alid;

    virtual int parseItem(BS2Item * item) {
        TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::_ParseAled::parseItem");
        BS2Atom * atom = item->atom();
        if (item->name() == _TX("ALED"))
        {
             if (atom->isBinary())
             {
                 BYTE * bin = ((BS2Binary *)atom)->value();
                 m_aled = ((*bin & 0x80) != 0) ? true : false;
             }
             else
             {
                 TRACE_DEBUG((_TX("ALED format is not binary(0x%x).\n"),
                              atom->format()));
                 return -1;
             }
        }
        else if (item->name() == _TX("ALID"))
        {
            atom->get(m_alid);
        }
        return 0;
    }
};

//-----------------------------------------------------------------------------
BS2Message * JGAlarmManager::setEnabled(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::setEnabled");

    BS2Message * replymsg;
    BYTE ack = ACK_OK;
    _ParseAled aledinfo;
    int result = msg->traverse(&aledinfo);    // parse S5F3
    if (result < 0)
    {
        replymsg = BS2Message::response(msg, (BYTE)ACK_NG, _TX("ACKC5"));
        return replymsg;
    }

    //
    bool aled = aledinfo.m_aled;
    if (aledinfo.m_alid.isNil())
    {   // change enable/disable bit of all alarms.
        JGAlarmTable::iterator iter = m_alarmtbl.begin();
        for ( ; iter != m_alarmtbl.end(); ++iter)
        {
            JGAlarm * alarm = &((*iter).second);
            alarm->enabled(aled);
        }
    }
    else
    {
        JGAlarmTable::iterator iter = m_alarmtbl.find(aledinfo.m_alid);
        if (iter != m_alarmtbl.end())
        {
            JGAlarm * alarm = &((*iter).second);
            alarm->enabled(aled);
        }
        else
        {
            ack = ACK_NG;            // set error code
        }
    }
    // Create reply message.
    replymsg = BS2Message::response(msg, ack, _TX("ACKC5"));
    return replymsg;
}

//-----------------------------------------------------------------------------
// List Enabled Alarm message (S5F8)
//-----------------------------------------------------------------------------
BS2Message * JGAlarmManager::alarmEnabledList(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "JGAlarmManager::alarmEnabledList");

    BS2ListItem * rootitem = new BS2ListItem;
    JGAlarmTable::iterator iter = m_alarmtbl.begin();
    for ( ; iter != m_alarmtbl.end(); ++iter)
    {
        JGAlarm * alarm = &((*iter).second);
        if (alarm->enabled())
        {
            BS2Item * item = alarm->alarm();
            rootitem->add(item);
        }
    }
    BS2Message * replymsg = BS2Message::response(msg);
    BS2Assert(replymsg != NULL);
    replymsg->add(rootitem);

    return replymsg;
}

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

    if (msg->sf() == SFCODE(5,3))
    {
        replymsg = this->setEnabled(msg);
    }
    else if (msg->sf() == SFCODE(5,5))
    {
        replymsg = this->alarmList(msg);
    }
    else if (msg->sf() == SFCODE(5,7))
    {
        replymsg = this->alarmEnabledList(msg);
    }
    else if (msg->sf() == SFCODE(5,2))
    {
        ;  // ignore
    }
    else
    {   // Unexpected message
        replymsg = this->unrecognized(msg);
    }

    return replymsg;
}

