/*     
 *     x-kernel v3.3
 *
 *     Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 */

#ifndef SUNOS
#ifdef LINUX
#include <unistd.h>
#else
#include <sys/systeminfo.h>
#endif
#endif
#include <sys/types.h>
#include <time.h>
#include "xkernel.h"
#include "ip.h"
#include "msp.h"
#include "x_stdio.h"

#define argPrefix(str) ((!strncmp(arg, str, strlen(str))) && strlen(arg)>strlen(str))
#define argEq(str)     (!strcmp(arg, str))
#define HOST_TYPE      IPhost

#ifndef STR2HOST
#define STR2HOST str2ipHost
#endif

#define TIMES 7
#define DELAY 3

#define INIT_BUF_SIZE (16 * 1024)
#define START_MSG_TIME 1

typedef struct {
    Sessn    lls;
    Msg      savedMsg;
    XkHandle clientPushResult;
    int      sentMsgLength;
    XTime    starttime, endtime;
    int      buffer_size;
    int      done;
} PState;

static PState* protl_state;

static HOST_TYPE ServerAddr = { 0, 0, 0, 0 };
static HOST_TYPE ClientAddr = { 0, 0, 0, 0 };
static HOST_TYPE myHost;
static MSPport  serverPort = 1789;
static MSPport  clientPort = 1865;

static int  serverParam, clientParam;
static char *serverString;

/* Sizes of messages sent, in bytes */
#define NUM_TESTS 1
static int size[] = { 1024 };
static int trips = 100;

int tracemsptestp;

static void     client(Event, void *);
static void     server(Event, void *);
static int      runTest(Protl, int);
static XkReturn serverDemux(Protl, Sessn, Msg *);
static void     processOptions(void);
static XkReturn closeDone(Sessn);
static XkReturn customOpenDone(Protl, Protl, Sessn, Protl);

int msptest_init(Protl);

int msptest_init(Protl self)
{
    Protl  llp;
    PState *ps;
    char   buf[256];
    time_t t = time(0);

    processOptions();
    llp = xGetProtlDown(self, 0);
    if (!xIsProtl(llp))
        Kabort("Test protocol has no lower protocol");
    xControlProtl(xGetProtlDown(self, 0), GETMYHOST, (char *)&myHost,
		  sizeof(HOST_TYPE));
    ps = X_NEW(PState);
    bzero((char *)ps, sizeof(PState));
    self->state = (VOID *)ps;
    protl_state = ps;

#if defined(SUNOS) || defined(LINUX)
    gethostname(buf, 255);
#else
    sysinfo(SI_HOSTNAME, buf, 255);
#endif
    printf("Protocol: MSP\n");
    printf("Time: %s", ctime(&t));
    printf("Host: %s\n", buf);
    if (serverParam) {
        printf("Participant: server\n");
        evDetach(evSchedule(server, self, 0));
    }
    else if (clientParam) {
        printf("Participant: client\n");
	printf("Round Trips: %d \n", trips);
        STR2HOST(&ServerAddr, serverString);
        ClientAddr = myHost;
        evDetach(evSchedule(client, self, 0));
    }
    return 0;
}

static void
processOptions()
{
    int  i;
    char *arg;

    for (i = 1; i < globalArgc; i++) {
        arg = globalArgv[i];
        if (argEq("-s"))
	    serverParam = 1;
        else if (argPrefix("-c")) {
	    clientParam = 1;
	    serverString = arg + 2;
	}
        else if (argEq("-c")) {
	    clientParam = 1;
	    serverString = globalArgv[i+1];
	    i++;
        }
        else if (argPrefix("-trips="))
	    sscanf(arg + strlen("-trips="), "%d", &trips);
        else if (argEq ("-trips")) {
	    sscanf(globalArgv[i+1], "%d", &trips);
	    i++;
	}
    }
}

/*
 *  Server startup routine
 */
static void
server(Event ev, VOID *foo)
{
    Part   p;
    Protl  myProtl = (Protl) foo;
    PState* ps = myProtl->state;

    xAssert(xIsProtl(myProtl));

    /* Define protocol methods */
    myProtl->demux = serverDemux;
    myProtl->opendone = customOpenDone;
    myProtl->closedone = closeDone;

    /*  Set some more server-specific information */
    ps->buffer_size = INIT_BUF_SIZE;
    ps->done = 0;

    /*  Set up and do a passive open */
    partInit(&p, 1);
    partPush(p, ANY_HOST, 0);
    partPush(p, &serverPort, sizeof(MSPport));

    if (xOpenEnable(myProtl, myProtl, xGetProtlDown(myProtl, 0), &p) ==
	XK_FAILURE) {
        printf("can't openenable lower protocol\n");
    }
}

/*
 * Consume received messages at some rate
 */
static void
server_consumer(Event ev, VOID *foo)
{
    Protl  myProtl = (Protl) foo;
    PState* ps = myProtl->state;
    int timeout = START_MSG_TIME;
    int num_read = 0;
    int buf_size;

    printf("Consumer timeout: ");
    while (ps->done == 0)
    {
	if (ps->buffer_size <= INIT_BUF_SIZE - 1024)
	{
	    /* "Consume" a packet - set new receive buffer size */
	    ps->buffer_size += 1024;
	    buf_size = ps->buffer_size;
	    xControlSessn(ps->lls, MSP_SETRCVBUFSIZE, (char*) &buf_size,
			sizeof(buf_size));

	    /*  Increase timeout (slow down consume rate) every so often */
	    num_read++;
	    if ((num_read % 10 == 0) && (timeout<START_MSG_TIME * (1 << 7))) {
	        timeout <<= 1;
		printf("%d  ",timeout);
	    }
	}
	Delay(timeout);
    }
    printf("\n");
}

/*
 * Client initialization routine
 */
static void
client(Event ev, VOID *foo)
{
    Protl  myProtl = (Protl) foo;
    PState *ps;
    int    i;
    Part   p[2];

    xAssert(xIsProtl(myProtl));
    ps = (PState *) myProtl->state;

    /* Define protocol methods */
    /* (we need no methods) */

    partInit(p, 2);
    partPush(p[0], &ServerAddr, sizeof(IPhost));
    partPush(p[0], &serverPort, sizeof(MSPport));
    partPush(p[1], ANY_HOST, 0);
    partPush(p[1], &clientPort, sizeof(MSPport));

    ps->lls = xOpen(myProtl, myProtl, xGetProtlDown(myProtl, 0), p);
    if (ps->lls == ERR_SESSN) {
        printf("open failed!\n");
        Kabort("End of test");
    }

    for (i = 0; i < NUM_TESTS; i++)
        runTest(myProtl, size[i]);

    xClose(ps->lls);
    printf("End of test\n");
}

static
#if defined(__GNUC__)
inline
#endif
void checkHandle(h, str)
XkHandle h;
char     *str;
{
    switch (h) {
        case XMSG_ERR_HANDLE:
        case XMSG_ERR_WOULDBLOCK:
	    sprintf(errBuf, "%s returns error handle %d", str, h);
	    Kabort(errBuf);
        default:
	  break;
    }
}

int runTest(self, len)
Protl self;
int   len;
{
    static int overall_msg_num = 0;
    Msg    msg;
    PState *ps = (PState *)self->state;
    XTime  total;
    int    test;
    int    count;

    xAssert(ps);
    msgConstructAllocate(&ps->savedMsg, len);
    ps->sentMsgLength = len;

    printf("\n\nMessage Length (bytes): %d\n",len);
    printf("Times (sec):\n");
    for (test = 0; test < TIMES; test++) {
        xGetTime(&(ps->starttime));

	/*  Spin in a tight loop pushing messages */
	for (count = 0; count < trips; count++)
	{
            msgConstructCopy(&msg, &ps->savedMsg);
	    /*  Can store overall message number into the message for
		server-side checking here */

	    ps->clientPushResult = xPush(ps->lls, &msg);
	    checkHandle(ps->clientPushResult, "client push");
	    msgDestroy(&msg);
	    ++overall_msg_num;
	}

	xGetTime(&(ps->endtime));
        xSubTime(&total, ps->endtime, ps->starttime);
        printf("%6d.%06d\n", total.sec, total.usec);

	/*  Wait DELAY seconds for the window to clear out */
        Delay(DELAY * 1000);
    }

    msgDestroy(&ps->savedMsg);
    return 0;
}

XkReturn
serverDemux(self, lls, dg)
Protl self;
Sessn lls;
Msg   *dg;
{
    PState *ps = (PState *)self->state;
    static unsigned int count = 0;

    count++;
    xIfTrace(msptestp, TR_MAJOR_EVENTS) {
        putchar('.');
        if (!(count % 50))
	    putchar('\n');
    }

    /*  Check if we have received all our messages - if so, close */
    if (count >= trips * TIMES * NUM_TESTS)
    {
	xClose(lls);
	protl_state->done = 1;
    }

    /*	Can put message data check here (make sure packets are received
	in order) */

    /*  Change our buffer size value and return */
    ps->buffer_size -= msgLength(dg);
    return XK_SUCCESS;
}

static XkReturn
closeDone(lls)
Sessn lls;
{
    xTrace1(msptestp, TR_MAJOR_EVENTS, "closedone (%x) called", lls);
    if (serverParam)
    {
        xClose(lls);
	protl_state->done = 1;
    }
    return XK_SUCCESS;
}

static XkReturn
customOpenDone(self, llp, s, hlpType)
Protl self, llp, hlpType;
Sessn s;
{
    PState* ps = (PState*) self->state;

    xTrace0(msptestp, TR_MAJOR_EVENTS, "msp test program openDone");

    /* Spawn consumer thread; wait two seconds to start */
    ps->lls = s;
    evDetach(evSchedule(server_consumer, self, 2000000));

    if (serverParam)
        xDuplicate(s);

    return XK_SUCCESS;
}

