#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <xti.h>
#include <tklib.h>
#include <sys/tk_types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include "funcdef.h"
#include "../defs.h"

char _near shared_buf[4096];
unsigned long my_address;

int _fastcall
nfs_init(buf)
short _far *buf;
{
    int i;

    fputs("PC/NFS transport layer\n", stdout);
    i = rtm_install(T_TCP);
    if (i < 0) {
	t_errno = TSYSERR;
	if (errno == ENOENT)
	    fputs("\nPC/NFS is not installed.\n", stdout);
	else
	    t_error("rtm_install");
	return -1;
    }
    buf[0] = 0;
    buf[2] = (u_short)(u_long)(char _far *)shared_buf;
    buf[3] = (u_short)((u_long)(char _far *)shared_buf >> 16);
    buf[4] = sizeof shared_buf;
    return 10;
}

int _fastcall
nfs_shutdown(buf)
short _far *buf;
{
    rtm_cleanup();
    return 0;
}

int _fastcall
nfs_socket(buf)
short _far *buf;
{
    int type = buf[1];
    struct net_addr _far *addr = (struct net_addr _far *)&buf[2];
    int fd;
    char *name;
    struct t_bind *bind;
    struct sockaddr_in *sap;

    switch (type) {
	case NET_TYPE_TCP:
	    name = T_TCP;
	    break;
	case NET_TYPE_UDP:
	    name = T_UDP;
	    break;
	default:
	    dfputs("unknown socket type\r\n");
	    return -1;
    }
    fd = t_open(name, O_RDWR|O_NONBLOCK, NULL);
    if (fd < 0)
	return -1;
    bind = (struct t_bind *)t_alloc(fd, T_BIND, T_ALL);
    if (!bind) {
	t_error("t_alloc");
	t_close(fd);
	return -1;
    }
    bind->qlen = 1;
    bind->addr.len = sizeof (struct sockaddr_in);
    sap = (struct sockaddr_in *)bind->addr.buf;
    sap->sin_family = AF_INET;
    sap->sin_port = htons(addr->socket);
    sap->sin_addr.s_addr = INADDR_ANY;

    if (t_bind(fd, bind, bind) < 0) {
	t_error("t_bind");
	t_free((char *)bind, T_BIND);
	(void)t_close(fd);
	return -1;
    }
    buf[0] = fd;
    my_address = sap->sin_addr.s_addr;
    addr->host = sap->sin_addr.s_addr;
    addr->socket = ntohs(sap->sin_port);
    t_free((char *)bind, T_BIND);
    return sizeof (struct net_addr) + 2 * sizeof (short);
}

int _fastcall
nfs_read(buf)
short _far *buf;
{
    int flags;
    int len;
    int fd = buf[1];
    struct t_discon *discon;

    len = t_rcv(fd, shared_buf, buf[2], &flags);
    if (len < 0) {
	if (t_errno == TLOOK) {
	    switch (len = t_look(fd)) {
		case T_ORDREL:
		    (void)t_rcvrel(fd);
		    t_errno = TOUTSTATE;
		    return -1;
		case T_DISCONNECT:
		    discon = (struct t_discon *)t_alloc(fd, T_DIS, 0);
		    if (discon) {
			discon->udata.buf = shared_buf;
			discon->udata.maxlen = buf[2];
		    }
		    if (t_rcvdis(fd, discon) < 0)
			t_error("t_rcvdis");
		    if (discon) {
			discon->udata.buf = 0;
			discon->udata.maxlen = 0;
			len = discon->udata.len;
			t_free((char *)discon, T_DIS);
			if (len) {
printf("rcvdis got %d\n", len);
			    buf[0] = len;
			    return 2;
			}
		    }
		    t_errno = TOUTSTATE;
		    return -1;
		default:
printf("t_rcv: state=%d\n", len);
	    }
	}
	return -1;
    }
    buf[0] = len;
    return 2;
}

int _fastcall
nfs_drain(buf)
short _far *buf;
{

    buf[2] = sizeof shared_buf;
    return nfs_read(buf);
}

int _fastcall
nfs_write(buf)
short _far *buf;
{
    int len;

    len = t_snd(buf[1], shared_buf, buf[2], 0);
    if (len < 0)
	return len;
    buf[0] = len;
    return 2;
}

int _fastcall
nfs_recv(buf)
short _far *buf;
{
    struct net_addr _far *addr = (struct net_addr _far *)&buf[1];
    struct t_unitdata *ud;
    int flags;

    ud = (struct t_unitdata *)t_alloc(buf[1], T_UNITDATA, T_ADDR);
    if (!ud)
	return -1;
    ud->udata.buf = shared_buf;
    ud->udata.maxlen = buf[2];
    ud->udata.len = 0;
    if (t_rcvudata(buf[1], ud, &flags) < 0) {
	ud->udata.buf = NULL;
	t_free((char *)ud, T_UNITDATA);
	return -1;
    }
    addr->host = ((struct sockaddr_in *)ud->addr.buf)->sin_addr.s_addr;
    addr->socket = ntohs(((struct sockaddr_in *)ud->addr.buf)->sin_port);
    buf[0] = ud->udata.len;
    ud->udata.buf = NULL;
    t_free((char *)ud, T_UNITDATA);
    return sizeof (struct net_addr) + sizeof (short);
}

int _fastcall
nfs_sendto(buf)
short _far *buf;
{
    struct net_addr _far *addr = (struct net_addr _far *)&buf[4];
    struct t_unitdata *ud;

    ud = (struct t_unitdata *)t_alloc(buf[1], T_UNITDATA, T_ADDR);
    if (!ud)
	return -1;
    ud->udata.buf = shared_buf;
    ud->udata.maxlen = buf[2];
    ud->udata.len = buf[2];
    ud->addr.len = sizeof (struct sockaddr_in);
    ((struct sockaddr_in *)ud->addr.buf)->sin_family = AF_INET;
    ((struct sockaddr_in *)ud->addr.buf)->sin_addr.s_addr = addr->host;
    ((struct sockaddr_in *)ud->addr.buf)->sin_port = htons(addr->socket);
    if (t_sndudata(buf[1], ud) < 0) {
	ud->udata.buf = 0;
	ud->udata.maxlen = 0;
	t_free((char *)ud, T_UNITDATA);
	return -1;
    }
    ud->udata.buf = 0;
    ud->udata.maxlen = 0;
    t_free((char *)ud, T_UNITDATA);
    buf[0] = 0;
    return 2;
}
