/**
 * IPC module - simulator (one thread) version.
 *
 * @copyright 2008, Research In Motion Ltd.
 *
 * @version $Id: //depot/rel/dsp/ecall/2/c/testlab/ipc_sim.c#1 $
 */

#include <stdlib.h>

#include "rimtypes.h"
#include "msg.h"
#include "ipc.h"
#include "error.h"

#define QUEUE_LENGTH (3)

typedef struct {
    int read;
    int write;
    Msg data[QUEUE_LENGTH];
} Queue;

static void QueueReset(Queue *q) {
    q->read = 0;
    q->write = 0;
}

static void QueueWrite(Queue *q, const Msg *m) {
    q->data[q->write] = *m;
    q->write += 1;
    if (q->write >= QUEUE_LENGTH) q->write -= QUEUE_LENGTH;
}

static void QueueRead(Queue *q, Msg *m) {
    *m = q->data[q->read];
    q->read += 1;
    if (q->read >= QUEUE_LENGTH) q->read -= QUEUE_LENGTH;
}

static int QueueWriteAvail(Queue *q) {
    int n = q->read - q->write - 1;
    if (n < 0) n += QUEUE_LENGTH;
    return n;
}

static int QueueReadAvail(Queue *q) {
    int n = q->write - q->read;
    if (n < 0) n += QUEUE_LENGTH;
    return n;
}

struct _IpcConn {
    IpcConn *other;
    void *type;
    void (*run)(void);
    char host[100];
    char port[100];
    Queue *rx;
    Queue *tx;
};

static int     lastQueue;
static Queue   queues[10];
static int     lastConnection;
static IpcConn connections[10];
static IpcConn *recvConn;
static IpcConn *(*recvFn)(Msg *msg);

static Queue *AllocQueue(void) {
    if (lastQueue >= LENGTH(queues)) return NULL;
    return &(queues[lastQueue++]);
}

static IpcConn *AllocConnection(void) {
    if (lastConnection >= LENGTH(connections)) return NULL;
    return &(connections[lastConnection++]);
}

static IpcConn *FindMatchingListen(IpcConn *c) {
    int n;

    for (n = 0; n < lastConnection; n += 1) {
        IpcConn *cc = &(connections[n]);

        if ((cc->type == IpcListen) &&
            (strcmp(cc->host, c->host) == 0) &&
            (strcmp(cc->port, c->port) == 0)) {
            return cc;
        }
    }
    return NULL;
}

void IpcInit(void) {
    lastQueue = 0;
    lastConnection = 0;
}

IpcConn *IpcListen(const char *host, const char *port) {
    IpcConn *c = AllocConnection();
    if (c == NULL) return NULL;

    strcpy(c->host, host);
    strcpy(c->port, port);
    c->type = IpcListen;
    c->run  = NULL;
    return c;
}


IpcConn *IpcConnect(const char *host, const char *port) {
    IpcConn *c = AllocConnection();
    IpcConn *cc;

    if (c == NULL) return NULL;
    strcpy(c->host, host);
    strcpy(c->port, port);
    c->type = IpcConnect;
    c->run  = NULL;

    cc = FindMatchingListen(c);
    if (cc == NULL) return NULL;

    c->tx = cc->rx = AllocQueue();
    c->rx = cc->tx = AllocQueue();
    c->other = cc;
    cc->other = c;
    return c;
}

IpcConn *ReceiveConnect(Msg *msg) {
    if (QueueReadAvail(recvConn->rx) > 0) {
        QueueRead(recvConn->rx, msg);
        return recvConn;
    }
    return NULL;
}

IpcConn *ReceiveListen(Msg *msg) {
    int n;

    /* Run all the far end tasks */
    for (n = 0; n < lastConnection; n += 1) {
        IpcConn *c = &(connections[n]);

        if ((c->type == IpcListen) &&
            (c->other->run != NULL)) {
            recvFn = ReceiveConnect;
            recvConn = c->other;
            recvConn->run();
            recvFn = ReceiveListen;
        }
    }

    /* See if any returned some results - check in random order */
    for (n = 0; n < 100000; n += 1) {
        int k = ((n < 9000) ? rand() : n) % lastConnection;
        IpcConn *c = &(connections[k]);
        if (c->type == IpcListen) {
            if (QueueReadAvail(c->rx) > 0) {
                QueueRead(c->rx, msg);
                return c;
            }
        }
    }
    return NULL;
}

void IpcStart(void) {
    recvFn = ReceiveListen;
}

void IpcSend(IpcConn *conn, const Msg *msg) {
    if (QueueWriteAvail(conn->tx) > 0) {
        QueueWrite(conn->tx, msg);
        return;
    }
    Abort("IpcSend: QueueWriteAvail returned 0");
}

IpcConn *IpcRecv(Msg *msg) {
    return recvFn(msg);
}

void IpcStop(void) {
}

void IpcFinal(void) {
}

void IpcRegisterTask(IpcConn *conn, BOOL (*run)(void)) {
    conn->run  = run;
}
