//Editor-Info: -*- C++ -*-
//
//Subject: TOVE project
//
//File: gsmpprotocol.cpp
//
//Version: $Revision: 1.15 $
//
//State: $State: Exp $
//
//Date: $Date: 1998/12/16 16:57:24 $
//
//Organisation:
//      Helsinki University of Technology
//      Laboratory of Telecommunications Software and Multimedia
//
//Author:
//      Harri Sunila.
//
//Description:
//      See corresponding header file.
//
//Copyright:
//      Copyright 1999 Helsinki University of Technology
//      ALL RIGHTS RESERVED BETWEEN JANUARY 1996 AND JUNE 1999.
//
//Licence:
//
//
//History: 

#include "gsmpprotocol.h"
#include "gsmpidlestate.h"
#include "gsmpactivestate.h"
#include "gsmpconfigurationmessages.h"
#include "gsmpconnectionmanagementmessages.h"
#include "gsmpportmanagementmessages.h"
#include "gsmpstatisticsmessages.h"
#include "gsmpeventmessages.h"
#include "gsmpexceptions.h"
#include "pf/error.h"

#include "pf/debug.h"

//
// Class: gsmpProtocol
//
// Description: 
//

pfUlong gsmpProtocol::_transactionIdentifier = 0;

//
// Function: create 
//
// Description:
//     A static method to create a new protocol
//

pfConduit gsmpProtocol :: create(void)
{
    gsmpProtocol *protocol = new gsmpProtocol;
    pfConduit conduit(protocol);
    return conduit;
}

//
// Function: gsmpProtocol 
//
// Description:
//     Constructor
//

gsmpProtocol :: gsmpProtocol(void)
    : pfProtocol(),
      _proxyMap(),
      _eventProxyMap(),
      _timerMap(),
      _portSessionNumberMap(),
      _responseTimeout(3000)
{
    toGsmpIdleState();
    return;
}

//
// Function: gsmpProtocol
//
// Description:
//     Copy constructor
//

gsmpProtocol :: gsmpProtocol(const gsmpProtocol &other_)
    : pfProtocol(other_),
      _proxyMap(other_._proxyMap),
      _eventProxyMap(other_._eventProxyMap),
      _timerMap(other_._timerMap),
      _portSessionNumberMap(other_._portSessionNumberMap),
      _responseTimeout(other_._responseTimeout)
{
    toGsmpIdleState();
    return;
}

//
// Function: ~gsmpProtocol
//
// Description:  
//     Destructor
//

gsmpProtocol :: ~gsmpProtocol(void)
{
    return;
}

//
// Function: clone
//
// Description:  
//

pfProtocol *gsmpProtocol :: cloneImplementation(void)
{
    pfProtocol *newProtocol = new gsmpProtocol(*this);
    return newProtocol;
}

//
// Function: accept
//
// Description:  
//     Define GSMP to process transporters synchronously.  
//

void gsmpProtocol :: accept(pfTransporter *transporter_)
{
    pfConduit proxy;
    pfMsgTransporter *transporter = 0;
    if (transporter_->isSender(_sideA))
    {
        transporter_->setSenderIsA();
    }
    else
    {
        // The transporter is from some not connected conduit (a user of GSMP)
        transporter = dynamic_cast<pfMsgTransporter *>(transporter_);
        if (transporter != 0)
        {
            proxy = transporter->_senderConduit;
            
            // Assign the transaction identifier for the messenger
            gsmpMessage *message = dynamic_cast<gsmpMessage *>(
                transporter->_messenger);
            pfUlong transactionIdentifier;
            if (message != 0)
            {
                // messenger is a gsmpMessage instead of
                // gsmpResponseTimeout
                transactionIdentifier = message->getTransactionIdentifier();
                addProxy(transactionIdentifier, proxy);
            }
        }
    }
    acceptSynchronous(transporter_);
    return;
}

//
// Function: gsmpResponseTimeoutOccured 
//
// Description:  
//     A timer in the _timerMap has expired and a failure response message
//     has to be generated and send to some user of GSMP.
//

void gsmpProtocol :: gsmpResponseTimeoutOccured(
    pfUlong transactionIdentifier_)
{
    pfByte messageType;
    pfUlong function;
    gsmpMessage *message = 0;
    timerIterator i = _timerMap.find(transactionIdentifier_);
    if (i != _timerMap.end())
    {
        (*i).second.stopTimer();
        messageType = (*i).second.getMessageType();
        switch (messageType)
        {
            case gsmpConnectionManagementMessage::ADD_BRANCH:
                message = new gsmpAddBranchMessage;
                break;
            case gsmpConnectionManagementMessage::DELETE_BRANCH:
                message = new gsmpDeleteBranchMessage;
                break;
            case gsmpConnectionManagementMessage::DELETE_TREE:
                message = new gsmpDeleteTreeMessage;
                break;
            case gsmpConnectionManagementMessage::VERIFY_TREE:
                message = new gsmpVerifyTreeMessage;
                break;
            case gsmpConnectionManagementMessage::DELETE_ALL:
                message = new gsmpDeleteAllMessage;
                break;
            case gsmpConnectionManagementMessage::MOVE_BRANCH:
                message = new gsmpMoveBranchMessage;
                break;
            case gsmpPortManagementMessage::PORT_MANAGEMENT:
                function = (*i).second.getFunction();
                switch (function)
                {
                    case gsmpPortManagementMessage::BRING_UP:
                        message = new gsmpBringUpMessage;
                        break;
                    case gsmpPortManagementMessage::TAKE_DOWN:
                        message = new gsmpTakeDownMessage;
                        break;
                    case gsmpPortManagementMessage::INTERNAL_LOOPBACK:
                        message = new gsmpInternalLoopbackMessage;
                        break;
                    case gsmpPortManagementMessage::EXTERNAL_LOOPBACK:
                        message = new gsmpExternalLoopbackMessage;
                        break;
                    case gsmpPortManagementMessage::BOTHWAY_LOOPBACK:
                        message = new gsmpBothwayLoopbackMessage;
                        break;
                    case gsmpPortManagementMessage::RESET_INPUT_PORT:
                        message = new gsmpResetInputPortMessage;
                        break;
                    case gsmpPortManagementMessage::RESET_EVENT_FLAGS:
                        message = new gsmpResetEventFlagsMessage;
                        break;
                    default:
                        break;
                }
                break;
            case gsmpMessage::VC_ACTIVITY:
                message = new gsmpVCActivityResponseMessage;
                break;
            case gsmpMessage::PORT_STATISTICS:
                message = new gsmpPortStatisticsResponseMessage;
                break;
            case gsmpMessage::VC_STATISTICS:
                message = new gsmpVCStatisticsResponseMessage;
                break;
            case gsmpMessage::SWITCH_CONFIGURATION:
                message = new gsmpSwitchConfigurationResponseMessage;
                break;
            case gsmpMessage::PORT_CONFIGURATION:
                message = new gsmpPortConfigurationResponseMessage;
                break;
            case gsmpMessage::ALL_PORTS_CONFIGURATION:
                message = new gsmpAllPortsConfigurationResponseMessage;
                break;
            default:
                break;
        }
        if (message != 0)
        {
            message->setResult(gsmpMessage::GSMP_FAILURE);
            message->setCode(gsmpMessage::GSMP_E_UNSPECIFIED);
            message->setTransactionIdentifier(transactionIdentifier_);
            acceptResponse(transactionIdentifier_,
                           message);
        }
        _timerMap.erase(i);
    }
    return;
}

//
// Function: toGsmpIdleState
//
// Description:
//     Change state to idle state. If the Adjacency Protocol is not in ESTAB
//     state, GSMP must be idle.
//

void gsmpProtocol :: toGsmpIdleState(void)
{
    changeState(gsmpIdleState::instance());
    return;
}

//
// Function: toGsmpActiveState
//
// Description:
//     Change state to active state. The Adjacency Protocol is in ESTAB
//     state and GSMP may send requests. GSMP sends also a GSMP All Ports
//     Configuration Request to get the current configuration of all ports
//     in the switch.
//

void gsmpProtocol :: toGsmpActiveState(void)
{
    changeState(gsmpActiveState::instance());
    sendGsmpAllPortsConfigurationRequestMessage();
    return;
}

//
// Function: acceptResponse 
//
// Description:
//     Call accept() to the sender specified by transactionIdentifier.
//

void gsmpProtocol :: acceptResponse(pfUlong transactionIdentifier_,
                                    gsmpMessage *message_)
{
    conduitIterator i = _proxyMap.find(transactionIdentifier_);
    if (i != _proxyMap.end())
    {
        pfMsgTransporter *transporter = pfMsgTransporter::createMsgTransporter(
            message_);
        pfConduit proxy = (*i).second;
        proxy.accept(transporter);
    }
    removeProxy(transactionIdentifier_);
    return;
}

//
// Function: acceptEvent 
//
// Description:
//     Call accept() to every proxy in the event proxy map.
//

void gsmpProtocol :: acceptEvent(gsmpMessage *message_)
{
    pfConduit proxy;
    conduitIterator i = _eventProxyMap.begin();
    conduitIterator end = _eventProxyMap.end();
    pfMsgTransporter *transporter;
    while (i != end)
    {
        // The old message can be delivered further because its contents won't
        // change. Just send it to the proxy
        transporter = pfMsgTransporter::createMsgTransporter(message_);
        proxy = (*i).second;
        proxy.accept(transporter);
        i++;
    }
    return;
}

//
// Function: generateTransactionIdentifier
//
// Description:  
//

pfUlong gsmpProtocol :: generateTransactionIdentifier(void)
{
    if (_transactionIdentifier == 0xFFFFFFFF)
    {
        _transactionIdentifier = 0;
    }
    _transactionIdentifier++;
    return _transactionIdentifier;
}

//
// Function: addProxy
//
// Description:
//     Add the proxy of the corresponding request to the _proxyMap. The proxy
//     may accept the response as soon as it arrives.
//

void gsmpProtocol :: addProxy(pfUlong transactionIdentifier_,
                              pfConduit &proxy_)
{
    _proxyMap.insert(conduitValue(transactionIdentifier_, proxy_));
    return;
}

//
// Function: removeProxy
//
// Description:
//     Remove the proxy from the _proxyMap.
//

void gsmpProtocol :: removeProxy(pfUlong transactionIdentifier_)
{
    conduitIterator i = _proxyMap.find(transactionIdentifier_);
    if (i != _proxyMap.end())
    {
        // Remove the old reference and the map item
        pfConduit nilConduit;
        (*i).second = nilConduit;
        _proxyMap.erase(i);
    }
    return;
}

//
// Function: addTimer 
//
// Description:
//     Add a timer in the _timerMap. If the timer expires, it generates a
//     correct failure response since a response from the switch has not been
//     received.
//

void gsmpProtocol :: addTimer(pfUlong transactionIdentifier_,
                              pfByte messageType_,
                              pfUlong function_)
{
    pfTimer responseTimer;
    gsmpResponseTimeout *timeout = new gsmpResponseTimeout(
        transactionIdentifier_);
    responseTimer.setMessenger(timeout);
    responseTimer.setTimeout(_responseTimeout);
    responseTimer.setHost(this);
    
    gsmpTimerMapValue timerMapValue(responseTimer, messageType_, function_);
    _timerMap.insert(timerValue(transactionIdentifier_, timerMapValue));
    _timerMap[transactionIdentifier_].startTimer();
    return;
}

//
// Function: removeTimer 
//
// Description:  
//

void gsmpProtocol :: removeTimer(pfUlong transactionIdentifier_)
{
    timerIterator i = _timerMap.find(transactionIdentifier_);
    if (i != _timerMap.end())
    {
        (*i).second.stopTimer();
        _timerMap.erase(i);
    }
    return;
}

//
// Function: getPortSessionNumber
//
// Description:  
//     Return port session number for specified port. Return 0 if the
//     specified port does not exist.
//
                     
pfUlong gsmpProtocol :: getPortSessionNumber(pfUlong portNumber_) const
{
    pfUlong portSessionNumber = 0;
    integerMap::const_iterator i = _portSessionNumberMap.find(portNumber_);
    if (i != _portSessionNumberMap.end())
    {
        portSessionNumber = (*i).second;
    }
    return portSessionNumber;
}

//
// Function: setPortSessionNumber
//
// Description:  
//

void gsmpProtocol :: setPortSessionNumber(pfUlong portNumber_,
                                          pfUlong portSessionNumber_)
{
    _portSessionNumberMap.insert(
        integerValue(portNumber_, portSessionNumber_));
    return;
}

//
// Function: setPortSessionNumber
//
// Description:  
//

void gsmpProtocol :: removePortSessionNumber(pfUlong portNumber_)
{
    _portSessionNumberMap.erase(portNumber_);
    return;
}

//
// Function: sendGsmpPortConfigurationRequestMessage
//
// Description:  
//     GSMP uses this method to update its interal port data.
//

void gsmpProtocol :: sendGsmpPortConfigurationRequestMessage(pfUlong port_)
{
    pfUlong transactionIdentifier = generateTransactionIdentifier();
    gsmpPortConfigurationRequestMessage *message =
        new gsmpPortConfigurationRequestMessage(transactionIdentifier,
                                                port_);
    toA(message);
    return;
}

//
// Function: sendGsmpAllPortsConfigurationRequestMessage
//
// Description:  
//     GSMP uses this method to get the initial configuration of the ports
//     in the switch.  
//

void gsmpProtocol :: sendGsmpAllPortsConfigurationRequestMessage(void)
{
    pfUlong transactionIdentifier = generateTransactionIdentifier();
    gsmpAllPortsConfigurationRequestMessage *message =
        new gsmpAllPortsConfigurationRequestMessage(transactionIdentifier);
    toA(message);
    return;
}

//
// Function: registerEventListener
//
// Description:  
//     Register a conduit to receive events.
//

void gsmpProtocol :: registerEventListener(pfUlong transactionIdentifier_)
{
    // Find the conduit from the _proxyMap
    conduitIterator i = _proxyMap.find(transactionIdentifier_);
    if (i != _proxyMap.end())
    {
        pfConduit proxy = (*i).second;
        
        // Insert the conduit in the _eventProxyMap
        _eventProxyMap.insert(conduitValue(transactionIdentifier_, proxy));
        
        // Remove the reference from the _proxyMap, since _proxyMap is for
        // conduits waiting response messengers for some requests.
        pfConduit nilConduit;
        (*i).second = nilConduit;
        _proxyMap.erase(i);
    }
    return;
}

//
// Function: unRegisterEventListener
//
// Description:
//     Remove a conduit from receiving events
//

void gsmpProtocol :: unRegisterEventListener(gsmpForgetEventsMessage *message_)
{
    pfUlong transactionIdentifier = message_->getTransactionIdentifier();

    // Find the conduit waiting to be removed from the _eventProxyMap.
    // First find it from the _proxyMap
    conduitIterator i = _proxyMap.find(transactionIdentifier);

    // Then find it from the _eventProxyMap
    conduitIterator eventConduit = _eventProxyMap.begin();
    if (i != _proxyMap.end())
    {
        pfConduit eventProxy = (*i).second;
        while (((*eventConduit).second != eventProxy) &&
               (eventConduit != _eventProxyMap.end()))
        {
            eventConduit++;
        }

        // Return the message as an acknowledgement to the eventProxy
        pfMsgTransporter *transporter = pfMsgTransporter::createMsgTransporter(
            message_);
        pfConduit sender(this);
        transporter->setSender(sender);
        eventProxy.accept(transporter);

        // Remove proxies from both maps
        pfConduit nilConduit;
        if (eventConduit != _eventProxyMap.end())
        {
            (*eventConduit).second = nilConduit;
            _eventProxyMap.erase(eventConduit);
        }
        (*i).second = nilConduit;
        _proxyMap.erase(i);
    }
    return;
}

//
// Function: resetEventFlags
//
// Description:
//     Send a Reset Event Flags message to acknowlegde the arrived events
//

void gsmpProtocol :: resetEventFlags(pfUlong port_,
                                     pfByte eventFlags_)
{
    pfUlong transactionIdentifier = generateTransactionIdentifier();
    pfUlong portSessionNumber = getPortSessionNumber(port_);
    gsmpResetEventFlagsMessage *message = new gsmpResetEventFlagsMessage(
        gsmpMessage::GSMP_NO_SUCCESS_ACK,
        transactionIdentifier,
        port_,
        portSessionNumber,
        eventFlags_);
    toA(message);
    return;
}
