//Editor-Info: -*- C++ -*-
//
//Subject: TOVE project / SW
//
//File: swswitch.cpp
//
//Version: $Revision: 1.42 $
//
//State: $State: Exp $
//
//Date: $Date: 1999/03/10 16:26:19 $
//
//Organisation:
//      Helsinki University of Technology
//      Laboratory of Telecommunications Software and Multimedia
//
//Author:
//      Timo Pärnänen
//
//Description:
//      See corresponding header file.
//
//Copyright:
//      Copyright 1999 Helsinki University of Technology
//      ALL RIGHTS RESERVED BETWEEN JANUARY 1996 AND JUNE 1999.
//
//Licence:
//
//
//History: 

// PF
#include "pf/storage.h"
#include "pf/error.h"
#include "pf/fileparser.h"

// IE
#include "ie/connectioninfo.h"
#include "ie/connectionidentifier.h"

// MTP3
#include "protocol/mtp3/mtp3protocol.h"
#include "protocol/mtp3/mtp3config.h"

// SS7
#include "protocol/sccp/sccpprotocol.h"
#include "protocol/tcap/tcapadapter.h"

// CC
#include "protocol/cc/ccdebug.h"
#include "protocol/cc/cccrossconnectormux.h"
#include "protocol/cc/ccmanagementadapter.h"

// UNI
#include "protocol/uni/uniprotocol.h"
#include "protocol/uni/unimode.h"

// SIGIF
#include "iface/sigif/sigupprimitives.h"
#include "iface/sigif/sigstrings.h"

// BISUP
#include "protocol/bisup/bisupprotocol.h"

// SAAL
#include "protocol/saal/saalunilink.h"
#include "protocol/saal/saalnnilink.h"
#include "iface/naalif/naaldownprimitives.h"

// SW
#include "iface/swif/swfabriccallback.h"
#include "iface/swif/swportconfig.h"
#include "swdefs.h"
#include "swportfactory.h"
#include "swatmport.h"
#include "swunilink.h"
#include "swnnilink.h"
#include "swswitch.h"

// Implementation of the singleton pattern

swSwitch *swSwitch::_only = 0;

swSwitch *swSwitch :: instance(void)
{
    if (_only == 0)
    {
        THROW_RESOURCE_UNAVAILABILITY_UNSPECIFIED;
    }
    return _only;
}

void swSwitch :: initSwitch(void)
{
    if (_only != 0)        
    {
        THROW_RESOURCE_UNAVAILABILITY_UNSPECIFIED;        
    }

    _only = new swSwitch();
    _only->readConfigFile();
    _only->initFCF();

    // Create CC management adapter (for TDP management)
    ccManagementAdapter::instance();
    return;
}

swSwitch :: swSwitch(void)
    : _netPrefix(),
      _switchName(),
      _switchType(0),
      _versionNumber(0),
      _portMap(),
      _portFactory(),
      _ss7Configure(ss7ConfigureImplementation::createSS7Configure()),
      _tcapProxy(),
      _sccpProxy(),
      _config(),
      _atmFCF()
{
    // SIGNALING LINKS
    _uniLink = swUNIlink::createPrototype();
    _nniLink = swNNIlink::createPrototype();

    // SAAL LINKS
    _uniSAALlink = saalUNIlink::createPrototype();
    _nniSAALlink = saalNNIlink::createPrototype();

    // UNI
    _uni = uniProtocol::create(uniNetworkMode::instance());
    _uniProxy = pfConduit(_uni);

    // Create crossConnector instance here, that TRACE output
    // would not be so confused when first link is created.
    ccCrossConnectorMux::instance();

    return;
}

swSwitch :: ~swSwitch(void)
{
    _only = 0;
    return;
}

void swSwitch :: initFCF(void)
{
    if (SW_FABRIC_USED == true)
    {
        _atmFCF.init();
    }
    return;
}

//
//Function: readConfigFile
//
//Description:
//    Read config file and set values for founded variables.
//    Some init methods are called from other objects.
//
void swSwitch :: readConfigFile(void)
{
    // Use FileParser to read configFile
    string fileName(SW_CONFIG_FILE_NAME);

    // Create parser and set comment line character
    pfFileParser file(fileName);
    file.setCommentCharacter(SW_COMMENT_CHARACTER);

    try
    {
        file.parseFile();
    }
    catch (pfException &exception)
    {
        cerr << "Config file (" << SW_CONFIG_FILE_NAME;
        cerr << ") missing !! (some mandatory values needed)" << endl;
        exit(0);        
    }

    //
    // DEBUG TRACE
    //
    pfUlong debugTrace = 0;
    file.getIntegerValue("DEBUG_TRACE", debugTrace);
    if (debugTrace != 0)
    {
        debugOutputCout();
        // Set CC module trace on
        // (Use wide terminal for optimal pleasure)
        ccDebug::instance()->setTrace(1,1,1,1,1,1);
    }
    
    //
    // POINT CODE
    //
    string pointCode;
    if (file.getStringValue("POINT_CODE", pointCode) == false)
    {
        // ++TODO++ cerr & exit are ugly (?)
        cerr << "Mandatory config variable POINT_CODE missing !!" << endl;
        cerr << "Add POINT_CODE to config file: ";
        cerr << SW_CONFIG_FILE_NAME << endl;
        exit(0);
    }
    _config.init(pointCode);
    pfUlong pointCodeValue = atoi(pointCode.c_str());
    _ss7Configure.setPointCode(pointCodeValue);

    //
    // NETWORK PREFIX
    //
    string netPrefix;
    file.getStringValue("NET_PREFIX", netPrefix);
    setNetPrefix(netPrefix);

    //
    // GLOBAL VARIABLES
    //
    file.getIntegerValue("CONTROL_PORT", SW_CONTROL_PORT);
    file.getIntegerValue("FABRIC_CONTROL_PORT", SW_FABRIC_CONTROL_PORT);
    file.getIntegerValue("SIGNALING_VPI", SW_SIGNALING_VPI);
    file.getIntegerValue("SIGNALING_VCI", SW_SIGNALING_VCI);
    file.getIntegerValue("ILMI_VPI", SW_ILMI_VPI);
    file.getIntegerValue("ILMI_VCI", SW_ILMI_VCI);
    file.getIntegerValue("GSMP_VPI", SW_GSMP_VPI);
    file.getIntegerValue("GSMP_VCI", SW_GSMP_VCI);

    file.getIntegerValue("MIN_VPI", SW_MIN_VPI);
    file.getIntegerValue("MAX_VPI", SW_MAX_VPI);
    file.getIntegerValue("MIN_VCI", SW_MIN_VCI);
    file.getIntegerValue("MAX_VCI", SW_MAX_VCI);
    
    file.getIntegerValue("SIGNALING_VCI_BASE", SW_SIGNALING_VCI_BASE);
    file.getIntegerValue("ILMI_VCI_BASE", SW_ILMI_VCI_BASE);
    
    file.getIntegerValue("ILMI_USED", SW_ILMI_USED);

    //
    // FABRIC / PORTS
    //
    // If switching fabric is used ports are set automatically by
    // the hardware, unless user can set ports in config file.
    //

    // Register port types to a port factory
    swAtmPort *atmPortPrototype = new swAtmPort("FSR155");
    _portFactory.registerPortPrototype(atmPortPrototype);
    
    file.getIntegerValue("FABRIC_USED", SW_FABRIC_USED);
    if (SW_FABRIC_USED == 0)
    {
        string portType("FSR155"); // Only available port type at moment.
        string value;
        // Iterate port numbers
        for (file.initLine("PORTS"); file.getNextValue(value);)
        {
            pfUlong portNumber = atoi(value.c_str());
            addPort(portNumber, portType);
        }
    }

    //
    // SS7
    //
    file.getIntegerValue("SS7_USED", SW_SS7_USED);
    if (SW_SS7_USED != 0)
    {
        initSS7();
    }
    
    return;
}

//
//Function: initSS7
//
//Description:
//    Create/connect SS7 stack. InstallUser Parts
//    and configure MTP routing tables.
//
//    NOTE: This method is under construction !!
//

void swSwitch :: initSS7(void)
{
    // -------------------------------------------------------------
    //
    // START SS7 SECTION
    //
    // MTP3, User Mux, AAL Mux and NI Mux
    //
    //++TODO++ 
    // o init ss7Configure (routing tables, address translation...)
    // o maxValue (each mux can have a different value)
    //
    //
    // Some harcoded values for testing purpose 
    setSS7stack();
    addMTP3Routes(10, 13, 1, 1);
    
    pfUlong maxValue = 1000;
    pfUlong BISUP = 9;

    _mtp3 = mtp3Protocol::createProtocol(_ss7Configure);
    _mtp3Proxy = pfConduit(_mtp3);
    string SI("serviceIndicator");
    _userMuxProxy = pfMux::createMux(maxValue, SI);
    string AAL("AALid");
    _aalMuxProxy = pfMux::createMux(maxValue, AAL);

    _aalMuxProxy.connectToA(_mtp3Proxy);
    _userMuxProxy.connectToA(_mtp3Proxy);
    _mtp3Proxy.connectToB(_userMuxProxy);
    _mtp3Proxy.connectToA(_aalMuxProxy);

    _niMuxProxy = pfMux::createMux(maxValue, AAL);
    connectMTP3User(BISUP, _niMuxProxy);
    _ss7Configure.setBISUP();

    // BISUP
    _bisup = bisupProtocol::create();
    _bisupProxy = pfConduit(_bisup);
    //
    // END OF SS7
    // -------------------------------------------------------------

    return;
}

//
//Functions: General methods
//
//Description:
//    General static methods used by different objects in sw module.
//
pfUlong swSwitch :: buildLinkIdentifier(pfUlong portNumber_, 
                                        pfUlong linkNumber_)   
{
    return (portNumber_ + (linkNumber_ << SW_LINKNUMBER_OFFSET));
}

pfUlong swSwitch :: parseLinkNumber(pfUlong linkIdentifier_)
{
    return (linkIdentifier_ >> SW_LINKNUMBER_OFFSET);
}

pfUlong swSwitch :: parsePortNumber(pfUlong linkIdentifier_)
{
    return (linkIdentifier_ & SW_MAX_PORT_NUMBER);
}

//
// Reserve methods for special VPI/VCI
//

pfUlong swSwitch :: reserveSignalingVPI(pfUlong linkIdentifier_) const
{
    (void)linkIdentifier_;
    return 0;
}

pfUlong swSwitch :: reserveSignalingVCI(pfUlong linkIdentifier_) const
{
    pfUlong portNumber = parsePortNumber(linkIdentifier_);
    pfUlong vci = SW_SIGNALING_VCI_BASE + portNumber;
    
    return vci;
}

pfUlong swSwitch :: reserveIlmiVPI(pfUlong linkIdentifier_) const
{
    (void)linkIdentifier_;
    return 0;
}

pfUlong swSwitch :: reserveIlmiVCI(pfUlong linkIdentifier_) const
{
    pfUlong portNumber = parsePortNumber(linkIdentifier_);
    pfUlong vci = SW_ILMI_VCI_BASE + portNumber;
    
    return vci;
}

// system variables

pfUlong swSwitch :: getPointCode(void) const
{
    pfUlong pointCode = _ss7Configure.getPointCode();
    return pointCode;
}

void swSwitch :: setNetPrefix(const string &netPrefix_)
{
    debugString("Network Prefix set in switch", netPrefix_);
    _netPrefix = netPrefix_;
    return;
}

string swSwitch :: getNetPrefix(void) const
{
    return _netPrefix;
}

// port management

void swSwitch :: addPort(pfUlong portNumber_, const string &portType_)
{
    swPort *port = _portFactory.createPort(portNumber_, portType_);
    
    pair<mapIterType, bool> p;
    p = _portMap.insert(mapType::value_type(portNumber_, port));
    
    if (p.second == 0)
    {
        delete port;
        THROW_RESOURCE_UNAVAILABILITY_UNSPECIFIED;
    }
    debugPfUlong("Port set in switch", portNumber_);
    return;
}

void swSwitch :: removePort(pfUlong portNumber_)
{
    mapIterType iter = _portMap.find(portNumber_);       
    if (iter != _portMap.end())
    {
        swPort *port = ((*iter).second);
        debugUser("Port deleted");
        delete port;
        port = 0;
    }
    (void)_portMap.erase(portNumber_);

    return;
}

swPort *swSwitch :: findPort(pfUlong portNumber_) const
{
    mapConstIterType iter = _portMap.find(portNumber_);       
    if (iter == _portMap.end())
    {
        THROW_RESOURCE_UNAVAILABILITY_UNSPECIFIED;
    }
    swPort *port = ((*iter).second);
    return port;
}

void swSwitch :: setSwitchName(const string& switchName_)
{
    debugString("SwitchName", switchName_);
    _switchName = switchName_;
    return;
}

void swSwitch :: setSwitchType(pfUlong switchType_)
{
    debugPfUlong("SwitchType", switchType_);
    _switchType = switchType_;
    return;
}

void swSwitch :: setFirmwareVersionNumber(pfUlong versionNumber_)
{
    debugPfUlong("FirmwareVersionNumber", versionNumber_);
    _versionNumber = versionNumber_;
    return;
}

void swSwitch :: setAddress(pfUlong linkIdentifier_, const string &address_)
{
    debugUser("-- ADDRESS REGISTERED --");
    debugPfUlong("LINK ID", linkIdentifier_);
    debugString("ADDRESS", address_);

    // ++TODO++
    // Register address to Rounting module !!
    return;
}

// clones

swUNIlink *swSwitch :: createUNIlink(pfUlong linkIdentifier_)
{
    // Clone prototype
    swUNIlink *newLink =
        _uniLink->clonePrototype(linkIdentifier_);
    return newLink;
}

swNNIlink *swSwitch :: createNNIlink(pfUlong linkIdentifier_,
                                     pfUlong destinationPointCode_)
{
    // Clone prototype
    swNNIlink *newLink =
        _nniLink->clonePrototype(linkIdentifier_, destinationPointCode_);
    return newLink;
}

saalUNIlink *swSwitch :: createSAALUNIlink(pfUlong linkIdentifier_,
                                           pfUlong vpi_,
                                           pfUlong vci_)
{
    // Clone prototype
    saalUNIlink *newLink = _uniSAALlink->clonePrototype(linkIdentifier_, 
                                                        SW_CONTROL_PORT,
                                                        vpi_,
                                                        vci_);
    return newLink;
}

saalNNIlink *swSwitch :: createSAALNNIlink(pfUlong linkIdentifier_,
                                           pfUlong vpi_,
                                           pfUlong vci_)
{
    // Clone prototype
    saalNNIlink *newLink = _nniSAALlink->clonePrototype(linkIdentifier_, 
                                                        SW_CONTROL_PORT,
                                                        vpi_,
                                                        vci_);
    return newLink;
}

void swSwitch :: startNNIlink(pfConduit &nsscfProxy_)
{
    // Set up SAAL connection
    naalSTARTreq *startMessenger = new naalSTARTreq;
    pfMsgTransporter *startMessage = 
        pfMsgTransporter::createMsgTransporter(startMessenger);
    startMessage->setSender(_aalMuxProxy);
    
    debugUser("Start SAAL NNI link");
    nsscfProxy_.accept(startMessage);
    return;
}


// MTP3

void swSwitch :: setSS7stack(void)
{
    //
    // Create implementations and proxies
    //
    _tcapProxy = tcapAdapter::createTCAP(getPointCode());
    _sccpProxy = sccpProtocol::createProtocol(_ss7Configure);

    _sccpProxy.connectToB(_tcapProxy);
    _tcapProxy.connectToA(_sccpProxy);

    // Connect user parts
    connectMTP3User(SCCP, _sccpProxy);
    _ss7Configure.setSCCP();

    return;
}

void swSwitch :: addMTP3Routes(pfUlong beginPointCode_,
                               pfUlong endPointCode_,
                               pfUlong portNumber_,
                               pfUlong linkNumber_)
{
    pfUlong linkIdentifier = buildLinkIdentifier(portNumber_, linkNumber_);
    _ss7Configure.configureRoutingTable(beginPointCode_,
                                        endPointCode_,
                                        linkIdentifier);
    return;
}

void swSwitch :: connectSAALtoMTP3(pfUlong linkIdentifier_, 
                                   pfConduit &saalConduit_)
{
    // Connect AAL connections
    mtpConfigTransporter config =
        mtpConfigTransporter::createConfigSideA(linkIdentifier_, saalConduit_);
    _mtp3Proxy.accept(&config);
    return;
}

void swSwitch :: connectBISUPtoMTP3(pfUlong linkIdentifier_,
                                    pfConduit &niProxy_)
{
    // ++TODO++
    // o Error handling (exceptions)
    
    // Install NI protocol
    pfInstallTransporter installer =
        pfInstallTransporter::createInstallTransporter(niProxy_);
    
    installer.setKey(linkIdentifier_);
    installer.useThisKey();    
    _niMuxProxy.accept(&installer);
    niProxy_.connectToA(_niMuxProxy);
    return;
}

pfConduit swSwitch :: getUNIprototype(void) const
{
    return _uniProxy;
}

pfConduit swSwitch :: getNNIprototype(void) const
{
    return _bisupProxy;
}

// ConnectIface

void swSwitch :: connect(ieConnectionIdentifier *input_,
                         ieConnectionIdentifier *output_,
                         bool bidirection_,
                         pfConduit &callback_)
{
    pfUlong inLinkIdentifier = input_->getLinkIdentifier();
    pfUlong inPortNumber = parsePortNumber(inLinkIdentifier);

    pfUlong outLinkIdentifier = output_->getLinkIdentifier();
    pfUlong outPortNumber = parsePortNumber(outLinkIdentifier);
    
    input_->setPort(inPortNumber);
    output_->setPort(outPortNumber);
    
    _atmFCF.connect(input_, output_, bidirection_, callback_);
    return;
}

pfUlong swSwitch :: connect(ieConnectionIdentifier *input_,
                            ieConnectionIdentifier *output_,
                            bool bidirection_,
                            swFabricCallback *callback_)
{
    pfUlong id = _atmFCF.connect(input_, output_, bidirection_, callback_);
    return id;
}

void swSwitch :: disconnect(ieConnectionIdentifier *input_,
                            ieConnectionIdentifier *output_,
                            bool bidirection_)
{
    _atmFCF.disconnect(input_, output_, bidirection_);
    return;
}

void swSwitch :: connect(const pfStorage &setup_,
                         pfUlong inputLinkIdentifier_,
                         pfConduit &callback_)
{
    // Get called party number
    const pfStorage &numberStorage = 
        setup_.getStorage(sigCalledPartyNumberStr);
    string calledPartyNumber = 
        numberStorage.getString(sigCalledPartyNumber_DigitsStr);
    
    // Select route
    // ++TODO++
    // Call routing block to get output link identifier

    pfUlong outputLinkIdentifier = 0;

    // Dummy routing
    string::iterator i = calledPartyNumber.end();
    i--;
    pfUlong outPortNumber = (*i) - 48;
    pfUlong outLinkNumber = 1;
    outputLinkIdentifier = buildLinkIdentifier(outPortNumber, outLinkNumber);
    // End dummy routing

    debugUser("------ swSwitch / connect -------");
    debugString("calledPartyNumber", calledPartyNumber);
    debugPfUlong("outPortNumber", outPortNumber);
    debugPfUlong("outputLinkIdentifier", outputLinkIdentifier);

    //
    //++TODO++ use auto pointers for ies, but NOT for ports !!
    //
    swPort *outPort = findPort(outPortNumber);
    ieConnectionInfo *outParameters = outPort->reserveResources();
    outParameters->setCallback(callback_);
    outParameters->setConnectManager(this);
    outParameters->setLinkIdentifier(outputLinkIdentifier);
    
    pfUlong inPortNumber = parsePortNumber(inputLinkIdentifier_);
    
    swPort *inPort = findPort(inPortNumber);
    // ++TODO++
    // Get input connection info from setup and try to reserve
    // with values proposed by an user, if present.
    ieConnectionInfo *inParameters = inPort->reserveResources();
    inParameters->setLinkIdentifier(inputLinkIdentifier_);

    debugPfUlong("inPortNumber", inPortNumber);
    debugPfUlong("inputLinkIdentifier", inputLinkIdentifier_);
    debugUser("----------------------------------");

    bool bidirection = !(setup_.isVariableDefined(sigEndpointReferenceStr));
    debugPfUlong("bidirection", bidirection);
    inParameters->connect(outParameters, bidirection);
    return;
}

void swSwitch :: connect(const pfStorage &setup_, 
                         ieConnectionInfo *input_,
                         pfConduit &callback_)
{
    // Get called party number
    const pfStorage &numberStorage = 
        setup_.getStorage(sigCalledPartyNumberStr);
    string calledPartyNumber = 
        numberStorage.getString(sigCalledPartyNumber_DigitsStr);
    
    // Select route
    // ++TODO++
    // Call routing block to get output link identifier

    pfUlong outputLinkIdentifier = 0;

    // Dummy routing
    string::iterator i = calledPartyNumber.end();
    i--;
    pfUlong outPortNumber = (*i) - 48;
    pfUlong outLinkNumber = 1;
    outputLinkIdentifier = buildLinkIdentifier(outPortNumber, outLinkNumber);
    // End dummy routing

    debugUser("------ swSwitch / MP connect -------");
    debugString("calledPartyNumber", calledPartyNumber);
    debugPfUlong("outPortNumber", outPortNumber);
    debugPfUlong("outputLinkIdentifier", outputLinkIdentifier);

    //
    //++TODO++ use auto pointers for ies, but NOT for ports !!
    //
    swPort *outPort = findPort(outPortNumber);
    ieConnectionInfo *outParameters = outPort->reserveResources();
    outParameters->setCallback(callback_);
    outParameters->setConnectManager(this);
    outParameters->setLinkIdentifier(outputLinkIdentifier);

    bool bidirection = false;
    input_->connect(outParameters, bidirection);

    return;
}

void swSwitch :: disconnect(ieConnectionInfo *input_,
                            ieConnectionInfo *output_,
                            bool multiPoint_)
{
    debugUser("------ swSwitch / disconnect -------");
    pfUlong inLinkIdentifier = input_->getLinkIdentifier();
    pfUlong inPortNumber = parsePortNumber(inLinkIdentifier);
    swPort *inPort = findPort(inPortNumber);
    inPort->freeResources(input_);

    pfUlong outLinkIdentifier = output_->getLinkIdentifier();
    pfUlong outPortNumber = parsePortNumber(outLinkIdentifier);
    swPort *outPort = findPort(outPortNumber);
    outPort->freeResources(output_);

    output_->setConnectManager(this);


    // Make sure that no references to callback are stored in
    // information elements.
    pfConduit nullConduit;

    output_->setCallback(nullConduit);
    input_->setCallback(nullConduit);
    //

    input_->disconnect(output_, !multiPoint_);
    return;
}

// Private methods

void swSwitch :: connectMTP3User(pfUlong identifier_, pfConduit &proxy_)
{
    mtpConfigTransporter config =
        mtpConfigTransporter::createConfigSideB(identifier_, proxy_);
    _mtp3Proxy.accept(&config);
    return;
}
