//Editor-Info: -*- C++ -*-
//
//Subject: TOVE project / OVOPS++
//
//File: mux.cpp
//
//Version: $Revision: 1.57 $
//
//State: $State: Exp $
//
//Date: $Date: 1998/12/22 14:42:39 $
//
//Organisation:
//      Helsinki University of Technology
//      Laboratory of Telecommunications Software and Multimedia
// 
//Authors:
//      Pasi Nummisalo
//      Timo Pärnänen
//      Juhana Räsänen
//
//Description:
//     See corresponding header file.  
//
//Copyright:     
//      Copyright 1999 Helsinki University of Technology
//      ALL RIGHTS RESERVED BETWEEN JANUARY 1996 AND JUNE 1999.
//      
//Licence:
//     
//
//History:
//
//

#include "mux.h"
#include "debug.h"

pfMux :: pfMux(pfKey maxKeyValue_, const string &keyName_)
    : pfProtocol(), 
      _map(),
      _cursor(0),
      _maxKeyValue(maxKeyValue_),
      _keyName(keyName_),
      _transporter(0)
{
    return;
}

pfMux :: pfMux(const pfMux &other_)
    : pfProtocol(other_),
      _map(other_._map),
      _cursor(other_._cursor),
      _maxKeyValue(other_._maxKeyValue),
      _keyName(other_._keyName)
{
    return;
}

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

pfConduit pfMux :: createMux(pfKey maxKeyValue_, const string &keyName_)
{
    pfConduit newMux(new pfMux(maxKeyValue_, keyName_));
    return newMux;
}

pfProtocol* pfMux :: cloneImplementation(void) const
{
    pfProtocol* mux = new pfMux(*this);
    return mux;
}

//
//Function: accept methods
//
//Description:
//    Accept method takes a transporter (visitor) as an argument.
//    If accept method is synchronous the operation is performed at
//    specific conduit right a way. In other case the transporter
//    is saved to schedulers message queue (see pfProtocol)   
//

void pfMux :: accept(pfTransporter *transporter_)
{
    if (transporter_->isSender(_sideA))
    {
        transporter_->setSenderIsA();
    }
    else
    {
        transporter_->setSenderIsB();
    }
    acceptSynchronous(transporter_);
    return;
} 

void pfMux :: acceptSynchronous(pfTransporter *transporter_)
{
    transporter_->atMux(this);

    // Delete transporter (if set)
    delete _transporter;
    _transporter = 0;
    return;
}

void pfMux :: tryToGotoB(pfTransporter *transporter_)
    throw(pfOutOfRangeException)
{
    pfConduit proxy(this);
    transporter_->setSender(proxy);

    // Returs 1 if _keyName is set else 0
    bool set = transporter_->isDispatchKeySet(_keyName);

    if (set != 0)
    {
        // Get dispatch key from messenger
        pfKey key = transporter_->getDispatchKey(_keyName);
        
        if (key > _maxKeyValue)
        {
            debugUser("Muxreference is greater than _maxKeyValue!");
            throw pfOutOfRangeException(key, 0, _maxKeyValue, PF_EX_INFO);
        }
        
        mapIterType mapKey = _map.find(key);

        if (mapKey != _map.end())
        {
            pfConduit destination((*mapKey).second);
            
            transporter_->setAcceptMethodToAsynchronous();
            // Send it to one of the side B conduits
            destination.accept(transporter_);
        }
        else // dispatchKey is not found
        {
            transporter_->setAcceptMethodToSynchronous();
            transporter_->setKey(key);
            toFactory(transporter_);
        }
    }
    else // dispatchKey is not set
    {
        transporter_->setAcceptMethodToSynchronous();
        toFactory(transporter_);        
    }
    
    return;
}

//
//Function: isKeyInstalled
//
//Description:
//    Finds out if the given key is already in use
//

bool pfMux :: isKeyInstalled(pfKey key_) const
{
    bool result = 0;
    mapConstIterType mapKey = _map.find(key_);    
    if (mapKey != _map.end())
    {
        result = 1;
    }
    return result;
}


//
//Function: toB
//
//Description:
//    Send transporter to sideB, in other words call sideB accept
//    method the transporter as parameter.
//

void pfMux :: toB(pfTransporter *transporter_)
{
    // Used to send installer/uninstaller to the conduit on sideB
    pfConduit proxy(this);
    pfKey key = transporter_->getKey();
    
    if (key > _maxKeyValue)
    {
        throw pfOutOfRangeException(key, 0, _maxKeyValue, PF_EX_INFO);
    }
    mapIterType mapKey = _map.find(key);

    if (mapKey != _map.end())
    {
        pfConduit destination((*mapKey).second);
    	// Send it to one of the side B conduits
	transporter_->setSender(proxy);
	destination.accept(transporter_);
    }
    return;
}


//
//Function: toAllB
//
//Description:
//    Call conduit map to send copy of transporter to all sideB conduits.     
//

void pfMux :: toAllB(pfTransporter *transporter_)
{
    mapIterType mapKey = _map.begin();
    mapIterType mapEnd = _map.end();
    
    while(mapKey != mapEnd)
    {
        pfConduit conduit = (*mapKey).second;
        conduit.accept(transporter_->clone());
        mapKey++;
    }
    return;
}

//
//Function: toFactory
//
//Description:
//    Send transporter to factory, in other words call accept
//    method from factory the transporter as parameter.
//

void pfMux :: toFactory(pfTransporter *transporter_)
{
    pfConduit proxy(this);
    transporter_->setSender(proxy);
    _sideB.accept(transporter_);
    return;
}

//
//Function: installOnSideB
//
//Description:
//    Save conduit to map, and allocate and return new dispatch key
//

pfKey pfMux :: installOnSideB(pfConduit &conduit_)
{
    pair<mapIterType, bool> p;
 
    // Here is supposed that free place will found beetween range
    // (1 - _maxKeyValue) so there is no check for rounds.
    do
    {
        _cursor++;
        if (_cursor == _maxKeyValue)
        {
            _cursor = 1;
        }
        p = _map.insert(mapType::value_type(_cursor, conduit_));
    }
    while(p.second == 0);
    conduit_.setKey(_cursor);
    return _cursor;
}

//
//Function: connectToB
//
//Description:
//    Save conduit to map with key
//

void pfMux :: connectToB(pfConduit &conduit_, pfKey key_)
    throw(pfOutOfRangeException)//, pfNameAlreadyDefinedException)
{
    pair<mapIterType, bool> p;
    if (key_ > _maxKeyValue)
    {
        throw pfOutOfRangeException(key_, 0, _maxKeyValue, PF_EX_INFO);        
    }
    p = _map.insert(mapType::value_type(key_, conduit_));
    if (p.second == 0)
    {
        //++TODO++ throw pfNameAlreadyDefinedException;
    }
// ++TODO++ check this later
// this is commented out because:
// perustelu: se aiheuttaa varattavien id-numeroiden hyppäämisen väärälle
// alueelle, kun uni kääntää alhaalta tulevien id:n eniten merkitsevän bitin
// päälle ensimmäisen SETUPin yhteydessä. Kun nyt luodaan yhteys
// vastakkaiseen suuntaan ja samaan muxiin tulee cross connecter, se myös
// varaa id:n, jolla MSb on ylhäällä. Tätä sen ei tulisi tehdä, vaan varata
// id, jossa MSb on poikittain. Poistamalla tuo rivi saadaan haluttu vaikutus
// aikaan. Muutenkaan ei ole järkevää juosta annetun key_-arvon perässä, kun
// ne ei kuitenkaan ole suuruusjärjestyksessä, IMO. /26/6/1998 JP/
// _cursor = key_;
    
    conduit_.setKey(key_); // TARVITAANKO ?!!?
    return;
}

//
//Function: removeFromSideB
//
//Description:
//    Remove conduit with given key from map. 
//
//

void pfMux :: removeFromSideB(pfKey key_)
{
    // ret indicates number of erased elements
    //int ret = 
    (void)_map.erase(key_);

    // ++TODO++ throw exception if (ret < 1)

    return;
}

//
//Function: deleteTransporter
//
//Description:
//    This method is used to allow mux to delete a transporter
//    in accept method after atMux method returns from the transporter.
//    This method can be called from transporter atMux method.
//
//

void pfMux :: deleteTransporter(pfTransporter *transporter_)
{
    _transporter = transporter_;
    return;
}

