#ifndef lint
static const char	RCSid[] = "$Id: devcomm.c,v 2.19 2016/08/18 00:52:48 greg Exp $";
#endif
/*
 *  devcomm.c - communication routines for separate drivers.
 *
 *  External symbols declared in driver.h
 */

#include "copyright.h"

#include <sys/types.h>
#if !defined(_WIN32) && !defined(_WIN64)
#include <sys/wait.h>
#endif

#include "paths.h"
#include "platform.h"
#include "standard.h"
#include "driver.h"

#ifndef DEVPATH
#define DEVPATH		getenv("PATH")	/* device search path */
#endif

FILE	*devin, *devout;
int	devchild;

static struct driver * final_connect(void);
static void mygets(char	*s, FILE	*fp);
static void myputs(char	*s, FILE	*fp);
static void reply_error(char	*routine);
static void getstate(void);

static dr_closef_t comm_close;
static dr_clearf_t comm_clear;
static dr_paintrf_t comm_paintr;
static dr_getcurf_t comm_getcur;
static dr_comoutf_t comm_comout;
static dr_cominf_t comm_comin;
static dr_flushf_t comm_flush;

struct driver	comm_driver = {
	comm_close, comm_clear, comm_paintr, comm_getcur,
	comm_comout, comm_comin, comm_flush
};


static struct driver *
final_connect(void)				/* verify and initialize connection */
{
	putw(COM_SENDM, devout);
	fflush(devout);
	if (getw(devin) != COM_RECVM)
		return(NULL);
						/* get driver parameters */
	getstate();
						/* set error vectors */
	erract[COMMAND].pf = comm_comout;
	/*					doesn't work with raypcalls.c
	if (erract[WARNING].pf != NULL)
		erract[WARNING].pf = comm_comout;
	*/
	return(&comm_driver);
}


struct driver *
slave_init(			/* run rview in slave mode */
	char	*dname,
	char	*id
)
{
	devchild = -1;				/* we're the slave here */
	devout = stdout;			/* use standard input */
	devin = stdin;				/* and standard output */
	return(final_connect());		/* verify initialization */
}


struct driver *
comm_init(			/* set up and execute driver */
	char	*dname,
	char	*id
)
{
	char	*dvcname;
	int	p1[2], p2[2];
	char	pin[16], pout[16];
						/* find driver program */
	if ((dvcname = getpath(dname, DEVPATH, X_OK)) == NULL) {
		eputs(dname);
		eputs(": not found\n");
		return(NULL);
	}
#ifdef RHAS_FORK_EXEC
						/* open communication pipes */
	if (pipe(p1) == -1 || pipe(p2) == -1)
		goto syserr;
	if ((devchild = fork()) == 0) {	/* fork driver process */
		close(p1[1]);
		close(p2[0]);
		sprintf(pin, "%d", p1[0]);
		sprintf(pout, "%d", p2[1]);
		execl(dvcname, dname, pin, pout, id, NULL);
		perror(dvcname);
		_exit(127);
	}
	if (devchild == -1)
		goto syserr;
	close(p1[0]);
	close(p2[1]);
	/*
	 * Close write stream on exec to avoid multiprocessing deadlock.
	 * No use in read stream without it, so set flag there as well.
	 */
	fcntl(p1[1], F_SETFD, FD_CLOEXEC);
	fcntl(p2[0], F_SETFD, FD_CLOEXEC);
	if ((devout = fdopen(p1[1], "w")) == NULL)
		goto syserr;
	if ((devin = fdopen(p2[0], "r")) == NULL)
		goto syserr;
	return(final_connect());		/* verify initialization */
syserr:
	perror(dname);
	return(NULL);

#else	/* ! RHAS_FORK_EXEC */

	eputs(dname);
	eputs(": no fork/exec\n");
	return(NULL);

#endif	/* ! RHAS_FORK_EXEC */
}


static void
comm_close(void)			/* done with driver */
{
	int	pid;

	erract[COMMAND].pf = NULL;		/* reset error vectors */
	if (erract[WARNING].pf != NULL)
		erract[WARNING].pf = wputs;
	fclose(devout);
	fclose(devin);
	if (devchild < 0)
		return;
#if !defined(_WIN32) && !defined(_WIN64)
	while ((pid = wait(0)) != -1 && pid != devchild)
		;
#endif
}


static void
comm_clear(				/* clear screen */
	int	xres,
	int	yres
)
{
	putc(COM_CLEAR, devout);
	putw(xres, devout);
	putw(yres, devout);
	fflush(devout);
}


static void
comm_paintr(	/* paint a rectangle */
	COLOR	col,
	int	xmin,
	int	ymin,
	int	xmax,
	int	ymax
)
{
	putc(COM_PAINTR, devout);
	putbinary(col, sizeof(COLOR), 1, devout);
	putw(xmin, devout);
	putw(ymin, devout);
	putw(xmax, devout);
	putw(ymax, devout);
}


static void
comm_flush(void)				/* flush output to driver */
{
	putc(COM_FLUSH, devout);
	fflush(devout);
	if (getc(devin) != COM_FLUSH)
		reply_error("flush");
	getstate();
}


static int
comm_getcur(			/* get and return cursor position */
	int	*xp,
	int	*yp
)
{
	int	c;

	putc(COM_GETCUR, devout);
	fflush(devout);
	if (getc(devin) != COM_GETCUR)
		reply_error("getcur");
	c = getc(devin);
	*xp = getw(devin);
	*yp = getw(devin);
	return(c);
}


static void
comm_comout(			/* print string to command line */
	char	*str
)
{
	putc(COM_COMOUT, devout);
	myputs(str, devout);
	if (str[strlen(str)-1] == '\n')
		fflush(devout);
}


static void
comm_comin(			/* read string from command line */
	char	*buf,
	char	*prompt
)
{
	putc(COM_COMIN, devout);
	if (prompt == NULL)
		putc(0, devout);
	else {
		putc(1, devout);
		myputs(prompt, devout);
	}
	fflush(devout);
	if (getc(devin) != COM_COMIN)
		reply_error("comin");
	mygets(buf, devin);
	getstate();
}


static void
mygets(				/* get string from file (with nul) */
	char	*s,
	FILE	*fp
)
{
	int	c;

	while ((c = getc(fp)) != EOF)
		if ((*s++ = c) == '\0')
			return;
	*s = '\0';
}


static void
myputs(				/* put string to file (with nul) */
	char	*s,
	FILE	*fp
)
{
	do
		putc(*s, fp);
	while (*s++);
}


static void
reply_error(			/* what should we do here? */
	char	*routine
)
{
	eputs(routine);
	eputs(": driver reply error\n");
	quit(1);
}


static void
getstate(void)				/* get driver state variables */
{
	getbinary((char *)&comm_driver.pixaspect,
			sizeof(comm_driver.pixaspect), 1, devin);
	comm_driver.xsiz = getw(devin);
	comm_driver.ysiz = getw(devin);
	comm_driver.inpready = getw(devin);
}
