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

#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 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;
}

void IpcStart(void) { }

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

IpcConn *IpcRecv(IpcConn *conn, Msg *msg) {
    int n;

    /* 
     * Run the far end process until a message is available or 'timeout'.  The
     * 'timeout' count is the maximum number of messages that can be processed
     * before a message is sent in reply.
     */
    for(n = 0; n < 2; ++n) {
        if ((conn->type == IpcListen) && (conn->other->run != NULL)) {
            conn->other->run();
        }

        /* Receive from the specified connection */
        if (QueueReadAvail(conn->rx) > 0) {
            QueueRead(conn->rx, msg);
            return conn;
        }
    }

    return NULL;
}

void IpcStop(void) {
}

void IpcFinal(void) {
}

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