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

//=============================================================================
/**
 *  @file    JGLimitManager.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 "JGLimitManager.h"
#include "JGEquipment.h"
#include "JGInfoManager.h"
#include "JGCommDevice.h"
#include "JGTaskTrigger.h"
#include "BS2ACKMessage.h"
#include "BS2ErrorMessage.h"
#include "BS2ListItem.h"
#include "BS2DeclAtoms.h"

static JGLimitManager * _manager = NULL;

enum
{
    LVACK_VARIABLE_NOT_EXIST = 1,
    LVACK_NO_LIMIT_CAPABILITY = 2,
    LVACK_VARIABLE_REPEATED = 3,
    LVACK_LIMIT_VALUE_ERROR = 4
};

//-----------------------------------------------------------------------------
// Make nack that is of limit variable for S2F46
//-----------------------------------------------------------------------------
//      VLAACK = 0 : Acknowledge, command will be performed
//               1 : Limit attribute definition error
//               2 : Cannot perform now
//             > 2 : Other equipment-specified error
//      LVACK = 1 : Variable does not exist
//              2 : Variable has no limits capability
//              3 : Variable repeated in message
//              4 : Limit value error as described in LIMITACK
//-----------------------------------------------------------------------------
static void addNackItem(BS2ListItem * baselist, JGid& vid, BYTE nack,
                        BS2ListItem * limitack_list)
{
    BS2Atom * atom = BS2Atom::factory(vid);
    BS2Item * item = BS2Item::factory(_TX("VID"), atom);
    BS2ListItem * mbrlist = new BS2ListItem;
    mbrlist->add(item);

    atom = new BS2Binary(nack);
    item = BS2Item::factory(_TX("LVACK"), atom);
    mbrlist->add(item);

    if (nack == LVACK_LIMIT_VALUE_ERROR && limitack_list != NULL)
    {
        mbrlist->add(limitack_list);   // Set reason
    }
    else
    {
        BS2ListItem * limlist = new BS2ListItem;
        mbrlist->add(limlist);         // Set empty list
    }
    baselist->add(mbrlist);
    return ;
}

//-----------------------------------------------------------------------------
// Constructor/Destructor
//-----------------------------------------------------------------------------
JGLimitManager::JGLimitManager() : JGManager(CATEGORY_LIMIT)
{
    TRACE_FUNCTION(TRL_CONSTRUCT, "JGLimitManager::JGLimitManager");
}

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

}

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

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

    this->linkBorder();

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Link deadband data to limit
//-----------------------------------------------------------------------------
int JGLimitManager::linkBorder(int parm)
{
    TRACE_FUNCTION(TRL_LOW, "JGLimitManager::linkBorder");
    ACE_UNUSED_ARG(parm);

    JGLimitTable::iterator iter = m_validLimits.begin();
    for ( ; iter != m_validLimits.begin(); iter++)
    {
        JGLimit * limit = &((*iter).second);
        JGVariable * var = limit->variable();
        var->limit(limit);              // Set limit information

        // Convert border data string to binary.
        int borderCount = 0;
        for (size_t i = 0; i < limit->m_borders.size(); i++)
        {
            JGLimitBorder * border = &(limit->m_borders[i]);
            if (border->enabled())
            {
                borderCount++;
            }
        }
        if (borderCount < 1)
        {   // Valid border data is nothing
            continue;                   // Ignore this limit information
        }

        limit->sort();                  // Sort border data
                                        // Is limit-id sequensial ?
// #ifdef JYUGEM_DEBUG
        for (size_t j = 0; j < limit->m_borders.size(); j++)
        {
            JGLimitBorder * border = &(limit->m_borders[j]);
            if (border->m_limid != (int)(j + 1))
            {
                TRACE_ERROR((_TX("Unexpect position of limit-id %s %d \n"),
                             limit->vid().toString().c_str(), border->m_limid));
            }
        }
// #endif
    }

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Supervise variables.
//-----------------------------------------------------------------------------
int JGLimitManager::monitor()
{
    TRACE_FUNCTION(TRL_LOW, "JGLimitManager::monitor");
    ACE_Guard<ACE_Thread_Mutex> limit_mon(this->m_lock);
    if (limit_mon.locked () == 0)
    {   // if collision, monitor is pass
        TRACE_ERROR((_TX("Mutex is not locked.\n")));
        return BEE_ERROR;
    }

    BS2TimeValue tv(ACE_OS::gettimeofday());  // Save current system time;
    JGLimitTable::iterator lim_iter = m_limits.begin();
    for ( ; lim_iter != m_limits.end(); lim_iter++)
    {
        JGLimit * curlim = &((*lim_iter).second);
        JGVariable * var = curlim->variable();
        JGvalue curv(var->getv());
        JGvalue lastv(var->lastval());
        if (var->changed())
        {
            int result = curlim->transition(lastv, curv);
            if (result != JGLimit::TRANSITION_NONE)
            {   // transit deadband
                curlim->m_tv = tv;
                this->notify(TRG_TRANSIT_ZONE, var, NULL);
            }
        }
    }

    return BEE_SUCCESS;
}


//-----------------------------------------------------------------------------
// New limit is made by valid limit.
//-----------------------------------------------------------------------------
JGLimit * JGLimitManager::entry(JGid& vid)
{
    TRACE_FUNCTION(TRL_LOW, "JGLimitManager::entry");

    JGLimitTable::iterator iter = m_validLimits.find(vid);
    if (iter == m_validLimits.end())
    {
        return NULL;
    }
    m_limits.insert(JGLimitPair(vid, (*iter).second));
    JGLimitTable::iterator clone_it = m_limits.find(vid);
    JGLimit * limit = &((*clone_it).second);

    JGVariable * var = m_equipment->variable(vid);
    BS2Assert(var != NULL);
    var->limit(limit);

    return limit;
}

//-----------------------------------------------------------------------------
// Get limit data by vid.
//-----------------------------------------------------------------------------
JGLimit * JGLimitManager::find(JGid& vid)
{
    TRACE_FUNCTION(TRL_LOW, "JGLimitManager::find");

    JGLimitTable::iterator iter = m_limits.find(vid);
    if (iter == m_limits.end())
    {
        return NULL;
    }
    return &((*iter).second);
}

//-----------------------------------------------------------------------------
// Remove limit data by vid.
//-----------------------------------------------------------------------------
int JGLimitManager::remove(JGid& vid)
{
    TRACE_FUNCTION(TRL_LOW, "JGLimitManager::remove");

    JGLimitTable::iterator iter = m_limits.find(vid);
    if (iter == m_limits.end())
    {   // Not found
        return BEE_ERROR;
    }
    m_limits.erase(iter);
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Get validate limit data by vid.
//-----------------------------------------------------------------------------
JGLimit * JGLimitManager::getValidLimit(JGid& vid)
{
    TRACE_FUNCTION(TRL_LOW, "JGLimitManager::getValidLimit");

    JGLimitTable::iterator iter = m_validLimits.find(vid);
    if (iter == m_validLimits.end())
    {
        return NULL;
    }
    return &((*iter).second);
}

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

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

    int result;
    if (category == TRG_TRANSIT_ZONE)
    {
        JGDeviceRequest * devReq = new JGDeviceRequest(category, var, arg, this);
        result = this->put(devReq);
    }
    else
    {
        TRACE_ERROR((_TX("Not implement: %s\n"), category.c_str()));
        result = BEE_ERROR;
    }
    return result;
}


//-----------------------------------------------------------------------------
// Check Definition of Variable Limit Attributes (S2F45)
//-----------------------------------------------------------------------------
int JGLimitManager::checkLimitAttributes(BS2ListItem * baselist,
                                         JGLimitArray& liminfo)
{
    TRACE_FUNCTION(TRL_LOW, "JGLimitManager::checkLimitAttributes");
    int result = BEE_SUCCESS;
    JGLimit * curlim;

    // Make ack data
    for (size_t i = 0; i < liminfo.size(); i++)
    {
        // Get limit information
        curlim = this->find(liminfo[i].vid());
        if (curlim != NULL)
        {   // Found same vid's limit information
            if (curlim->m_defined)
            {   // Variable repeated
                addNackItem(baselist, liminfo[i].vid(),
                            LVACK_VARIABLE_REPEATED, NULL);
                result = BEE_ERROR;
                continue;
            }
            curlim->defined(true);

            BS2ListItem * limitack = curlim->checkNewBorders(liminfo[i]);
            if (limitack != NULL)
            {   // Limit value error
                addNackItem(baselist, liminfo[i].vid(),
                            LVACK_LIMIT_VALUE_ERROR, limitack);
                result = BEE_ERROR;
                continue;
            }
            //
            // Good
            //
        }
        else
        {   // Variable does not exist
            if (m_equipment->variable(liminfo[i].vid()) == NULL)
            {   // The variable is not for limit
                addNackItem(baselist, liminfo[i].vid(),
                            LVACK_VARIABLE_NOT_EXIST, NULL);
                result = BEE_ERROR;
                continue;
            }
            else
            {
                JGLimit * valid = getValidLimit(liminfo[i].vid());
                if (valid == NULL)
                {   // No capability
                    addNackItem(baselist, liminfo[i].vid(),
                                LVACK_NO_LIMIT_CAPABILITY, NULL);
                    result = BEE_ERROR;
                }
            }
        }
    }
    return result;
}

//-----------------------------------------------------------------------------
// Update Limit Attributes (S2F45)
//-----------------------------------------------------------------------------
int JGLimitManager::updateLimitAttributes(JGLimitArray& liminfo)
{
    TRACE_FUNCTION(TRL_LOW, "JGLimitManager::updateLimitAttributes");
    JGLimit * curlim;

    // Update Variables
    for (size_t i = 0; i < liminfo.size(); i++)
    {
        if (liminfo[i].m_borders.size() == 0)
        {   // No Limit Attributes
            this->remove(liminfo[i].vid());  // Disable limit supervisor
            continue;
        }

        // Get limit information
        curlim = this->find(liminfo[i].vid());
        if (curlim == NULL)
        {
            curlim = this->entry(liminfo[i].vid());
            if (curlim == NULL)
            {
                TRACE_ERROR((_TX("Variable not found (%s)\n"),
                             liminfo[i].vid().toString().c_str()));
                return BEE_ERROR;
            }
        }

        // Found same vid's limit information
        curlim->defined(false);           // Reset defining

        curlim->updateNewBorders(liminfo[i]);
        curlim->enabled(true);
    }
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Define Variable Limit Attributes : S2F45 --> S2F46
//-----------------------------------------------------------------------------
int JGLimitManager::parseS2F45(void * clientData, BS2Item * item)
{
    TRACE_FUNCTION(TRL_LOW, "JGLimitManager::parseS2F45");
    JGLimitArray * limits = (JGLimitArray *)clientData;
    if (item->isList())
    {   // Ignore list item
        return 0;
    }

    BS2Atom * atom = item->atom();
    if (item->name() == _TX("VID"))
    {
        JGid vid;
        atom->get(vid);
        JGLimit limit(vid);               // Assign new limit
        limits->push_back(limit);
    }
    else if (item->name() == _TX("LIMITID"))
    {
        if (! atom->isBinary())
        {
            TRACE_ERROR((_TX("Unexpect limit-id format\n")));
            return -1;
        }
        BYTE * idptr = ((BS2Binary *)atom)->value();
        int limid = *idptr;
                                          // Assign new limit border
        JGLimitBorder lim_border(limits->back().vid(), limid);
        limits->back().m_borders.push_back(lim_border);
    }
    else if (item->name() == _TX("UPPERDB"))
    {
        JGvalue upval(*atom);
        limits->back().m_borders.back().upperdb(upval);
        limits->back().m_borders.back().enabled(true);
    }
    else if (item->name() == _TX("LOWERDB"))
    {
        JGvalue lowval(*atom);
        limits->back().m_borders.back().lowerdb(lowval);
        limits->back().m_borders.back().enabled(true);
    }
    else if (item->name() == _TX("DATAID"))
    {
        ;          // Ignore
    }
    return 0;
}

//-----------------------------------------------------------------------------
//
BS2Message * JGLimitManager::defineLimit(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "JGLimitManager::defineLimit");

    BS2ListItem * rootlist = new BS2ListItem;
    BS2Message * replymsg;
    BS2Item * item;
    JGLimit * curlim;
    BYTE vlaack;
    JGLimitArray liminfo;
    int result = msg->traverse(JGLimitManager::parseS2F45, &liminfo); // ***
    if (result < 0)
    {
        item = BS2Item::factory(_TX("VLAACK"), new BS2Binary((BYTE)ACK_NG));
        rootlist->add(item);
        replymsg = BS2Message::response(msg);
        replymsg->add(rootlist);
        return replymsg;
    }

    ACE_Guard<ACE_Thread_Mutex> limit_mon(this->m_lock);
    while (1)
    {
        if (limit_mon.locked () != 0)
            break;
        ACE_Time_Value tv(0, 300000);
        ACE_OS::sleep(tv);  // collision
    }

    BS2ListItem * baselist = new BS2ListItem;  // List of ignore parameter

    // Reset all define flag in limit table
    JGLimitTable::iterator iter;
    for (iter = m_limits.begin(); iter != m_limits.end(); iter++)
    {
         curlim = &((*iter).second);
         curlim->defined(false);
    }

    // Update limit table by message
    if (liminfo.size() == 0)
    {   // No invalid valiable limit attributes
        // LIMIT Lock   <<<<<<
        m_limits.erase(m_limits.begin(), m_limits.end());  // Clear all limit
        // LIMIT Unlock >>>>>>
        vlaack = ACK_OK;
    }
    else
    {   // Check and Copy Limt Information
        int result = checkLimitAttributes(baselist, liminfo);
        if (result == BEE_SUCCESS)
        {
            // LIMIT Lock   <<<<<<
            result = updateLimitAttributes(liminfo);    // Update limit borders
            // LIMIT Unlock >>>>>>
            vlaack = (result >= 0) ? ACK_OK : ACK_NG;
        }
        else
        {   // Replace ack code
            vlaack = ACK_NG; // Define error
        }
    }

    // Create reply message.
    item = BS2Item::factory(_TX("VLAACK"), new BS2Binary(vlaack));
    rootlist->add(item);
    rootlist->add(baselist);
    replymsg = BS2Message::response(msg);
    replymsg->add(rootlist);
    return replymsg;
}

//-----------------------------------------------------------------------------
// Variable Limit Attributes Request : S2F47 --> S2F48
//-----------------------------------------------------------------------------
BS2Message * JGLimitManager::requestLimit(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "JGLimutManager::requestLimit");

    BS2Message * replymsg;
    JGLimit * curlim;
    vector<JGid> vids;
    int result = msg->getId(_TX("VID"), vids);    // Parse S2F47
    if (result < 0)
    {
        TRACE_ERROR((_TX("Received illegal message (%s).\n"),
                     msg->charName()));
        replymsg = new BS2S9F7Message(msg);
        return replymsg;
    }

    ACE_Guard<ACE_Thread_Mutex> limit_mon(this->m_lock);
    while (1)
    {
        if (limit_mon.locked () != 0)
            break;
        ACE_Time_Value tv(0, 500000);
        ACE_OS::sleep(tv);  // collision
    }

    BS2Item * item;
    BS2ListItem * rootlist = new BS2ListItem;
    if (result == 0)
    {   // Response limit attributes of all variables
        JGLimitTable::iterator lim_iter = m_limits.begin();
        for ( ; lim_iter != m_limits.end(); lim_iter++)
        {
            curlim = &((*lim_iter).second);
            item = curlim->attributeItem();
            rootlist->add(item);
        }
    }
    else
    {   // Response limit attributes
        for (size_t i = 0; i < vids.size(); i++)
        {
            curlim = find(vids[i]);
            if (curlim == NULL)
            {
                continue;
            }
            item = curlim->attributeItem();
            rootlist->add(item);
        }
    }

    // Create reply message.
    replymsg = BS2Message::response(msg);
    BS2Assert(replymsg != NULL);
    replymsg->add(rootlist);

    return replymsg;
}

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

    if (msg->sf() == SFCODE(2,45))
    {
        replymsg = defineLimit(msg);
    }
    else if (msg->sf() == SFCODE(2,47))
    {
        replymsg = requestLimit(msg);
    }
    else
    {   // Unexpected message
        replymsg = this->unrecognized(msg);
    }

    return replymsg;
}


//-----------------------------------------------------------------------------
// Thread of received message .
//-----------------------------------------------------------------------------
int JGLimitManager::device_svc(JGDeviceTrigger * trigger)
{
    JGDeviceTrigger * notify = trigger;
    if (notify->name() == TRG_TRANSIT_ZONE)
    {
        JGVariable * var = notify->variable();
        m_equipment->sendEvent(this, EVT_LIMIT, var);
    }
    else
    {
        ACE_ERROR((LM_ERROR,
            ACE_TEXT("(%t) %s manager is not support notification (%s).\n"),
            this->charName(), notify->charName()));
    }

    return BEE_SUCCESS;
}


