//Editor-Info: -*- C++ -*-
//
//Subject: TOVE project / UNI testing
//
//File: unitest.cpp
//
//Version: $Revision: 1.19 $
//
//State: $State: Exp $
//
//Date: $Date: 1999/01/25 13:09:01 $
//
//Organisation:
//      Helsinki University of Technology
//      Laboratory of Telecommunications Software and Multimedia
//
//Author: Jari Katajavuori
//
//
//Description:
//
//
//Copyright:
//      Copyright 1999 Helsinki University of Technology
//      ALL RIGHTS RESERVED BETWEEN JANUARY 1996 AND JUNE 1999.
//
//Licence:
//
//
//History: 


#include <typeinfo>
#include <string>
#include <iostream.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <netinet/in.h>

#include "protocol/uni/unidefs.h"
#include "protocol/uni/unistrings.h"
#include "protocol/uni/unipdu.h"
#include "protocol/uni/unicoordprotocol.h"
#include "protocol/uni/unitransporter.h"
#include "protocol/uni/uniprotocol.h"
#include "protocol/uni/unimode.h"
#include "unitestadapter.h"
#include "unitestccprotocol.h"

#include "iface/sigif/sigdownprimitives.h"

#include "pf/bytes.h"
#include "pf/frame.h"
#include "pf/messenge.h"
#include "pf/system.h"
#include "pf/mux.h"
#include "pf/exception.h"
#include "pf/factory.h"
#include "pf/debug.h"

#include "protocol/saal/saalunilink.h"

#include "protocol/cc/ccdebug.h"

void initUserConduits(void);
void initNetworkConduits(void);
string getHostAddress(void);
string getNSAPAddress(string host);

string hostName;
int localPort;
int remotePort;
string ThostName;
int TlocalPort;
int TremotePort;
string ChostName;
int ClocalPort;
int CremotePort;

int vpi, vci;
int Tvpi, Tvci;

int location;
int start = 0;
int atmFirst = 0;
int atmSecond = 0;
int commander = 0;

// for selecting the type of switch (or connection)
int opermode=0;

string name;

string destinationAddress;
string sourceAddress;

void usage_ex(void)
{
    cerr << "Usage: " << name
         << " host lport rport u [start destAddr]" << endl
         << " host Olport Orport n Thost Tlport Trport" << endl
         << " port vpi vci u [start destAddr]" << endl
         << " port vpi vci n port vpi vci" << endl;
    
    exit(1);
}

void usage(void)
{
    cerr << "Usage: "
         << name << " <OconnectionInfo> u [<startInfo>|<commandInfo>]" << endl
         << name << " <OconnectionInfo> n <TconnectionInfo> " << endl
         << "where:" << endl
         << " <connectionInfo> : <remoteHost> <localPort> <remotePort>" << endl
         << " <connectionInfo> : <port> <vpi> <vci>" << endl
         << " <startInfo> : s <destAddrNSAP>" << endl
         << " <commandInfo> : <commandHost> <localPort> <remotePort>" << endl
         << "example:" << endl
         << " mozart % " << name << " wagner 4444 5555 n 0 0 100" << endl
         << " mahler % " << name << " 0 0 100 u sibelius 8888 9999" << endl
         << " wagner % " << name << " mozart 5555 4444 u s "
         << "47.0005.80FFE1000000F21A26D8.0020D4102A80.42" << endl; 
    cerr << "Note that a new option has been introduced which tells" << endl
         << "unitest the switch it is connected to (FSR=0,FORE=1). The" << endl
         << "argument defaults to FSR if not present." << endl
         << "Example:" << endl
         << name << " mikki 0 0 5 [0|1] u s "
         << "47.0005.80FFE1000000F20F4A15.0020EA002AEF.00" << endl;
    exit(1);
}

main(int argc, char **argv)
{
    // Set debug into std output
    debugOutputCout();

    try 
    {
        int argCount = 0;
        name = argv[argCount++];
        
        if (argc-argCount < 4)
        {
            usage();
        }
        
        hostName = argv[argCount++];
        if (isdigit(hostName[0]) != 0)
        {
            // hostName contains an ATM port
            // -> ATM connection request
            localPort = atoi(hostName.data());
            vpi = atoi(argv[argCount++]);
            vci = atoi(argv[argCount++]);
            atmFirst = 1;
        }
        else
        {
            // UDP connection request
            localPort = atoi(argv[argCount++]);
            remotePort = atoi(argv[argCount++]);
        }

        if (isdigit(argv[argCount][0]) != 0)
        {
            opermode = atoi(argv[argCount++]);
            cout << "opermode specified: " << opermode << endl;
        }
        else
        {
            cout << "opermode not specified, defaulting to: " << opermode << endl;
        }
        
        location = argv[argCount++][0]; 
        if ((location != 'u') && (location != 'n'))
        {
            usage();
        }
        
        // based on location a number of things are specified
        if (location == 'u')
        {
            // Location: User
            if (argc-argCount < 1)
            {
                start = 0;
            }
            else if (argc-argCount == 2)
            {
                // arguments: s NSAPaddr
                start = 1;
                argCount++;
                destinationAddress = getNSAPAddress(argv[argCount++]);
            }
            else if (argc-argCount == 3)
            {
                // Commander arguments present
                ChostName = argv[argCount++];
                ClocalPort = atoi(argv[argCount++]);
                CremotePort = atoi(argv[argCount++]);
                commander = 1;
            }
        }
        else
        {
            // Location: Network
            if (argc-argCount < 3)
            {
                usage();
            }

            ThostName = argv[argCount++];
            if (isdigit(ThostName[0]) != 0)
            {
                // ATM connection request
                TlocalPort = atoi(ThostName.data());
                Tvpi = atoi(argv[argCount++]);
                Tvci = atoi(argv[argCount++]);
                atmSecond = 1;
            }
            else
            {
                // UDP connection request
                TlocalPort = atoi(argv[argCount++]);
                TremotePort = atoi(argv[argCount++]);
            }
        }

        sourceAddress = getHostAddress();
        
        if (location == 'u')
        {
            initUserConduits();
        }
        else
        {
            initNetworkConduits();
        }
            
        pfSystem::instance()->run();
    }
    catch (pfException &e)
    {
        e.printInfo();
    }
    catch (sfException &e)
    {
        e.printInfo();
    }
    
    return 0;
}

void initUserConduits(void)
{
    // Test Adapter

//     uniTestAdapter *test = new uniTestAdapter();
//     if (start != 0)
//     {
//         test->setString("Destination Address", destinationAddress);
//     }
//     test->setString("Source Address", sourceAddress);
//     pfConduit testConduit(test);
    pfConduit testConduit;
    if (start != 0)
    {
        cout << "Activating Timer to launch signaling" << endl;
        testConduit = uniTestAdapter::createSelfoperateAdapter(
            sourceAddress, destinationAddress, opermode);
    }
    else
    {
        if (commander != 0)
        {
            cout << "Opening Commander UDP connection (port "
                 << ClocalPort << ") to " << ChostName << " port "
                 << CremotePort << endl;
            testConduit = uniTestAdapter::createCommandConnection(
                ChostName, htons(ClocalPort), htons(CremotePort));
        }
        else
        {
            cout << "Test adapter waiting.." << endl;
            testConduit = uniTestAdapter::createSelfoperateAdapter(
                sourceAddress, opermode);
        }
    }
    
    // UNI
    uniMode *mode = uniUserMode::instance();
    pfConduit uniConduit(uniProtocol::create(mode));
    uniConduit.setId(1);

    // Factory
    pfConduit factoryConduit =
        pfFactory::createFactory(uniConduit, testConduit);
    
    // Mux
    pfConduit muxConduit = pfMux::createMux(0xFFFFFF, uniMuxReferenceStr);
    muxConduit.setId(1);

    uniMuxCatcherTransporter cat = uniMuxCatcherTransporter::create();
    muxConduit.accept(&cat);
    pfMux *mux = cat.catchedMux();

    // UNI Coord
    pfConduit coordConduit = uniCoOrdProtocol::create(muxConduit);
    coordConduit.setId(1);

    // SAAL
    saalUNIlink *link;
    
    if (atmFirst != 0)
    {
        // Create / open ATM connection
        cout << "Opening ATM connection at port " << localPort
             << ", VPI=" << vpi << ", VCI=" << vci << endl;
        
        link = saalUNIlink::createATMUNIlink(1, localPort, vpi, vci,
						16384);
    }
    else
    {
        // Create / open ATM connection
        cout << "Opening UDP connection (port " << localPort
             << ") to " << hostName << " port " << remotePort << endl;

        link = saalUNIlink::createUDPUNIlink(1,
                                             hostName,
                                             localPort,
                                             remotePort);
    }
//     if (start != 0)
//     {
//         test->startTimer("TEST");
//     }

    pfConduit saalConduit = link->getConduit();
    
    // Connections
    
    testConduit.connectToA(uniConduit);
    uniConduit.connectToB(testConduit);
    uniConduit.connectToA(muxConduit);
    mux->connectToB(uniConduit, 1);
    factoryConduit.connectToA(muxConduit);
    muxConduit.connectToB(factoryConduit);
    muxConduit.connectToA(coordConduit);
    coordConduit.connectToB(muxConduit);
    coordConduit.connectToA(saalConduit);
    saalConduit.connectToB(coordConduit);

    return;
}    
    
void initNetworkConduits(void)
{
    // TopMux

    pfConduit topMuxConduit = uniTestCCProtocol::topMuxInstance();
    
    // Test CC

    uniTestCCProtocol *test = new uniTestCCProtocol();
    pfConduit testConduit(test);
    
    // prototype UNI

    uniMode *mode = uniNetworkMode::instance();
    pfConduit uniConduit(uniProtocol::create(mode));
    uniConduit.setId(2);
    
    // Factory

    pfConduit factoryOConduit = pfFactory::createFactory(uniConduit);
    pfConduit factoryTConduit = pfFactory::createFactory(uniConduit);
    
    // Mux
    pfConduit muxOConduit = pfMux::createMux(0xFFFFFF, uniMuxReferenceStr);
    pfConduit muxTConduit = pfMux::createMux(0xFFFFFF, uniMuxReferenceStr);
    
    // UNI Coord
    pfConduit coordOConduit = uniCoOrdProtocol::create(muxOConduit);
    pfConduit coordTConduit = uniCoOrdProtocol::create(muxTConduit);
    
    // SAAL
    saalUNIlink *linkO;
    saalUNIlink *linkT;
        
    if (atmFirst != 0)
    {
        // Open ATM connection
        cout << "Opening ATM connection at port " << localPort
             << ", VPI=" << vpi << ", VCI=" << vci << endl;

        linkO = saalUNIlink::createATMUNIlink(1, localPort, vpi, vci,
						16384);
    }
    else
    {
        // Open UDP connection 
        cout << "Opening UDP connection (port " << localPort
             << ") to " << hostName << " port " << remotePort << endl;

        linkO = saalUNIlink::createUDPUNIlink(1,
                                              hostName,
                                              localPort,
                                              remotePort);
    }

    if (atmSecond != 0)
    {
        // Open ATM connection
        cout << "Opening ATM connection at port " << localPort
             << ", VPI=" << Tvpi << ", VCI=" << Tvci << endl;
        
        linkT = saalUNIlink::createATMUNIlink(2, TlocalPort, Tvpi, Tvci, 
						16384);
    }
    else
    {
        // Open UDP connection 
        cout << "Opening UDP connection (port " << TlocalPort
             << ") to " << ThostName << " port " << TremotePort << endl;

        linkO = saalUNIlink::createUDPUNIlink(2,
                                              ThostName,
                                              TlocalPort,
                                              TremotePort);
    }

    // SAAL conduits
    pfConduit saalOConduit = linkO->getConduit();
    pfConduit saalTConduit = linkT->getConduit();    

    // Connections, originating end

    pfInstallTransporter installer =
        pfInstallTransporter::createInstallTransporter(factoryOConduit);
    installer.setKey(0);
    installer.useThisKey();
    topMuxConduit.accept(&installer);

    factoryOConduit.connectToB(topMuxConduit);
    factoryOConduit.connectToA(muxOConduit);
    muxOConduit.connectToB(factoryOConduit);
    muxOConduit.connectToA(coordOConduit);
    coordOConduit.connectToB(muxOConduit);
    coordOConduit.connectToA(saalOConduit);
    saalOConduit.connectToB(coordOConduit);

    // Connections, terminating end

    installer =
        pfInstallTransporter::createInstallTransporter(factoryTConduit);
    installer.setKey(1);
    installer.useThisKey();
    topMuxConduit.accept(&installer);
    
    factoryTConduit.connectToB(topMuxConduit);
    factoryTConduit.connectToA(muxTConduit);
    muxTConduit.connectToB(factoryTConduit);
    muxTConduit.connectToA(coordTConduit);
    coordTConduit.connectToB(muxTConduit);
    coordTConduit.connectToA(saalTConduit);
    saalTConduit.connectToB(coordTConduit);
    
    return;
}    

string getHostAddress(void)
{
    string result;

    char *tmp = new char[40];
    int size = 40;
    gethostname(tmp, size);
    string nameStr = tmp;
    nameStr += "-atm";

    result = getNSAPAddress(nameStr);
    delete [] tmp;
    
    return result;
}

string getNSAPAddress(string host)
{
    string result;

    char *tmp = new char[80];
    
    if (host.length() < 20)
    {
        cout << "Getting NSAP address for " << host << endl;
        
        FILE *f = fopen("/etc/hosts.atm", "r");
        if (f == 0)
        {
            cerr << "Couldn't open /etc/hosts.atm" << endl;
        }
        else
        {
            string tmpAddr, tmpHost;
            while (!feof(f) && !(tmpHost == host))
            {
                fscanf(f, "%s", tmp);
                tmpAddr = tmp;
                fscanf(f, "%s", tmp);
                tmpHost = tmp;
            }
            if (tmpHost == host)
            {
                result = tmpAddr;
            }
            fclose(f);
        }    
        
        cout << "Address for " << host << " is " << result << endl;
    }
    else
    {
        result = host;
    }
    
    delete [] tmp;
        
    return result;
}
