#ifndef USE_NETHACK
void nethack_init() { }
#else /* do USE_NETHACK */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <fcntl.h>
#include <alloca.h>

#include <FL/Fl.H>

#include "specks.h"
#include "partiviewc.h"
#include "partiview.H"

struct sockaddr_in netsin;

int netfd = -1;

struct netops {
    int jumps;
    int cmds;
};

static struct netops listening = { 1, 1 };
static struct netops broadcasting = { 0, 0 };

static int net_parse_args( struct stuff **, int argc, char *argv[], char *fname, void * );
static void dorecv(int fd, void *junk);
static int setnet(struct sockaddr_in *sinp, int ttl);
static int sendjump( float txyz[3], float rxyz[3] );
static int sendcmd( int argc, char *argv[] );
static void netupdate();
static void navtrace();
static void cmdtrace( struct stuff **, int argc, char *argv[] );
void aer2rxyz( float rxyz[3], const float aer[3] );
void rxyz2aer( float aer[3], const float rxyz[3] );

static int netupdates;

extern "C" {
 void nethack_init() {
    parti_add_commands( net_parse_args, "net", NULL );
 }
}

int net_parse_args( struct stuff **, int argc, char *argv[], char *fname, void * ) {

  int i;
  struct sockaddr_in sin;
  struct hostent *hp;
  struct netops *ops;
  static char Usage[] = "net addr IPADDR/PORT | listen [[-]nav] [[-]cmd] | broadcast [[-]nav] [[-]cmd]";

  if(argc < 1 || strcmp(argv[0], "net"))
      return 0;

  if(argc <= 1) {
      msg(Usage);
      return 1;
  }

  switch(argv[1][0]) {
    case 'a':
	if(argc > 2) {
	    int port = 7490, ttl = 2;
	    char *cp, *ep;
	    cp = strchr(argv[2], '/');
	    ttl = 2;
	    if(cp) {
		*cp = '\0';
		port = strtol(cp+1, &ep, 0);
		if(ep != cp && *ep == '/')
		    ttl = strtol(ep+1, NULL, 0);
	    }
	    sin.sin_port = htons( port );
	    if(inet_aton(argv[2], &sin.sin_addr)) {
		sin.sin_family = AF_INET;
	    } else if((hp = gethostbyname(argv[2])) != NULL) {
		memcpy(&sin.sin_addr, hp->h_addr_list[0], sizeof(sin.sin_addr));
		sin.sin_family = hp->h_addrtype;
	    } else {
		msg("%s: net addrs: %s: unknown host\n", fname, argv[2]);
		break;
	    }
	    setnet( &sin, ttl );
	}
	msg("net addr %s/%d", inet_ntoa( netsin.sin_addr ), htons( netsin.sin_port ));
	break;
    
    case 'l':
    case 'b':
	ops = (argv[1][0] == 'l') ? &listening : &broadcasting;
	for(i = 2; i < argc; i++) {
	    char *cp = &argv[i][0];
	    int on = 1;
	    if(*cp == '-') on = 0, cp++;
	    if(*cp == '+') on = 1, cp++;
	    switch(*cp) {
		case 'j': ops->jumps = on; break;
		case 'c': ops->cmds = on; break;
		case 'o':
			  if(!strcmp(cp, "on")) ops->jumps = ops->cmds = 1;
			  else if(!strcmp(cp, "off")) ops->jumps = ops->cmds = 0;
			  break;
	    }
	}
	msg("net %s %cjumps %ccmds",
		argv[1][0]=='l' ? "listen" : "broadcast",
		"-+"[ops->jumps], "-+"[ops->cmds]);
	break;

    case 'j': {
	Point xyz;
	float aer[3], rxyz[3];
	Matrix c2w;
	int sendit = 0;
	parti_getc2w( &c2w );
	tfm2xyzaer( &xyz, aer, &c2w );
	aer2rxyz( rxyz, aer );

	if(argc > 2 && argv[2][0] == 'h') {
	    sendit = 1;
	} else if(argc > 5) {
	    sendit = getfloats( &xyz.x[0], 3, 2, argc, argv );
	    sendit += getfloats( &rxyz[0], 3, 5, argc, argv );
	}
	if(sendit)
	    sendjump( &xyz.x[0], &rxyz[0] );
	break;
      }

    case 'c':
	sendcmd( argc-2, argv+2 );
	break;

    default:
	msg(Usage);
	break;
  }
  netupdate();
  return 1;
}

static void netupdate() {
    netupdates = 0;
    if(netsin.sin_addr.s_addr == INADDR_ANY) {
	ppui.drawtrace = NULL;
	parti_cmdtrace( NULL );
 	return;
    }
    ppui.drawtrace = broadcasting.jumps ? navtrace : NULL;
    parti_cmdtrace( broadcasting.cmds ? cmdtrace : NULL );
}

void aer2rxyz( float rxyz[3], const float aer[3] ) {
    rxyz[0] = aer[0];
    rxyz[2] = aer[1];
    rxyz[1] = aer[2];
}

void rxyz2aer( float aer[3], const float rxyz[3] ) {
    aer[0] = rxyz[0];
    aer[1] = rxyz[2];
    aer[2] = rxyz[1];
}

static void navtrace() {
    static Matrix oldc2w;
    Matrix c2w;

    if(!broadcasting.jumps)
	return;

    parti_getc2w( &c2w );
    if(netupdates == 0 || memcmp(&c2w, &oldc2w, sizeof(c2w)) != 0) {
	Point pos;
	float aer[3], rxyz[3];
	oldc2w = c2w;
	netupdates++;
	tfm2xyzaer( &pos, aer, &c2w );
	aer2rxyz( rxyz, aer );
	sendjump( pos.x, rxyz );
    }
}

static void cmdtrace( struct stuff **, int argc, char *argv[] ) {
    if(!broadcasting.cmds)
	return;
    if(argc>1 && !strcmp(argv[0], "net"))	/* unsafe to broadcast "net ..." cmds */
	return;
    sendcmd( argc, argv );
}

static int setnet(struct sockaddr_in *sinp, int ttl) {
    int multi = IN_CLASSD( ntohl( sinp->sin_addr.s_addr ) );
    static int on = 1;

    if(netfd >= 0) {
	Fl::remove_fd( netfd );
	close(netfd);
	netfd = -1;
    }

    netfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

    if(netfd < 0) {
	perror("socket");
	return -1;
    }

    fcntl(netfd, F_SETFL, fcntl(netfd, F_GETFL, 0) | O_NONBLOCK|O_NDELAY);


#ifdef SO_REUSEPORT
    if(setsockopt(netfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) {
	perror("setsockopt: SO_REUSEPORT");
    }
#else /* if (as in Linux) no SO_REUSEPORT, try SO_REUSEADDR at least */
    if(setsockopt(netfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
	perror("setsockopt: SO_REUSEADDR");
    }
#endif /*SO_REUSEPORT*/

    if(bind(netfd, (struct sockaddr *)sinp, sizeof(*sinp)) < 0) {
	struct sockaddr_in bsin = *sinp;
	bsin.sin_addr.s_addr = INADDR_ANY;
	if(bind(netfd, (struct sockaddr *)&bsin, sizeof(bsin)) < 0) {
	    fprintf(stderr, "bind to %s: ", inet_ntoa(sinp->sin_addr));
	    perror("");
	    return -1;
	}
    }

    if(multi) {
	unsigned char mttl = ttl;
	static int bufsize = 32768;
	static unsigned char nope = 0;
	struct ip_mreq mr;

	if(mttl < 2) mttl = 2;

	if(setsockopt(netfd, IPPROTO_IP, IP_MULTICAST_LOOP, &nope, 1) < 0) {
	    perror("disabling IP_MULTICAST_LOOP");
	}

	if(setsockopt(netfd, IPPROTO_IP, IP_MULTICAST_TTL, &mttl, 1) < 0) {
	    perror("setting IP_MULTICAST_TTL");
	}

	if(setsockopt(netfd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(int)) < 0) {
	    perror("multicast SO_SNDBUF");
	}


	if(setsockopt(netfd, IPPROTO_IP, IP_MULTICAST_TTL, &mttl, 1) < 0) {
	    perror("setting IP_MULTICAST_TTL");
	}

#ifdef IP_ADD_MEMBERSHIP
	mr.imr_interface.s_addr = INADDR_ANY;
	mr.imr_multiaddr.s_addr = sinp->sin_addr.s_addr;
	if (setsockopt(netfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr, sizeof(mr)) < 0) {
	    fprintf(stderr, "IP_ADD_MEMBERSHIP: can't join multicast group %s",
		    inet_ntoa(sinp->sin_addr));
	    perror("");
	}
#endif /*IP_ADD_MEMBERSHIP*/

    }

    netsin = *sinp;

    Fl::add_fd( netfd, dorecv, NULL );
    netupdate();
    return 1;
}

#define PV_MAGIC 0x19570118
#define PV_JUMP  ('j'<<24 | 'u'<<16 | 'm'<<8 | 'p')
#define PV_CMD   ('c'<<24 | 'm'<<16 | 'd'<<8 | 0)

struct jumper {
    int magic;
    int kind;
    float txyz[3];
    float rxyz[3];
};

void htonlfloats( float *dst, float *src, int count ) {
    int i;
    for(i = 0; i < count; i++)
	*(int *)&dst[i] = htonl( *(int *)&src[i] );
}

static int sendjump( float txyz[3], float rxyz[3] ) {
    struct jumper jump;

    if(netfd < 0)
	return 0;
    jump.magic = htonl(PV_MAGIC);
    jump.kind = htonl(PV_JUMP);
    htonlfloats( jump.txyz, txyz, 3 );
    htonlfloats( &jump.rxyz[0], &rxyz[0], 3 );
    if(sendto( netfd, &jump, sizeof(jump), 0,  (struct sockaddr *)&netsin, sizeof(netsin) ) < 0) {
	perror("sendjump: sendto");
	return -1;
    }
    return 1;
}

static int sendcmd( int argc, char *argv[] ) {
    if(netfd < 0)
	return 0;
    char *str = rejoinargs( 0, argc, argv );
    int len = strlen(str);
    int totlen = 3*sizeof(int) + len + 1;
    int *buf = (int *) alloca( 3*sizeof(int) + len + 1 );
    buf[0] = htonl(PV_MAGIC);
    buf[1] = htonl(PV_CMD);
    buf[2] = htonl( strlen(str) );
    memcpy( (char *)&buf[3], str, len+1 );
    if(sendto( netfd, buf, totlen, 0,  (struct sockaddr *)&netsin, sizeof(netsin) ) < 0) {
	perror("sendcmd: sendto");
	return -1;
    }
    return 1;
}

static void receivejump( struct jumper *jump, int len, struct sockaddr_in *srcsin ) {
    Matrix c2w;
    Point xyz;
    float aer[3], rxyz[3];

    if(len < sizeof(struct jumper)) {
	msg("short net jump from %s", inet_ntoa(srcsin->sin_addr));
	return;
    }
    if(!listening.jumps)
	return;
    if(broadcasting.jumps) {
	msg("Avoiding loop with %s: setting net broadcast -jump",
			inet_ntoa(srcsin->sin_addr));
	broadcasting.jumps = 0;
	netupdate();
    }
    htonlfloats( &xyz.x[0], jump->txyz, 3 );
    htonlfloats( &rxyz[0], jump->rxyz, 3 );
    rxyz2aer( aer, rxyz );
    xyzaer2tfm( &c2w, &xyz, aer );
    parti_setc2w( &c2w );
}

static void receivecmd( int *buf, int len, struct sockaddr_in *srcsin ) {
    int slen = ntohl(buf[2]);
    if(3*sizeof(int) + slen > len) {
	msg("short net cmd from %s (len = %d expected %d)",
			inet_ntoa(srcsin->sin_addr),
			len, 3*sizeof(int) + slen);
	return;
    }
    if(!listening.cmds)
	return;
    if(broadcasting.cmds) {
	msg("Avoiding loop with %s: setting net broadcast -cmd",
			inet_ntoa(srcsin->sin_addr));
	broadcasting.cmds = 0;
	netupdate();
    }

    char *tstr = (char *)alloca( slen+1 );
    char *argv[512];
    int argc = tokenize( (char *)&buf[3], tstr, COUNT(argv), argv, NULL );
    specks_parse_args( &ppui.st, argc, argv );
    parti_redraw();
}

#ifndef __BITS_SOCKET_H
# define  socklen_t  int
#endif

static void dorecv(int fd, void *junk) {
    struct sockaddr_in sfrom;
    socklen_t sfromlen = sizeof(sfrom);
    int len;
    int buf[16384];
    int kind;

    for(;;) {
	/* remember, we set this socket to non-blocking mode */
	/* loop to receive all that's available */
	len = recvfrom(fd, (char *)buf, sizeof(buf), 0,
	    		(struct sockaddr *)&sfrom, &sfromlen);
	if(len <= 0)
	    return;

	if(ntohl( buf[0] ) != PV_MAGIC) {
	    msg("net: bad magic 0x%x", ntohl( buf[0] ));
	    return;
	}
	kind = ntohl( buf[1] );
	switch(kind) {
	    case PV_JUMP:
		receivejump( (struct jumper *)buf, len, &sfrom );
		break;
	    case PV_CMD:
		receivecmd( buf, len, &sfrom );
		break;
	}
    }
}
#endif /*USE_NETHACK*/