//Editor-Info: -*- C++ -*-
//
//Subject: TOVE project/CC
//
//File: ccprotocol.cpp
//
//Version: $Revision: 1.116 $
//
//State: $State: Exp $
//
//Date: $Date: 1999/03/09 10:29:56 $
//
//Organisation:
//      University of Technology
// 
//Author:
//      Pasi Nummisalo
//
//Description:
//     See header.
//
//Copyright:
//      Copyright 1999 Helsinki University of Technology
//      ALL RIGHTS RESERVED BETWEEN JANUARY 1996 AND JUNE 1999.
//      
//Licence:
//     
//
//History:
//

#include "pf/mux.h"
#include "pf/factory.h"
#include "pf/error.h"

#include "ccostate_null.h"
#include "ccostate_selectroute.h"
#include "cctstate_null.h"
#include "ccostate_exception.h"
#include "cctstate_exception.h"

#include "pf/debug.h"
#include "pf/tools.h"
#include "sw/swswitch.h"
#include "ccdebug.h"
#include "ccdpstate.h"
#include "ccstate.h"

#include "ccinadapter.h"
#include "ccmanagementadapter.h"
#include "cccrossconnectormux.h"
#include "pf/naming.h"
#include "protocol/uni/unidefs.h"
#include "sw/swswitch.h"

#ifdef UNITTCN
#define PRODUCTION 0
#else
#define PRODUCTION 1
#endif

class ccINadapter;

ccProtocol *ccProtocol :: create(ccProtocol *controllingProtocol_)
{
    ccProtocol *protocol = new ccProtocol();

    protocol->_controllingProtocol = controllingProtocol_;
  
    return protocol;
}

ccProtocol :: ccProtocol(void)
    : pfProtocol(),
      _data(),
      _dataConnectionActive(0),
      _EDPcount(0),
      _controllingProtocol(0),
      _controlRelationship(0),
      _iam(NOT_DEFINED),
      _in(1),
      _defaultDp(),
      _dpData(),
      _dpIter(_dpData.begin()),
      _inapAdapter(0),
      _inapTarget(0),
      _inapTargetAddress(),
      _inapTargetApplicationContext(),
      _assocationId(),
      _invokeCount(0),
      _dialogOpen(false),
      _selectRoute(),
      _inConnection(0),
      _outConnection(0)
{
    // Timer for SSF-FSM "waiting for instructions" state
    defineTimer(ccTimerSSFtimeout::timerName,
                ccTimerSSFtimeout::create(), ccTimerSSFtimeout::value1);

    return;
}

ccProtocol :: ccProtocol(const ccProtocol &other_)
    : pfProtocol(other_),
      _data(other_._data),
      _dataConnectionActive(0),
      _EDPcount(0),
      _controllingProtocol(other_._controllingProtocol),
      _controlRelationship(0),
      _iam(NOT_DEFINED),
      _in(other_._in),
      _defaultDp(other_._defaultDp),
      _dpData(other_._dpData),
      _dpIter(other_._dpIter),
      _inapAdapter(0),
      _inapTarget(0),
      _inapTargetAddress(),
      _inapTargetApplicationContext(),
      _assocationId(),
      _invokeCount(0),
      _dialogOpen(false),
      _selectRoute(),
      _inConnection(0),
      _outConnection(0)
{
    
    if (this != &other_)
    {
        // Timer for SSF-FSM "waiting for instructions" state
        defineTimer(ccTimerSSFtimeout::timerName,
                    ccTimerSSFtimeout::create(), ccTimerSSFtimeout::value1);
        
    }
    
    return;
}

ccProtocol :: ~ccProtocol(void)
{
    delete _inConnection;
    delete _outConnection;
    
    _inConnection = 0;
    _outConnection = 0;
   
    delete _inapAdapter;
 
    return;
}

//
//Function: error
//
//Description:
//    Hook method for handling errors in runCallback 
//

void ccProtocol :: error(pfUlong cause_)
{
    debugExceptionCatched(cause_);

    pfStorage storage = pfStorage();
    storage.defineStorage(sigCauseStr);
    storage[sigCauseStr].defineInteger(sigCause_ValueStr);
    storage[sigCauseStr].setInteger(sigCause_ValueStr, cause_);
    setData("releaseInd", storage);

    if (isSideO() == true)
    {
        changeState(ccOstateException::instance());
        ccOstateException::instance()->proceed(this);
    }
    else
    {
        changeState(ccTstateException::instance());
        ccTstateException::instance()->proceed(this);
    }

    return;
}


pfProtocol *ccProtocol :: cloneImplementation(void) const 
{
    ccProtocol *newProtocol = new ccProtocol(*this);
    assert(newProtocol != 0);
   
    return newProtocol;
}

//
//Function: acceptSynchronous
//
//Description:
//      This protocol is initialized with a state structure,
//      which makes it an originating side or a terminating side protocol.
//      Initialization is made based on direction of first incoming message
//      (from lower layer or from originating side).
//

void ccProtocol :: acceptSynchronous(pfTransporter *transporter_)
{
    // If state is still pfProtocol = this is first primitive
    pfProtocol *protocol = dynamic_cast<pfProtocol *>(getState());
    if (protocol != 0)
    {
        
        if (transporter_->isFromA() != 0)
        {
	     // First message from side A
             // this must be an originating side CC
             initOriginatingSide();         
        }
        else
	{ 
	     initTerminatingSide();
	}
    }

    // Let the pfProtocol class do the rest of work
    pfProtocol::acceptSynchronous(transporter_);

    return;
}

//
//Function: initOriginatingSide
//
//Description:
//   Set this protocol as CC-O.
//

void ccProtocol :: initOriginatingSide(void)
{    
#if CC_DEBUG
traceState(ccCC,"Init O");
#endif

    _iam = O_SIDE;
    
    changeState(ccOstateNull::instance());

    if (_in != 0)
    {
        // Get DPs from ccManagementAdapter
        ccManagementAdapter::instance()->cloneDPsForSideO(_dpData);
    }
   
    return;
}

//
//Function: initTerminatingSide
//
//Description:
//    Set this as CC-T protocol
//

void ccProtocol :: initTerminatingSide(void)
{
#if CC_DEBUG
traceState(ccCCT,"Init T");
#endif
    
    _iam = T_SIDE;
    
    changeState(ccTstateNull::instance());

    if (_in != 0)
    {
        // Get DPs from ccManagementAdapter
        ccManagementAdapter::instance()->cloneDPsForSideT(_dpData);
    }
    
    return;
}

//
//Function: saveSelectRoute
//
//Description:
//   Save select route message from SCP for later use.
//

void ccProtocol :: saveSelectRoute(binapSELECT_ROUTEpdu &pdu_)
{
    _selectRoute = pdu_;
}

//
//Function: openDialog
//
//Description:
//   Open dialog to SCP.
//

void ccProtocol :: openDialog(string &addr_, string &ctx_)
{

    if (CORBA_is_nil(_inapAdapter))
    {
        cout << "creating IN adapter" << endl;
        _inapAdapter = new ccINadapter(this);
    }
        
    // Is this address already resolved?
    if ((addr_ != _inapTargetAddress) && 
        (ctx_ != _inapTargetApplicationContext))
    {
        _inapTargetApplicationContext = ctx_;
        _inapTargetAddress = addr_;
        _invokeCount = 0;
        _assocationId = pfTools::intToString(getId());
 
        pfUlong pointCode = swSwitch::instance()->getPointCode();
        
        string pointCodeString = pfTools::intToString(pointCode);
        string GT("GT");
        string aeFactory("AeFactory");
        
        debugUser("Resolve:");
        debugString("PC ", pointCodeString);
        debugString("GT ", _inapTargetAddress);
        debugString("AC ", _inapTargetApplicationContext);
        
        CosNaming_Name name;
        name.length(5);
        name[0].id = CORBA_string_dup(pointCodeString.c_str());
        name[0].kind = CORBA_string_dup("");
        name[1].id = CORBA_string_dup(GT.c_str());
        name[1].kind = CORBA_string_dup("");
        name[2].id = CORBA_string_dup(_inapTargetAddress.c_str());
        name[2].kind = CORBA_string_dup("");
        name[3].id = CORBA_string_dup(_inapTargetApplicationContext.c_str());
        name[3].kind = CORBA_string_dup("");
        name[4].id = CORBA_string_dup(aeFactory.c_str());
        name[4].kind = CORBA_string_dup("");

        try
        {
            // Obtain an object reference to the target object
                 
            CORBA_Object_var factory = 
                pfNaming::instance()->resolve(name);
            
            
            toveinap_TcUserFactory_var tcUserFactory = 
                toveinap_TcUserFactory::_narrow(factory);
            
            if (CORBA_is_nil(tcUserFactory))
            {
                debugUser("**toveinap_TcUserFactory::_narrow() failed!**");
                THROW_NETWORK_OUT_OF_ORDER
            }

            _assocationId = pfTools::intToString(getId());
            TcSignaling_TcContextSetting tcContextSetting = 0;
#if CC_DEBUG   
cout << "calling tcUserFactory->create_SSF_SCF_responder" << endl;
#endif
            _inapTarget = tcUserFactory->create_SSF_SCF_responder(
                _inapAdapter,
                CORBA_string_dup(_assocationId.c_str()),
                tcContextSetting);
 
            if (CORBA_is_nil(_inapTarget))
            {
                debugUser("**inapTarget is null!**");
                THROW_NETWORK_OUT_OF_ORDER
            }
        }
        catch (pfMethodFailed &mf)
        {
            debugUser("**Resolve failed!**");
            THROW_NETWORK_OUT_OF_ORDER
        }
        catch(TcSignaling_NoMoreAssociations &)
        {
            debugUser("**NoMoreAssocations exception!**");
            THROW_RESOURCE_UNAVAILABILITY_UNSPECIFIED
        }
        catch(CORBA_SystemException &ex)
        {
            OBPrintException(ex);
            THROW_NETWORK_OUT_OF_ORDER
        }
    }
    
    _dialogOpen = true;

    return;
}

//
//Function: isDialogOpen
//
//Description:
//   Returns true if dialog is open to SCP.
//

bool ccProtocol :: isDialogOpen(void) const
{
    return _dialogOpen;
}

//
//Function: endDialog
//
//Description:
//   Ends current dialog.
//

void ccProtocol :: endDialog(void)
{   
   _inapTarget->end_association(_assocationId.c_str());
   resetDialog();
}

//
//Function: resetDialog
//
//Description:
//   Resets current dialog.
//

void ccProtocol :: resetDialog(void)
{
    setArmedEDPcount(0);
    _dialogOpen = false;
}

//
//Function: releaseCall
//
//Description:
//   Release current call (requested from SCP).
//

void ccProtocol :: releaseCall(void)
{
    pfStorage storage = pfStorage();
    storage.defineStorage(sigCauseStr);
    storage[sigCauseStr].defineInteger(sigCause_ValueStr);
    storage[sigCauseStr].setInteger(sigCause_ValueStr,
                                    uniCauseValue_NormalUnspecified);
    setData("releaseInd", storage);

    if (isSideO() == true)
    {
        changeState(ccOstateNull::instance());
        ccOstateNull::instance()->proceed(this);
    }
    else
    {
        changeState(ccTstateNull::instance());
        ccOstateNull::instance()->proceed(this);
    }

    return;
}

//
//Functions: send INAP messages
//
//Description:
//    Send INAP messages to SCP. 
//

void  ccProtocol :: sendInitialDP(ccDp &dp_, 
                                  toveinap_InitialDPArgType &initialDPArg_)
{
    // Operation name = message name
    string messageName("initialDP");
    // Construct a request object by calling _request() on the target object
    CORBA_Request_var request = _inapTarget->_request(messageName.c_str());
    request->add_in_arg() <<= initialDPArg_;
    
    // Context
    TcSignaling_TcContext ctext;
    ctext.ctr = TcSignaling_NOT_SPECIFIED;
    ctext.ivk_id = ++_invokeCount;
    ctext.a_id = _assocationId.c_str();
        
    request->add_inout_arg() <<= ctext;

    // Send using deferred synchronous method (non blocking)
    request->send_deferred();

#if CC_DEBUG    
ccDebug::instance()->sendInapMessage(dp_,this,messageName,_inapTargetAddress);
#endif    

    return;
}

void  ccProtocol :: sendEventReportBCSM(
    ccDp &dp_,
    toveinap_EventReportBCSMArgType &eventReportBCSMArg_)
{
    dp_.fillEDPrequest(this, eventReportBCSMArg_);
    
    // Operation name = message name
    string messageName("eventReportBCSM");    
    // Construct a request object by calling _request() on the target object
    CORBA_Request_var request = _inapTarget->_request(messageName.c_str());
    request->add_in_arg() <<= eventReportBCSMArg_;

    // Context
    TcSignaling_TcContext ctext;
    ctext.ctr = TcSignaling_NOT_SPECIFIED;
    ctext.ivk_id = ++_invokeCount;
    ctext.a_id = _assocationId.c_str();
        
    request->add_inout_arg() <<= ctext;
    
    request->send_deferred();

#if CC_DEBUG   
ccDebug::instance()->sendInapMessage(dp_,this,messageName,_inapTargetAddress);
#endif
    
    return;    
}

//
//Function: toOtherSide
//
//Description:
//    Send to T/O side.  
//

void ccProtocol :: toOtherSide(pfMessenger *messenger_)
{
    toB(messenger_);
    return;
}

//
//Function: toDown
//
//Description:
//   Send to down layer, e.g. DSS2 or UNI  
//
//

void ccProtocol :: toDown(pfMessenger *messenger_)
{
    toA(messenger_);
    return;
}

//
//Function: toSelf
//
//Description:
//   Send to this protocol (for internal messages)   
//
//

void ccProtocol :: toSelf(pfMessenger *messenger_)
{
    pfMsgTransporter *transporter = 
        pfMsgTransporter::createMsgTransporter(messenger_);

    transporter->setSaveMethodToHead();
    
    accept(transporter);
    return;
}

//
//Function: toCrossConnector
//
//Description:
//   Send cross connector transporter to cross Connector mux.v
//

void ccProtocol :: toCrossConnector(pfCrossConnecter *transporter_)
{
#if CC_DEBUG
ccDebug::instance()->sendCrossConnector(transporter_->getKey());
#endif    
    ccCrossConnectorMux::instance()->accept(transporter_);
}

bool ccProtocol :: isSideO() const
{
    bool result = false;
    if (_iam == O_SIDE)
    {
        result = true;
    }
    return result;
}

//
//Functions: set/get data
//
//Description:
//  
//

pfStorage ccProtocol :: getData(string name_)
{
    pfStorage result;
    result = _data.getStorage(name_);
    return result;
}

void ccProtocol :: setData(string name_, pfStorage &storage_)
{
    if (_data.isVariableDefined(name_) != 0)
    {
        _data.setStorage(name_, storage_);
    }
    else
    {
        _data.defineStorage(name_);
        _data.setStorage(name_, storage_);
    }
    
    return;
}

void ccProtocol :: saveData(string name_, pfMessenger *messenger_)
{
    pfStorage *storagePtr = dynamic_cast<pfStorage*>(messenger_);
    THROW_IF_DYNAMIC_CAST_FAILED(storagePtr)
    pfStorage storage = *storagePtr;
    setData(name_, storage);
}

void ccProtocol :: copyData(pfMessenger *messenger_, string name_)
{
     const pfStorage &data = getData(name_);
     messenger_->fetch(data);

     return;
}

//
//Function: getDp
//
//Description:
//     Get DP data object from storage 
//

ccDp &ccProtocol ::  getDp(toveinap_EventTypeBCSMType dp_)
{
    
    if (_in != 0)
    {
        ccMapIterType iter =  _dpData.find(dp_);
        
        if (iter == _dpData.end())
        {
#if CC_DEBUG
            cerr << "ccProtocol::getDp 'DP (" << dp_ << ") not found' "
                 << "Using default DP" << endl;
#endif
            return _defaultDp;
        }
        else
        {
            ccDp &result = (*iter).second;
            return result;
        }
    }
    else
    {
        return _defaultDp;
    }

}

//
//Function: getDp
//
//Description:
//     Get DP data object from storage
//     Deprecated
//

ccDp &ccProtocol ::  getDpValueObject(int index_)
{
    ccDp &result = getDp((toveinap_EventTypeBCSMType) index_);
    return  result;
}

pfConduit ccProtocol ::  getSideB(void)
{
    return _sideB;
}

//
//Functions: relationship related
//
//Description:
//    Given that an armed DP is encountered and DP criteria are met
//    the SSF may provide an information flow via a relationship.
//    - a control relationship if the SCF is able to influence call
//        processing via the relationship
//    - a monitor relationship if the SCF is not able to influence
//        call processing via the relationship.
//    Reletioship terminates if there are no more EDPs armed.
//

void ccProtocol :: setControlRelationship(void)
{
    _controlRelationship = true;
}

bool ccProtocol :: isRelationship(void) const
{
    if (_EDPcount > 0)
    {
        return 1;
    }
    else
    {    
        return 0;
    }
}

bool ccProtocol :: isControlRelationship(void) const
{
    return _controlRelationship;
}

void ccProtocol :: setArmedEDPcount(unsigned int count_)
{
    _EDPcount = count_;
    return;
}

void ccProtocol :: decreaseArmedEDPcount(void)
{
    _EDPcount--;
    return;
}

//
//Function: setINon
//
//Description:
//    IN functionality is in use.      
//
//

void ccProtocol :: setINon(void)
{
    _in = 1;
    return;
}

//
//Function: close
//
//Description:
//    Does closing actions.
//

void ccProtocol :: close(void)
{
    // IN stuff
    if (_dialogOpen == true)
    {
        endDialog();
    }

    // Other
    // Set side B in ccProtocol to null. Prevents uninstaller
    // from traveling to other side.
    pfConduit null;
    connectToB(null);
    connectToA(null);
    
    return;
}

//
//Functions: get numbers
//
//Description:
// 
//

string ccProtocol :: getCalledPartyNumber(void)
{
    string numberString;

    // If CC has received selectRoute inap-message (from SCP),
    // use the destination routing list.
    if (_selectRoute.hasArguments() == true)
    {
#if CC_DEBUG
        cout << "Using destination address from SCP" << endl;
#endif
        numberString = _selectRoute.getNextNumber();
    } 
    else // Use number from caller
    {
        const pfStorage &setupStorage = _data.getStorage("setup");
        const pfStorage &numberStorage = 
            setupStorage.getStorage(sigCalledPartyNumberStr);
        numberString = numberStorage.getString(sigCalledPartyNumber_DigitsStr);
    }
        
    return numberString;
}

string ccProtocol :: getCallingPartyNumber(void) const
{
    string numberString;
    const pfStorage &setupStorage = _data.getStorage("setup");
    const pfStorage &numberStorage = 
        setupStorage.getStorage(sigCallingPartyNumberStr);
    numberString = numberStorage.getString("Digits");
    
    debugString("Calling party number= ", numberString); 
   
    return numberString;
}

//
//Function: isPointToMultiPointCall
//
//Description:
//     Returns true if point-to-multipoint call
//

bool ccProtocol :: isPointToMultiPointCall(void) const
{
    bool multiPoint =
        _data.getStorage("setup").isVariableDefined(sigEndpointReferenceStr);
    
    return multiPoint;    
}

ccProtocol *ccProtocol :: getPointToMultiPointRoot(void) const
{    
    return _controllingProtocol;
}

//
//Function: connect
//
//Description:
//     Make a hardware data connection.
//

void ccProtocol :: connect(void)
{    
    const pfStorage &setup = _data.getStorage("setup");

    if (_selectRoute.hasArguments() == true)
    {
        string newBnumber = _selectRoute.getNextNumber();
#if CC_DEBUG
        cout << "Using destination address from SCP: " << newBnumber << endl;
#endif
        
        pfStorage &numberStorage = 
            setup.getStorage(sigCalledPartyNumberStr);
        numberStorage.setString(sigCalledPartyNumber_DigitsStr, newBnumber);
    }
    
    pfConduit conduit(this);
    pfUlong inputPort = (pfUlong) getId();

    if ( (isPointToMultiPointCall() == true) && (_controllingProtocol != 0) )
    {
        // This instance has a root protocol (add branch situation).
        // Get in connection from it.
        ieConnectionInfo *inConnection = 
            _controllingProtocol->getInConnection();
        swSwitch::instance()->connect(setup, inConnection, conduit); 
    }
    else
    {
        swSwitch::instance()->connect(setup, inputPort, conduit); 
    }

    return;
}

//
//Function: multiPointConnect DEPRECATED 
//
//Description:
//     Make a point-to-multipoint hardware connection.
//

void ccProtocol :: multiPointConnect(void)
{
    if (_controllingProtocol != 0 ) // Has a root (i.e. connection(s))
    {
        addConnection(_controllingProtocol);
    }
    else // Don't have a root
    {
        // This protocol instance must be the
        // controlling (root) instance (first connection).
        pfStorage setup = getData("setup");
        pfConduit thisConduit(this);
        pfUlong inputPort = (pfUlong) getId();
        swSwitch::instance()->connect(setup, inputPort, thisConduit); 
    }

    return;
}

//
//Function: addConnection DEPRECATED  
//
//Description:
//    Add branches to point-to-multipoint connection. 
//

void ccProtocol :: addConnection(ccProtocol *rootProtocol_)
{
    pfStorage setup = getData("setup");
    pfConduit conduit(this);
    ieConnectionInfo *inConnection = rootProtocol_->getInConnection();
    swSwitch::instance()->connect(setup, inConnection, conduit); 
    
    return;
}

//
//Function: getInConnection DEPRECATED 
//
//Description:
//    Returns ieConnectionInfo pointer representing in connection.
//

ieConnectionInfo *ccProtocol :: getInConnection(void)
{
    return _inConnection;
}

//
//Function: getOutConnection
//
//Description:
//    Returns ieConnectionInfo pointer representing out connection.
//

ieConnectionInfo *ccProtocol :: getOutConnection(void)
{
    return _outConnection;
}

//
//Function: setConnections
//
//Description:
//    Set in and out connections.
//

void ccProtocol :: setConnections(ieConnectionInfo *inConnection_, 
                                  ieConnectionInfo *outConnection_)
{
    _inConnection = inConnection_;
    _outConnection = outConnection_;
    _dataConnectionActive = 1;

    return;
}

//
//Function: disconnect
//
//Description:
//    Disconenct data connection.
//

void ccProtocol :: disconnect(void)
{
    bool mp = isPointToMultiPointCall();
    swSwitch::instance()->disconnect(_inConnection, _outConnection, mp);    
    _dataConnectionActive = 0;
    return;
}

//
//Function: isDataConnectionActive
//
//Description: 
//

bool ccProtocol :: isDataConnectionActive(void) const
{
    return _dataConnectionActive;
}
