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

//=============================================================================
/**
 *  @file    JGAccessManager.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 "JGAccessManager.h"
#include "JGEquipment.h"
#include "JGControlManager.h"
#include "JGEnumSub.h"
#include "JGTaskTrigger.h"
#include "JGErrorList.h"
#include "JGVariable.h"
#include "JGRemoteCmd.h"
#include "BS2Message.h"
#include "BS2ListItem.h"
#include "BS2DeclAtoms.h"

enum   // CPACK
{
    ILLEGAL_CPNAME = 1, ILLEGAL_VALUE = 2,
    ILLEGAL_FORMAT = 3, NAME_INVALID_USED = 4
};

enum  // HCACK : Remote Command Acknowledge
{
    ILLEGAL_RCMD = 1,  CANNOT_PERFORM = 2,    INVALID_PARAMETER = 3,
    PERFORM_LATER = 4, DESIRED_CONDITION = 5, NOT_EXIST_OBJECT = 6
};

//
// Operator Command Event Code
//
struct CommandToCode
{
    BCHAR * m_cmdName;
    int     m_cmdCode;
};

static CommandToCode _cmd2code[] =
{
    { _TX("UNKNOWN"), 0 },
    { _TX("START"),   1 },
    { _TX("STOP"),    2 },
    { _TX("ABORT"),   3 },
    { _TX("PAUSE"),   4 },
    { _TX("RESUME"),  5 },
    { _TX("SELECT"),  6 },
    { _TX("CHANGE"),  7 },
    { _TX("EQUIPMENT_CONSTANT"), 8 },
    { _TX("LOGIN"),   9 },
    { _TX("LOGOUT"),  10 },
    { NULL, -1}
};

static int _parseArguments(const string& argStr, vector<string>& argv)
{
    int    current = argStr.find_first_not_of(_TX(" "));
    size_t delimit = argStr.find_first_of(_TX(" "), current);
    while (delimit != argStr.npos)
    {
        argv.push_back(argStr.substr(current, delimit - 1));
        current = argStr.find_first_not_of(_TX(" "), delimit + 1);
        delimit = argStr.find_first_of(_TX(" "), current);
    }
    argv.push_back(argStr.substr(current));
    return 0;
}

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

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

//-----------------------------------------------------------------------------
// Constructor/Destoructor
//-----------------------------------------------------------------------------
JGAccessManager::JGAccessManager() : JGManager(CATEGORY_ACCESS)
{
    TRACE_FUNCTION(TRL_LOW, "JGAccessManager::JGAccessManager");
}


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

    m_opecmd = m_equipment->variable(VAR_OPERATOR_COMMAND);
    m_remoteCmd = m_equipment->variable(VAR_REMOTE_COMMAND);
    m_localCmd = m_equipment->variable(VAR_LOCAL_COMMAND);
    m_indicator = m_equipment->variable(VAR_COMMAND_INDICATOR);

    // Make converter that is command string to code. (Read infomation from DB !)
    int i = 0;
    while (_cmd2code[i].m_cmdName != NULL)
    {
        string name = _cmd2code[i].m_cmdName;
        JGRemoteCmd * rcmd = new JGRemoteCmd(name, _cmd2code[i].m_cmdCode, _TX(""));
        m_rcmdTable.insert(JGRemoteCmdPair(name, rcmd));
        i++;
    }

    return BEE_SUCCESS;
}

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

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

    if (category == TRG_OPERATOR_COMMAND)
    {   // make trigger from command variable.
        JGOperatorRequest * opeReq =
                new JGOperatorRequest(category, m_localCmd->getv().toString(),
                                      this);
        this->put(opeReq);
    }
    else
    {
        TRACE_ERROR((_TX("Not implement: %s\n"), category.c_str()));
        result = BEE_ERROR;
    }
    JGvalue initval((USHORT)0);
    m_indicator->setv(initval);

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Request command.
//-----------------------------------------------------------------------------
int JGAccessManager::notify(const string& objspec, const string& cmd,
                            const string& param)
{
    TRACE_FUNCTION(TRL_LOW, "JGAccessManager::notify");
    string arg = objspec;
    arg += _TX(" ");
    arg += cmd;
    arg += _TX(" ");
    arg += param;

    JGOperatorRequest * opeReq = new JGOperatorRequest(TRG_OPERATOR_COMMAND,
                                                       arg, this);
    this->put(opeReq);

    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Validate command parameters.
//-----------------------------------------------------------------------------
int JGAccessManager::validateParams(JGService * rcmd, JGErrorList& errs)
{
    TRACE_FUNCTION(TRL_LOW, "JGAccessManager::validateParams");

    TRACE_ERROR((_TX("Not support, yet.\n")));
    return 0;
    //
    //
    //

    JGRemoteCmdTable::iterator iter = m_rcmdTable.find(rcmd->name());
    if (iter == m_rcmdTable.end())
    {
        errs.ack(ILLEGAL_RCMD);
        return -1;
    }
    JGRemoteCmd * valid = (*iter).second;

    for (size_t i = 0; i < rcmd->m_params.size(); i++)
    {
        CmdParam * param = valid->find(rcmd->m_params[i].m_pname);
        if (param == NULL)
        {
            errs.append(ILLEGAL_CPNAME, rcmd->m_params[i].m_pname);
            if (errs.ack() == 0)
            {
                errs.ack(INVALID_PARAMETER);
            }
        }
        if (rcmd->m_params[i].m_pval.format() != param->m_cpval.format())
        {
            errs.append(ILLEGAL_FORMAT, rcmd->m_params[i].m_pname);
            if (errs.ack() == 0)
            {
                errs.ack(INVALID_PARAMETER);
            }
        }
        //
        // Check value range
        //
    }

    return ((errs.ack() == 0) ? BEE_SUCCESS : BEE_ERROR);
}

//-----------------------------------------------------------------------------
// Host Command Send : S2F41 --> S2F42
//-----------------------------------------------------------------------------
class HostCommandParam : public BS2Traverser
{
public:
    HostCommandParam(int cpack = ATOM_BINARY)
            : BS2Traverser(), m_service(NULL),
              m_errs(_TX("HCACK"), ATOM_BINARY, cpack) {
        }
    virtual ~HostCommandParam() {}

    JGService * m_service;
    JGErrorList m_errs;
    string  m_cpname;
    JGvalue m_cpval;

    int parseItem(BS2Item * item)
    {
        TRACE_FUNCTION(TRL_LOW, "JGAccessManager::HostCommandParam::parseItem");

        BS2Atom * atom = item->atom();
        if (item->name() == _TX("CPNAME"))
        {
            if (! atom->isAscii())
            {
                TRACE_ERROR((_TX("Illegal remote command format(CPNAME) \n")));
                m_errs.append(ILLEGAL_FORMAT, atom->toString().c_str());
                m_errs.ack(INVALID_PARAMETER);
                return 0;
            }
            m_cpname = ((BS2Ascii *)atom)->value();
        }
        else if (item->name() == _TX("CPVAL"))
        {
            if (m_cpname == _TX("ObjSpec"))
            {
                if (atom->isAscii())
                {
                    m_service->to(((BS2Ascii *)atom)->value());
                }
                else
                {
                    TRACE_ERROR((_TX("Illegal remote command format(CPVAL.ObjSpec) \n")));
                    m_errs.append(ILLEGAL_FORMAT, _TX("ObjSpec"));
                    m_errs.ack(INVALID_PARAMETER);
                    return 0;
                }
            }
            else
            {
                m_cpval = *atom;
                m_service->add(m_cpname, m_cpval);
            }
        }
        else if (item->name() == _TX("RCMD"))
        {
            if (atom->isAscii())
            {
                m_service = new JGService(((BS2Ascii *)atom)->value());
            }
            else
            {
                TRACE_ERROR((_TX("Illegal remote command format(RCMD) \n")));
                m_errs.ack(ILLEGAL_RCMD);
            }
        }
        return 0;
    }
};

//-----------------------------------------------------------------------------
BS2Message * JGAccessManager::hostCommand(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "JGAccessManager::hostCommand");
    BS2Message * replymsg = BS2Message::response(msg);

    HostCommandParam rcmd;
    int result = msg->traverse(&rcmd);
    if (result < 0 || rcmd.m_errs.ack() != 0)
    {
        if (rcmd.m_service != NULL)
        {
            delete rcmd.m_service;
        }
        replymsg->add(rcmd.m_errs.makeItem());
        return replymsg;
    }

    // Get Manager by RCMD
    if (rcmd.m_service->name() == TRG_CONTROL_REMOTE ||
        rcmd.m_service->name() == TRG_CONTROL_LOCAL)
    {
        rcmd.m_service->to(CATEGORY_CONTROL);
        // Change trigger name
        if (rcmd.m_service->name() == _TX("REMOTE"))
        {
            rcmd.m_service->name(TRIGGER_ONLINE_REMOTE);
        }
        else
        {
            rcmd.m_service->name(TRIGGER_ONLINE_REMOTE);
        }
    }

    //
    // Check Parameters
    //
    result = validateParams(rcmd.m_service, rcmd.m_errs);
    if (result < 0)
    {
        delete rcmd.m_service;
        replymsg->add(rcmd.m_errs.makeItem());
        return replymsg;
    }

    //
    // Issue command/service
    //
    rcmd.m_service->from(this->name());
    JGManager * manager = m_equipment->findManager(rcmd.m_service->to());
    if (manager != NULL)
    {
        result = manager->notify(rcmd.m_service);
        // Don't delete service
        rcmd.m_errs.ack(result);
    }
    else
    {
        TRACE_ERROR((_TX("Illegal remote command (category not found: %s).\n"),
                     rcmd.m_service->to().c_str()));
        rcmd.m_errs.ack(NOT_EXIST_OBJECT);
        delete rcmd.m_service;
        replymsg->add(rcmd.m_errs.makeItem());
        return replymsg;
    }

    replymsg->add(rcmd.m_errs.makeItem());
    return replymsg;
}


//-----------------------------------------------------------------------------
// Host Command Send : S2F49 --> S2F50
//-----------------------------------------------------------------------------
class EnhancedHostCommandParam : public HostCommandParam
{
public:
    EnhancedHostCommandParam() : HostCommandParam(ATOM_UINT1) {}
    virtual ~EnhancedHostCommandParam() {}

    JGid  m_dataid;

    int parseItem(BS2Item * item)
    {
        TRACE_FUNCTION(TRL_LOW, "JGAccessManager::EnhancedHostCommandParam::parseItem");

        BS2Atom * atom = item->atom();
        if (item->name() == _TX("CPNAME"))
        {
            if (atom->isAscii())
            {
                m_cpname = ((BS2Ascii *)atom)->value();
            }
            else
            {
                TRACE_ERROR((_TX("Illegal remote command format(CPNAME) \n")));
                m_errs.append(ILLEGAL_FORMAT, atom->toString().c_str());
                m_errs.ack(INVALID_PARAMETER);
            }
        }
        else if (item->name() == _TX("CEPVAL"))
        {
            m_cpval = *atom;
            m_service->add(m_cpname, m_cpval);
        }
        else if (item->name() == _TX("RCMD"))
        {
            if (atom->isAscii())
            {
                m_service = new JGService(((BS2Ascii *)atom)->value());
            }
            else
            {
                TRACE_ERROR((_TX("Illegal remote command format(RCMD) \n")));
                m_errs.ack(ILLEGAL_RCMD);
            }
        }
        else if (item->name() == _TX("OBJSPEC"))
        {
            if (atom->isAscii())
            {
                m_service->to(((BS2Ascii *)atom)->value());
            }
            else
            {
                TRACE_ERROR((_TX("Illegal remote command format(OBJSPEC) \n")));
                m_errs.ack(NOT_EXIST_OBJECT);
                m_errs.append(ILLEGAL_FORMAT, _TX("OBJSPEC"));
            }
        }
        else if (item->name() == _TX("DATAID"))
        {
            if (atom->isAscii())
            {
                m_dataid = ((BS2Ascii *)atom)->value();
            }
            else
            {
                m_dataid = atom->getInt();
            }
        }
        return 0;
    }
};

//-----------------------------------------------------------------------------
BS2Message * JGAccessManager::enhancedRemoteCommand(BS2Message * msg)
{
    TRACE_FUNCTION(TRL_LOW, "JGAccessManager::enhancedRemoteCommand");
    BS2Message * replymsg = BS2Message::response(msg);

    EnhancedHostCommandParam rcmd;
    int result = msg->traverse(&rcmd);
    if (result < 0 || rcmd.m_errs.ack() != 0)
    {
        if (rcmd.m_service != NULL)
        {
            delete rcmd.m_service;
        }
        replymsg->add(rcmd.m_errs.makeItem());
        return replymsg;
    }

    //
    // Check Parameters
    //
    result = validateParams(rcmd.m_service, rcmd.m_errs);
    if (result < 0)
    {
        delete rcmd.m_service;
        replymsg->add(rcmd.m_errs.makeItem());
        return replymsg;
    }

    //
    // Issue command/service
    //
    rcmd.m_service->from(this->name());
    JGManager * manager = m_equipment->findManager(rcmd.m_service->to());
    if (manager != NULL)
    {
        result = manager->notify(rcmd.m_service);
        // Don't delete service
        rcmd.m_errs.ack(result);
    }
    else
    {
        TRACE_ERROR((_TX("Illegal remote command (category not found: %s).\n"),
                     rcmd.m_service->to().c_str()));
        rcmd.m_errs.ack(NOT_EXIST_OBJECT);
        delete rcmd.m_service;
        replymsg->add(rcmd.m_errs.makeItem());
        return replymsg;
    }

    replymsg->add(rcmd.m_errs.makeItem());
    return replymsg;
}


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

    if (msg->sf() == SFCODE(2,41))
    {
        replymsg = hostCommand(msg);
    }
    else if (msg->sf() == SFCODE(2,49))
    {
        replymsg = enhancedRemoteCommand(msg);
    }
    else
    {   // Unexpected message
        replymsg = this->unrecognized(msg);
    }

    return replymsg;
}


//-----------------------------------------------------------------------------
//
// Service of operator command.
//
//-----------------------------------------------------------------------------
int JGAccessManager::ope_svc(JGOperatorTrigger * trigger)
{
    int result = BEE_SUCCESS;

    const string& ope = trigger->name();
    if (ope == TRG_OPERATOR_COMMAND)
    {
        //
        // Parse command line: "objspec" "command" "parameters ...."
        //
        vector<string> args;
        result = _parseArguments(trigger->arg(), args);
        if (result < 0 || args.size() < 2)
        {
            ;
            // Set error response
            return BEE_ERROR;
        }

        // Convert command string to code
        JGRemoteCmdTable::iterator iter = m_rcmdTable.find(args[1]);
        if (iter == m_rcmdTable.end())
        {
            ;
            // Set error response
            return BEE_ERROR;
        }
        JGRemoteCmd * rcmd = (*iter).second;
        USHORT  code = rcmd->m_cmdcode;
        JGvalue cmdval(code);
        m_opecmd->setv(cmdval);    // Set 'OperatorCommand' DVVAL

        // Send S6F11
        m_equipment->sendEvent(this, EVT_OPERATOR_COMMAND);

        //
        // args[0] is object specification, but it is manager name now
        //
        JGManager * target = m_equipment->findManager(args[0]);
        if (target != NULL)
        {
            JGService * rcmd = new JGService(args[1], args[0], this->name());
            target->notify(rcmd);
        }
        else
        {
            ACE_ERROR((LM_ERROR,
                    ACE_TEXT("Manager to issue command is not found (%s.%s(...))\n"),
                            args[0].c_str(), args[1].c_str()));
        }
    }
    return result;
}


