/* Copyright (c) 1997 The Regents of the University of California.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

/* Pd side of the Pd/Pd-gui interface. */

#include "m_imp.h"
#ifdef UNIX
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#ifndef __linux__
#include <bstring.h>
#endif
#include <sys/time.h>
#endif
#ifdef NT
#include <io.h>
#include <fcntl.h>
#include <process.h>
#include <winsock.h>
typedef int pid_t;
#define EADDRINUSE WSAEADDRINUSE
#endif
#include <stdarg.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <malloc.h>
#include <string.h>
#include <stdio.h>

typedef struct _fdpoll
{
    int fdp_fd;
    t_fdpollfn fdp_fn;
    void *fdp_ptr;
} t_fdpoll;

#define INBUFSIZE 4096
#define OUTBUFSIZE PIPE_BUF
#define OUTBUFMESSADVISE 1024
#define OUTBUFMESSOVERFLOW 1024
#define OUTBUFTOT (OUTBUFSIZE + OUTBUFMESSOVERFLOW)

struct _socketreceiver
{
    char *sr_inbuf;
    int sr_inhead;
    int sr_intail;
};

static int sys_nfdpoll;
static t_fdpoll *sys_fdpoll;
static int sys_maxfd;
static int sys_guisock;

static t_binbuf *inbinbuf;
static t_socketreceiver *sys_socketreceiver;

void sys_sockerror(char *s)
{
#ifdef NT
    int err = WSAGetLastError();
    if (err == 10054) return;
#endif
#ifdef UNIX
    int err = errno;
#endif
    fprintf(stderr, "%s: %s (%d)\n", s, strerror(err), err);
}

void sys_addpollfn(int fd, t_fdpollfn fn, void *ptr)
{
    int nfd = sys_nfdpoll;
    int size = nfd * sizeof(t_fdpoll);
    t_fdpoll *fp;
    sys_fdpoll = (t_fdpoll *)t_resizebytes(sys_fdpoll, size,
    	size + sizeof(t_fdpoll));
    fp = sys_fdpoll + nfd;
    fp->fdp_fd = fd;
    fp->fdp_fn = fn;
    fp->fdp_ptr = ptr;
    sys_nfdpoll = nfd + 1;
    if (fd >= sys_maxfd) sys_maxfd = fd + 1;
}

void sys_rmpollfn(int fd)
{
    int nfd = sys_nfdpoll;
    int i, size = nfd * sizeof(t_fdpoll);
    t_fdpoll *fp;
    for (i = nfd, fp = sys_fdpoll; i--; fp++)
    {
    	if (fp->fdp_fd == fd)
    	{
    	    while (i--)
    	    {
    	    	fp[0] = fp[1];
    	    	fp++;
    	    }
	    sys_fdpoll = (t_fdpoll *)t_resizebytes(sys_fdpoll, size,
    		size - sizeof(t_fdpoll));
	    sys_nfdpoll = nfd - 1;
	    return;
    	}
    }
    post("warning: %d removed from poll list but not found", fd);
}

static int sys_pollfds(int microsec)
{
    fd_set readset, writeset, exceptset;
    struct timeval timout;
    int i, didsomething = 0;
    t_fdpoll *fp;
    timout.tv_sec = 0;
    timout.tv_usec = microsec;
#ifdef DEBUGTIME    
    sys_gettime(&fuck);
#endif
    FD_ZERO(&writeset);
    FD_ZERO(&readset);
    FD_ZERO(&exceptset);
    for (fp = sys_fdpoll, i = sys_nfdpoll; i--; fp++)
    	FD_SET(fp->fdp_fd, &readset);
    select(sys_maxfd+1, &readset, &writeset, &exceptset, &timout);
#ifdef DEBUGTIME
    printf("sleep %d %d\n", microsec, sys_microsecsince(&fuck));
#endif
    for (fp = sys_fdpoll, i = sys_nfdpoll; i--; fp++)
    	if (FD_ISSET(fp->fdp_fd, &readset))
    	    (*fp->fdp_fn)(fp->fdp_ptr, fp->fdp_fd), didsomething = 1;
    return (didsomething);
}

void sys_microsleep(int microsec)
{
    sys_pollfds(microsec);
}

t_socketreceiver *socketreceiver_new(void)
{
    t_socketreceiver *x = (t_socketreceiver *)getbytes(sizeof(*x));
    x->sr_inhead = x->sr_intail = 0;
    if (!(x->sr_inbuf = malloc(INBUFSIZE))) bug("t_socketreceiver");;
    return (x);
}

void socketreceiver_free(t_socketreceiver *x)
{
    free(x->sr_inbuf);
    freebytes(x, sizeof(*x));
}

    /* this is in a separately called subroutine so that the buffer isn't
    sitting on the stack while the messages are getting passed. */
static int socketreceiver_doread(t_socketreceiver *x)
{
    char messbuf[INBUFSIZE], *bp = messbuf;
    int indx;
    int inhead = x->sr_inhead;
    int intail = x->sr_intail;
    char *inbuf = x->sr_inbuf;
    if (intail == inhead) return (0);
    for (indx = intail; indx != inhead; indx = (indx+1)&(INBUFSIZE-1))
    {
    	    /* if we hit a semi that isn't preceeded by a \, it's a message
    	    boundary.  LATER we should deal with the possibility that the
    	    preceeding \ might itself be escaped! */
    	char c = *bp++ = inbuf[indx];
    	if (c == ';' && (!indx || inbuf[indx-1] != '\\'))
    	{
    	    intail = (indx+1)&(INBUFSIZE-1);
    	    binbuf_text(inbinbuf, messbuf, bp - messbuf);
    	    if (sys_debuglevel & DEBUG_MESSDOWN)
    	    {
    	    	write(2,  messbuf, bp - messbuf);
    	    	write(2, "\n", 1);
    	    }
    	    x->sr_inhead = inhead;
    	    x->sr_intail = intail;
    	    return (1);
    	}
    }
    return (0);
}

void socketreceiver_read(t_socketreceiver *x, int fd)
{
    char *semi;
    int readto = (x->sr_inhead >= x->sr_intail ? INBUFSIZE : x->sr_intail-1);
    int ret;
    
    	/* the input buffer might be full.  If so, drop the whole thing */
    if (readto == x->sr_inhead)
    {
    	fprintf(stderr, "pd: dropped message from gui\n");
    	x->sr_inhead = x->sr_intail = 0;
    	readto = INBUFSIZE;
    }
    else
    {
	ret = recv(fd, x->sr_inbuf + x->sr_inhead, readto - x->sr_inhead, 0);
	if (ret < 0)
	{
	    sys_sockerror("recv");
	    if (x == sys_socketreceiver) sys_bail();
	}
	else if (ret == 0)
	{
	    if (x == sys_socketreceiver)
	    {
    	    	fprintf(stderr, "pd: exiting\n");
    	    	sys_bail();
	    }
	    else
	    {
	    	post("EOF on socket %d\n", fd);
	    	sys_rmpollfn(fd);
	    	sys_closesocket(fd);
	    }
	}
	else
	{
    	    x->sr_inhead += ret;
    	    if (x->sr_inhead >= INBUFSIZE) x->sr_inhead = 0;
    	    while (socketreceiver_doread(x))
    		binbuf_eval(inbinbuf, 0, 0, 0);
 	}
    }
}

void sys_closesocket(int fd)
{
#ifdef UNIX
    close(fd);
#endif
#ifdef NT
    closesocket(fd);
#endif
}


void sys_gui(char *s)
{
    int length = strlen(s), written = 0, res;
    if (sys_debuglevel & DEBUG_MESSUP) fprintf(stderr, "%s",  s);
    while (1)
    {
    	res = send(sys_guisock, s + written, length, 0);
    	if (res < 0)
    	{
    	    perror("pd output pipe");
    	    sys_bail();
    	}
    	else
    	{
    	    written += res;
    	    if (written >= length) return;
    	}
    }
}

/* LATER should do a bounds check -- but how do you get printf to do that?
    See also rtext_senditup() in this regard */

void sys_vgui(char *fmt, ...)
{
    int result, i;
    char buf[2048];
    va_list ap;

    va_start(ap, fmt);
    vsprintf(buf, fmt, ap);
    sys_gui(buf);
    va_end(ap);
}

static pid_t childpid;

#define FIRSTPORTNUM 5400

/* #define DEBUGTIME */
#ifdef DEBUGTIME
static t_systime fuck;
#endif



int sys_startgui(void)
{
    char cmdbuf[4*MAXPDSTRING];
    struct sockaddr_in server;
    int msgsock;
    char buf[15];
    int len = sizeof(server);
    int ntry = 0, portno = FIRSTPORTNUM;
    int xsock;
#ifdef UNIX
    	int pipeto[2], pipefrom[2];
#endif

#ifdef NT
    short version = MAKEWORD(2, 0);
    WSADATA nobby;

    if (WSAStartup(version, &nobby)) sys_sockerror("WSAstartup");
#endif

    /* create an empty FD poll list */
    sys_fdpoll = (t_fdpoll *)t_getbytes(0);
    sys_nfdpoll = 0;
    
    /* create a socket */
    xsock = socket(AF_INET, SOCK_STREAM, 0);
    if (xsock < 0) sys_sockerror("socket");
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;

    /* assign server port number */
    server.sin_port = portno;

    /* name the socket */
    while (bind(xsock, (struct sockaddr *)&server, sizeof(server)) < 0)
    {
#ifdef NT
    	int err = WSAGetLastError();
#endif
#ifdef UNIX
    	int err = errno;
#endif
    	if ((ntry++ > 20) || (err != EADDRINUSE))
    	{
	    perror("binding stream socket");
	    exit(1);
    	}
    	server.sin_port = ++portno;
    }
    
    fprintf(stderr, "port %d\n", portno);
    
    sys_socketreceiver = socketreceiver_new();

    inbinbuf = binbuf_new();

#ifdef UNIX
    if (pipe(pipeto) < 0) sys_sockerror("pipe");
    childpid = fork();
    if (childpid < 0) goto bad;
    else if (childpid) 	/* we're the parent */
    {
    }
    else    	    	    	/* we're the child */
    {
    	dup2(pipeto[0], 0);
    	close(pipeto[0]);
    	close(pipeto[1]);

#ifdef INSTALL_PREFIX
      strcpy(sys_progdir->s_name,INSTALL_PREFIX);
#endif

    	sprintf(cmdbuf,
    "TCL_LIBRARY=%s/tcl/library TK_LIBRARY=%s/tk/library %s/bin/pd-gui %d\n",
    	    sys_progdir->s_name, sys_progdir->s_name, sys_progdir->s_name,
    	    portno);
    	fprintf(stderr, "%s", cmdbuf);
    	execl("/bin/sh", "sh", "-c", cmdbuf, 0);
    	perror("pd: exec");
    	_exit(1);
    }
#endif
#ifdef NT
    {
    	char scriptbuf[MAXPDSTRING+30], wishbuf[MAXPDSTRING+30];
    	int spawnret;

    	fprintf(stderr, "%s\n", sys_progdir->s_name);
    	
    	strcpy(scriptbuf, sys_progdir->s_name);
    	strcat(scriptbuf, "/src/u_main.tk");
    	sys_bashfilename(scriptbuf, scriptbuf);
    	
    	strcpy(wishbuf, sys_progdir->s_name);
    	strcat(wishbuf, "/tcl/bin/wish80.exe");
    	sys_bashfilename(wishbuf, wishbuf);
    	
     	spawnret = _spawnl(P_NOWAIT, wishbuf, "wish80", scriptbuf, 0);
    	if (spawnret < 0)
    	{
    	    perror("spawnl");
    	    fprintf(stderr, "%s: couldn't load TCL\n", wishbuf);
    	    exit(1);
    	}
    }
#endif

    printf("Waiting for connection request... \n");
    if (listen(xsock, 5) < 0) sys_sockerror("listen");

    sys_guisock = accept(xsock, (struct sockaddr *) &server, &len);
    close(xsock);
    if (sys_guisock < 0) sys_sockerror("accept");
    sys_addpollfn(sys_guisock, (t_fdpollfn)socketreceiver_read,
    	sys_socketreceiver);

    printf("... connected\n");

    sys_vgui("pdtk_pd_startup\n"); 
    return (0);
bad:
    if (errno) perror("sys_startgui");
    else fprintf(stderr, "sys_startgui failed\n");
    return (1);

}



int sys_poll_togui(void)
{
    /* LATER use this to flush output buffer to gui */
    return (0);
}

int sys_pollgui(void)
{
    return(sys_pollfds(0) || sys_poll_togui());
}

/* LATER try to save dirty documents */
void sys_bail(void)
{
    sys_close_audio();
    exit(1);
}

void glob_quit(void *dummy)
{
    sys_vgui("exit\n");
}

