/*======================================================================
                    I N E T R A Y . C 
                    doc: Mon Feb 24 16:20:04 1992
                    dlm: Tue Aug 17 13:51:54 1993
                    (c) 1992 ant@julia
                    uE-Info: 414 31 T 0 0 72 10 2 8 ofnI
======================================================================*/

/*#define		PRINTSCHEDULE		/* print scheduling info */
/*#define		VERBOSE			/* print retrys */

#include	<stdio.h>		/* here we go! */
#include	<signal.h>
#include	<errno.h>
#include	<fcntl.h>
#include	<string.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<sys/time.h>
#include	<sys/param.h>
#include	<rpc/rpc.h>
#include	"inetray.h"
#include	"config.h"
#include	"common.h"
#include	"comm.h"
#include	"rayshade.h"
#include	"options.h"
#include	"picture.h"
#include	"viewing.h"
#include	"rcfile.h"
#include	"lbuffer.h"
#include	"version.h"
#include	"patchlevel.h"

static Pixel	**line;			/* array for picture data */
static int	bSize;			/* blocksize to trace in one sweep */
static int	lSize;			/* size of last block in frame */
static int	bNum;			/* number of blocks in frame */
static char	*completed;		/* array for completed blocks */
static int	doNext = 0;		/* next block to trace */
static int	doNBlk = 2;		/* number of blocks to send */
static char	pass2 = FALSE;		/* 2nd pass through picture */
static int	reDo = 0;		/* reDo # of outstanding blocks */
static int	nCompleted = 0;		/* number of lines completed per frame */
static int	bCompleted = 0;		/* number of blocks completed per frame */
static int	nSvc;			/* number of registered servers */
static int	nextB2W;		/* next block to write to pic file */
static int	bufPipe[2];		/* 2nd pipe to lBuffer */
static int	tty;			/* read from keyboard */

int	key;				/* session key (exported) */

extern	int errno;			/* error number to restart syscalls */

main(ac,av)
int ac; char *av[];
{
	static void initSelf(),initSubDiv(),initFrame();
	static void printStats(),distAll(),sigCaught(),writeBlocks();
	static int  initServers();
	int	fd;
	char	buf[64];
	
	if ((tty = open("/dev/tty",O_RDONLY)) < 0) {	/* keyboard */
		perror("open(/dev/tty)");
		exit(1);
	}

	fprintf(stderr,"%s [%s%d]\n",av[0],VERSION,PATCHLEVEL);
	initSelf(ac,av);			/* initialize self */
	PictureStart(av);			/* open picture file */

	key = (int)time(NULL);			/* session key */
	fprintf(stderr,"Session Key = %d\n",key);
	rename(KEYFILE,OLDKEYFILE);		/* save keyfile */
	fd = open(KEYFILE,O_CREAT|O_WRONLY,0600);
	if (fd < 0) {
		perror("open(KEYFILE)");
	} else {
		sprintf(buf,"%ld\n",key);	/* in ASCII */
		if (write(fd,buf,strlen(buf)) < 0) {
			perror("write");
			exit(1);
		}
		close(fd);
	}
	
	initServers(ac,av);			/* set up all servers */
	initSubDiv();				/* init frame subdivision */

	for (Options.framenum=Options.startframe;
	     Options.framenum<=Options.endframe;
 	     Options.framenum++) {
		initFrame(Options.framenum);	/* init & send first requests */
		if (Options.framenum>Options.startframe)
			PictureStart(av);	/* write picture in parallel */
		distAll();			/* distribute remaining lines */
		writeBlocks(TRUE);		/* write remaining blocks */
		fprintf(stderr,"Cleaning up... [kill");
		killAll();			/* kill running workers */
		fprintf(stderr,", flush");
		flushAll();			/* discard pending input */
		fprintf(stderr,", wait");
		waitAll();			/* wait for workers to come down */
		fprintf(stderr,"]");
		PictureFrameEnd();		/* end picture frame */
	}
	PictureEnd();
	Options.framenum = Options.endframe;	/* fix for stats */
	printStats();
	fprintf(stderr,"Exiting... [terminate"); 
	terminateAll();				/* now exit everything */
	fprintf(stderr,", close");
	closeAll();
	fprintf(stderr,"]\n");
	exit(0);
}

/*----------------------------------------------------------------------*/
/*		initSelf and supporting routines			*/
/*----------------------------------------------------------------------*/

static void busError()				/* SUN4 weird bus errors */
{
	fprintf(stderr,"Warning: SIGBUS caught!\n");
}

static void defSignals()			/* install default sigs */
{
	void	sigCaught();

	signal(SIGHUP,sigCaught);		/* all sigs which could mean */
	signal(SIGINT,sigCaught);		/* terminate */
	signal(SIGQUIT,sigCaught);
	signal(SIGABRT,sigCaught);
	signal(SIGTERM,sigCaught);
	signal(SIGTSTP,sigCaught);
	signal(SIGUSR1,sigCaught);
	signal(SIGUSR2,sigCaught);
	signal(SIGBUS,busError);		/* sometimes on SUN4s */
	signal(SIGPIPE,shutDown);		/* ^C in pipeline */
}

static void sigCaught()				/* abort signal rcved */
{
	char 	ans;
	
	fprintf(stderr,"\nSignal received...\n");
	if (getppid() != 1) {			/* parent alive */
		fprintf(stderr,"Really abort <y/n>?");
		do {				/* get ans from keyboard */
			if (read(tty,&ans,1) != 1) {
				perror("read(tty)");
				exit(1);
			}
		}
		while ((ans != 'y') && (ans != 'Y') &&
		       (ans != 'n') && (ans != 'N'));
		if ((ans == 'y') || (ans == 'Y')) shutDown(0);
	} else shutDown(0);
	defSignals();
}
	

static void initFrame(f)			/* init frame */
int f;
{
	int 	i;
	void 	dist1st();
	
	for (i=0; i<bNum; i++) 			/* clear completed flags */
		completed[i] = FALSE;
	doNext = nCompleted = 0;		/* reset counters */
	pass2 = FALSE;
	reDo = nextB2W = bCompleted = 0;
	fprintf(stderr,"\nFrame #%d:\n",f); 
	dist1st(f);				/* begin trace */
}

static void initSelf(ac,av)			/* init stuff */
int ac; char *av[];
{
	int bPid,i,lnSize,saveIn,p[2];

	readRc();				/* read .rc-file */
	
	if (pipe(p) < 0) {			/* set up input */
		perror("pipe");
		exit(1);
	}

	if (pipe(bufPipe) < 0) {		/* 2nd lBuffer pipe */
		perror("pipe");
		exit(1);
	}
	bPid = lPreBuffer(p,bufPipe);

	saveIn = dup(IN);			/* redirect stdin */
	close(IN);
	dup(p[IN]);
	close(p[IN]);

	RSInitialize(ac,av);			/* options, file, ... */
	kill(bPid,SIGUSR1);			/* kill if still killable */

	close(IN);				/* reset stdin */
	dup(saveIn);
	
	wait(NULL);				/* from popen() */
	defSignals();				/* signals */

	line = (Pixel **)malloc(Screen.ysize * sizeof(Pixel *));
	if (line == NULL) {
		fprintf(stderr,"malloc() failed\n");
		exit(1);
	}
	lnSize = Screen.xsize * sizeof(Pixel);
	for (i=0; i<Screen.ysize; i++) {
		line[i] = (Pixel *)malloc(lnSize);
		if (line[i] == NULL) {
			perror("malloc");
			exit(1);
		}
	}
}

static void initSubDiv()			/* init frame subdivision */
{
	bSize = Screen.ysize / nSvc
			/ blocksPerServer;	/* estimated size of blocks */
	if (bSize < minBlockSize)		/* assert bounds */
		bSize = minBlockSize;
	if (bSize > maxBlockSize)
		bSize = maxBlockSize;
	bNum = Screen.ysize / bSize + 1;	/* number of blocks */
	lSize = Screen.ysize-(bNum-1)*bSize;	/* remaining lines */
	if (lSize == 0) {			/* no remaining lines */
		bNum--;
		lSize = bSize;
	}

	completed = (char *)malloc(bNum);	/* supporting structures */
	if (completed == NULL) {
		fprintf(stderr,"malloc() failed\n");
		exit(1);
	}
	fprintf(stderr,"Frame Subdivision:");
	fprintf(stderr,"\n\t%d Blocks per Frame",bNum);
	fprintf(stderr,"\n\t%d Lines per Block",bSize);
	if (lSize != bSize)
		fprintf(stderr,"\n\t%d Lines in Last Block",lSize);
}
	

/*----------------------------------------------------------------------*/
/*		distribution 						*/
/*----------------------------------------------------------------------*/

static int findNext()				/* next blockNr */
{
	int i,sm= -1,nx= -1,next;

	for (i=0; i<bNum; i++)
		if (!completed[i]) {
			if (sm < 0) sm = i;
			if ((nx < 0) && (i >= doNext)) nx = i;
		}
	if (sm < 0) return FALSE;
	
	if (nx < 0) {				/* original next */
		if (!pass2) {
			fprintf(stderr,"\tEnd of Pass #1\n");
			pass2 = TRUE;
		}
		next = sm;
	} else next = nx;
	
	if (reDo > 0) {				/* rescheduled */
		reDo--;
		doNext = sm;
		doNBlk = 1;
	} else if (pass2 || next == bNum-1 || next == bNum-2) {
		doNext = next;
		doNBlk = 1;
	} else {
		doNext = next;
		doNBlk = 2;
	}
	return TRUE;
}

static void dist1st(frame)			/* dist 1 block p. server */
int frame;
{
	hInfo	*host;
	int 	res,rpcerr,nTrys;
	sfPrm	paramSF;
	
	for (host = hosts; host != NULL; host = host->next) {
		if (!findNext()) return;
		host->fDone = 0;
		if (host->broken) continue;
		paramSF.key = key;
		paramSF.fNr = frame;
		for (nTrys = 10; nTrys > 0; nTrys--) {
			rpcerr = clnt_call(host->clnt,STARTFRAME,
			  		   xdr_sfPrm,&paramSF,
					   xdr_int,&res,callTOut);
			if (rpcerr == RPC_SUCCESS) break;
			if (rpcerr == RPC_TIMEDOUT) {
#ifdef VERBOSE
				fprintf(stderr,"STARTFRAME retry to %s\n",host->name);
#endif
				host->reTrys++;
				if (nTrys > 1) continue;
			}
			fprintf(stderr,"startframe(): ");
			abort1(host);
		}
		host->request.key = key;
		host->request.bNr = doNext;
		host->request.bSz = (doNext == bNum-1) ? lSize : bSize;
		host->request.lNr = doNext*bSize;
		host->request.nBlocks = doNBlk;
		clnt_call(host->clnt,TRACEBLOCK,
				xdr_tbPrm,&(host->request),
				xdr_void,NULL,now);
#ifdef PRINTSCHEDULE
		fprintf(stderr,"\tBlock(s) #%d-#%d sent to %s(%d)\n",
				doNext,doNext+doNBlk-1,host->name,host->wid);
#endif
		doNext += doNBlk;
	}
}
		
static void printStats()			/* print statistics */
{
	hInfo	*host,*bHost;
	int	tot,all,best;
	char	nBuf[MAXHOSTNAMELEN+10];

	if (getppid() == 1) shutDown(0);
	signal(SIGINT,sigCaught); 
	fprintf(stderr,"\nStatistics:\n"); 
	tot = (Options.framenum-Options.startframe)*Screen.ysize+nCompleted;
	if (tot == 0) tot = 1;
	for (all=0; all<nSvc; all++) {
		best = 0;
		for (host = hosts; host != NULL; host = host->next) 
	    		if (host->done >= best) {
	    			best = host->done;
		    		bHost = host;
		    	}
		if (bHost->done == 0) bHost->done = -1;
		else bHost->done = -bHost->done;
		putc('\t',stderr);
		sprintf(nBuf,"%s[%d]",bHost->name,bHost->pid);
		fprintf(stderr,"\t%-15.14s",nBuf);
		if (bHost->broken) fprintf(stderr,"# ");
		else fprintf(stderr,"  ");
		if (bHost->reTrys != 0)
			fprintf(stderr,"%4d",bHost->reTrys);
		fprintf(stderr,"\t");
		fprintf(stderr,"%d \t(%d%%)\n",best,100*best/tot);
	}
	for (host = hosts; host != NULL; host = host->next)
		if (host->done == -1) host->done = 0;
		else host->done = -host->done;
	if (getppid() == 1) shutDown(0);
}

static void writeBlocks(flush)
char flush;
{
	int 	i,bSz,writ = 0;			/* write to file */
	hInfo 	*h;
	fd_set	sockSet;
	
	while ((nextB2W < bNum) && completed[nextB2W]) {
		if (writ == 0)
			fprintf(stderr,"\tWriting... ");
		bSz = (nextB2W == bNum-1) ? lSize : bSize;
		for (i=0; i<bSz; i++)
			PictureWriteLine(line[nextB2W*bSize+i]);
		nextB2W++; writ++;
		if (!flush) {			/* check for input */
		        FD_ZERO(&sockSet);
			for (h = hosts; h != NULL; h = h->next)
				if (!h->broken)
					FD_SET(h->sock,&sockSet);
		        if (select(sfdmax+1,&sockSet,
					(fd_set *)NULL,(fd_set *)NULL,
					&now) != 0) {
				fprintf(stderr,"[interrupted] ");
				break;
			}
		}
	}
	if (writ == 1)				/* report it */
		fprintf(stderr,"(1 block)\n");
	else if (writ > 1)
		fprintf(stderr,"(%d blocks)\n",writ);
}

static char getBlock(host)			/* get traced block */
hInfo *host;
{
	pixArr	xdrline;
	int	bNr,bSz,i;			/* block info */
	XDR 	*xdrs;
	hInfo	*h;
	
	xdrs = &(host->xdrs);
  	if (!xdrrec_skiprecord(xdrs)) { 	/* read next record*/
  		fprintf(stderr,"xdrrec_skiprecord(): ");
  		abort1(host);
		return FALSE;
	}
	if (!xdr_int(xdrs,&bNr)) {		/* get block number */
		abort1(host);
		return FALSE;
	}

	bSz = (bNr == bNum-1) ? lSize : bSize;	/* calc block size */

	for (i=0; i<bSz; i++) {			/* read all lines */
	        xdrline.pixArr_val = (xdrPix *)line[bNr*bSize+i];
	        if (!xdr_pixArr(xdrs,&xdrline)) {
        	        fprintf(stderr,"xdr_pixArr(): ");
			abort1(host);
			return FALSE;
		}
	}
	host->request.nBlocks--;
#ifdef PRINTSCHEDULE
	fprintf(stderr,"\tBlock #%d received from %s(%d)\n",
			bNr,host->name,host->wid);
	fprintf(stderr,"\t%d Block(s) remaining\n",
			host->request.nBlocks);
#endif
	if (!completed[bNr]) {			/* reporting & statistics */
		completed[bNr] = TRUE;
		if (nCompleted/Options.report_freq <
		    (nCompleted+bSz)/Options.report_freq) {
			fprintf(stderr,"\tFinished line %d\n",nCompleted+bSz);
		}
		nCompleted += bSz;		/* update stats */
		bCompleted++;
		host->done += bSz;
		host->fDone += bSz;
/*		if (bCompleted == bNum/2) {
			for (h = hosts; h != NULL; h = h->next)
				if (h->fDone == 0) reDo++;
			if (reDo > bNum/4) reDo = 0;
			reDo *= 2;
			if (reDo == 1)
				fprintf(stderr,"\t1 block rescheduled\n");
			else
				fprintf(stderr,"\t%d blocks rescheduled\n",reDo);
		} */
	}
	writeBlocks(FALSE);	
	return TRUE;
}

	
static void distAll()				/* block distribution routine */
{
    fd_set	sockSet;
    int		res;
    hInfo	*host;
    char	timedOut;

    do {					/* loop on all blocks */
	signal(SIGINT,printStats);
	
    restartSelect:
        FD_ZERO(&sockSet);			/* select() on sockets */
	for (host = hosts; host != NULL; host = host->next)
		if (!host->broken)
			FD_SET(host->sock,&sockSet);
	timedOut = TRUE;
        res = select(sfdmax+1,&sockSet,
			(fd_set *)NULL,(fd_set *)NULL,
			&resend);
        if (res < 0) {
        	if (errno == EINTR) goto restartSelect;
                perror("select");
		exit(1);
	}
	
	for (host = hosts; host != NULL; host = host->next) {
	    	if (!FD_ISSET(host->sock,&sockSet)) continue;
	    	if (host->broken) continue;
	    	timedOut = FALSE;
	    	if (!getBlock(host))		/* read one result */
			continue;		/* connection broken */
		if (host->request.nBlocks > 0)	/* not done yet */
			continue;
		if (!findNext()) return;	/* next line to trace */
		host->request.key = key;		/* next block */
		host->request.bNr = doNext;
		host->request.bSz = (doNext == bNum-1) ? lSize : bSize;
		host->request.lNr = doNext * bSize;
		host->request.nBlocks = doNBlk;
		(void)clnt_call(host->clnt,TRACEBLOCK,
				xdr_tbPrm,&(host->request),
				xdr_void,NULL,
				now);
#ifdef PRINTSCHEDULE
		fprintf(stderr,"\tBlock(s) #%d-#%d sent to %s(%d)\n",
				doNext,doNext+doNBlk-1,host->name,host->wid);
#endif
		doNext += doNBlk;
	}

    } FOREVER;					
}

/*----------------------------------------------------------------------*/
/*		initClients()						*/
/*----------------------------------------------------------------------*/

static char *makePStr(ac,av)			/* build param-str */
int ac; char *av[];
{
	int abp,ap,i;
	static char argbuf[256];

	for (abp=0,ap=0; ap < ac; ap++) {
		argbuf[abp++] = '\'';
		for (i=0; av[ap][i] != '\0'; i++)
			argbuf[abp++] = av[ap][i];
		argbuf[abp++] = '\'';
		argbuf[abp++] = ' ';
	}
	argbuf[abp] = '\0';
	return argbuf;
}

static int initServers(ac,av)			/* init all servers */
int ac; char *av[];
{
	char	cwd[MAXPATHLEN],hname[MAXHOSTNAMELEN];
	char	*getcwd();
	struct stat sBuf;

	if (gethostname(hname,MAXHOSTNAMELEN) < 0) {
		perror("gethostname");
		exit(1);
	}
	if (fstat(bufPipe[IN],&sBuf) < 0) {	/* buffer alive? */
		perror("fstat");
		exit(1);
	}
	fprintf(stderr,"Registering... ["); 
	registerSvc(hname,makePStr(ac,av),
		    getcwd(cwd,MAXPATHLEN),sBuf.st_size>0);
	nSvc = nRunning;
	if (nSvc == 0) {
		fprintf(stderr,"\nError: no Inetray Servers answering!\n");
		exit(1);
	}
	fprintf(stderr,"\n[%d workers registered]\n",nSvc);

	if (sBuf.st_size>0) {			/* send file */
		fprintf(stderr,"Distibuting StdIn...");
		distribute(bufPipe[IN]);
		fprintf(stderr,"\n");
	}
	wait(NULL);				/* wait for buffer */
}
