/* main.c,v 1.2 1995/04/08 19:54:42 explorer Exp */

/*
 * Copyright (C) 1989, 1991, Craig E. Kolb
 * All rights reserved.
 *
 * This software may be freely copied, modified, and redistributed
 * provided that this copyright notice is preserved on all copies.
 *
 * You may not distribute this software, in whole or in part, as part of
 * any commercial product without the express consent of the authors.
 *
 * There is no warranty or other guarantee of fitness of this software
 * for any purpose.  It is provided solely "as is".
 *
 */

char rcsid_main_c[] =
"main.c,v 1.2 1995/04/08 19:54:42 explorer Exp";

#include "rayshade.h"
#include "options.h"
#include "stats.h"
#include "viewing.h"
#include "picture.h"
#include "nspar.h"
#include "raytrace.h"

#ifdef AMIGA
#ifndef __GNUC__
long InitTime[2];
#endif
#endif

#ifdef NSPARALLEL
void server_render _PROTO((int, int, int));
void client_render _PROTO((char **));
void CheckWriteQueue _PROTO((char *[]));
NSWorkBuffer *AssignWork _PROTO((int));
void AddToWriteQueue _PROTO((NSWorkBuffer *));
#endif

void serial_render _PROTO((char **));

extern Geom *World;

int
#ifdef LINDA
rayshade_main(argc, argv)
#else
main(argc, argv)
#endif
  int argc;
  char **argv;
{
#ifdef NSPARALLEL
  int frame, start, end;
  int server_count;
#endif

#ifdef AMIGA
#ifndef __GNUC__
  timer(InitTime);
#endif
  ReducePriority();
#endif
#ifdef LINDA
  Options.workernum = 0;	/* we're the supervisor */
#endif
  
  RSInitialize(argc, argv);
  
#ifndef NSPARALLEL
  serial_render(argv);
#else
  if (Options.mode == MODE_STANDALONE) {
    serial_render(argv);
    exit(0);
  }
  
  /*
   * If we are a server, get work from the client and process it.  Loop
   * here until the client is done with us, so we don't have to reload the
   * scene information for each block.
   */
  if (Options.mode == MODE_SERVER) {
    while (get_work(Options.tcpfd, &frame, &start, &end) == 0)
      server_render(frame, start, end);
    fprintf(Stats.fstats, "Server exiting\n");
    exit(0);
  }
  
  if (Options.mode == MODE_CLIENT) {
    server_count = init_servers();
    fprintf(Stats.fstats, "We have %d servers\n", server_count);
    
    if (server_count <= 0) {
      RLerror(RL_ABORT, "No servers found!\n");
      exit(1);
    }
    
    client_render(argv);
    exit(0);
  }
#endif

#ifdef	lint
  return 0;
#else
  exit(0);
#endif
}

#ifdef NSPARALLEL
void
server_render(frame, start, end)
     int frame, start, end;
{
  /*
   * Start the frame
   */
  RSStartFrame(frame);
  
  Options.window_set = 1;
  Options.window[LOW][X] = 0;
  Options.window[HIGH][X] = Screen.xres - 1;
  Options.window[LOW][Y] = start;
  Options.window[HIGH][Y] = end;
  
  ViewingSetup(); /* XXX Should we be calling this? */
  
  if (Options.verbose) {
    /* World object info. */
    AggregatePrintInfo(World, Stats.fstats);
    /* Print info about rendering options and the like. */
    RSOptionsList();
  }
  
  /*
   * Tell the client we are willing to do its bidding.
   */
  PictureBufferStart();
  
  /*
   * We do what we're told.
   */
  raytrace();
  
  /*
   * and finally, tell the client we are done.
   */
  PictureBufferEnd();
}

/*
 * This routine knows entirely too much about the internals I believe.
 * Perhaps some functions should be written to abstract this to the correct
 * locations in picture.c, nspar.c, etc?
 */
void
client_render(argv)
     char *argv[];
{
  uint32 curline, curframe;
  uint32 waitframe, waitline;
  int cc;
  NetBuf nb;
  NSWorkBuffer *wbp;
  NSServer *srv;
  int nfds;
  fd_set rfd;
  int count = 0;
  
  curline = 0;  /* starting line to give to a server */
  curframe = 0;
  waitline = 0;  /* the chunk of data we are waiting on to write to the */
  waitframe = 0; /* file ... */
  
  PictureStart(argv);
  
  /*
   * Scan through the servers we have and assign work.
   */
  for (srv = Servers; srv != NULL ; srv = srv->next) {
    fprintf(stderr, "Adding host %s, fd %d\n", srv->name, srv->fd);
    srv->work = AssignWork(srv->fd);
    wbp = srv->work;
    
    /*
     * Did we actually get a work order?
     */
    if (wbp == NULL) {
      fprintf(stderr, "Nothing for %s to do!\n", srv->name);
      NetSendTerminate(srv->fd);
      close(srv->fd);
      srv->fd = -1;
    } else
      count++;
  }
  
  /*
   * Loop here, receiving and assigning new data to servers, until
   * we have assigned all the work we have and each server has responded
   * (or died)
   */
  while (count) {
    /*
     * set up for select call
     */
    nfds = getdtablesize();
    FD_ZERO(&rfd);
    for (srv = Servers; srv != NULL ; srv = srv->next)
      if (srv->fd >= 0)
	FD_SET(srv->fd, &rfd);
    
    if ((cc = select(nfds, &rfd, NULL, NULL, NULL)) < 0)
      RLerror(RL_PANIC, "Error in select call: %s\n", strerror(errno));
    
    /*
     * Scan through the list of all servers, finding ones which may have
     * returned something.
     */
    for (srv = Servers; srv != NULL ; srv = srv->next) {
      if (srv->fd >= 0)
	if (FD_ISSET(srv->fd, &rfd)) {
	  wbp = srv->work;
#ifdef PAR_DEBUG
	  fprintf(stderr, "Data from %s\n", srv->name);
#endif
	  /*
	   * See what the server has in store for us...
	   */
	  cc = NetReceive(srv->fd, &nb);
	  if (cc <= 0) {
	    RLerror(RL_WARN, "Closing %s due to read of %d\n", srv->name, cc);
	    /*
	     * add whatever this server was doing to the front of the
	     * pending queue, so the next AssignWork() call will get
	     * this work.
	     */
	    wbp->next = WorkBufferPendingList;
	    WorkBufferPendingList = wbp;
	    close(srv->fd);
	    srv->fd = -1;
	    count--;
	    RLerror(RL_WARN, "Only %d clients remain...\n", count);
	  } else {
	    /*
	     * no error, let's see what the server has to say.
	     */
	    switch (nb.data.op) {
	    case OP_PIC_START:
#ifdef PAR_DEBUG
	      fprintf(stderr, "OP_PIC_START -- starting trace\n");
#endif
	      break;
	    case OP_PIC_LINE:
#ifdef PAR_DEBUG
	      fprintf(stderr, "OP_PIC_LINE -- line %d\n", 
		      wbp->start + wbp->lines);
#endif
	      NetReceivePictureLine(srv->fd, &nb, wbp->buf[wbp->lines]);
	      wbp->lines++;
	      break;
	    case OP_PIC_END:
	      fprintf(stderr, "%s finished frame %d, lines %d - %d\n\n",
		      srv->name, 
		      srv->work->frame, srv->work->start, srv->work->end);
	      AddToWriteQueue(srv->work);
	      CheckWriteQueue(argv);
	      srv->work = AssignWork(srv->fd);
	      if (srv->work == NULL) {
		NetSendTerminate(srv->fd);
		close(srv->fd);
		srv->fd = -1;
		count--;
	      }
	      break;
	    }
	  }
	}
    }
  }
  
  PictureEnd();
}


static uint32 CurrentLine = 0;
static uint32 CurrentFrame = 0;

NSWorkBuffer *
AssignWork(fd)
     int fd;
{
  NSWorkBuffer *wbp;
  
  uint32 ending_line;
  
  if (WorkBufferPendingList == NULL) {
    if (CurrentFrame > Options.endframe) {
      fprintf(stderr, "No more work to assign...\n");
      return NULL;
    }

    wbp = GetFreeWorkBuffer();
    wbp->frame = CurrentFrame;
    wbp->start = CurrentLine;
    wbp->lines = 0; /* no lines there, yet... */
    ending_line = CurrentLine + (SERVER_BUFFERS - 1);
    if (ending_line > Screen.ysize - 1)
      ending_line = Screen.ysize - 1;
    wbp->end = ending_line;
    
    CurrentLine = ending_line + 1;
    if (CurrentLine >= Screen.ysize) {
      CurrentLine = Screen.miny;
      CurrentFrame++;
    }

  } else {
    wbp = WorkBufferPendingList;
    WorkBufferPendingList = wbp->next;
    wbp->lines = 0;  /* start over from scratch, for now */
  }
  
  fprintf(stderr, "Assigning frame %d, lines %d - %d\n",
	  wbp->frame, wbp->start, wbp->end);
  NetSendRender(fd, wbp->frame, wbp->start, wbp->end);
  
  return (wbp);
}


void
AddToWriteQueue(wbp)
     NSWorkBuffer *wbp;
{
  NSWorkBuffer *wbp1, *wbp2;
  
  if (WorkBufferWriteList == NULL) {
    WorkBufferWriteList = wbp;
    wbp->next = NULL;
    return;
  }
  
  wbp1 = WorkBufferWriteList;
  wbp2 = NULL;
  
  while(wbp1 != NULL && 
	(wbp1->frame < wbp->frame || 
	 ((wbp1->frame == wbp->frame) && (wbp1->start < wbp->start)))) {
    wbp2 = wbp1;
    wbp1 = wbp1->next;
  }
  
  /*
   * Did we get to the end of the list without finding somewhere to put
   * this sucker?
   */
  if (wbp1 == NULL) {
    wbp2->next = wbp;
    wbp->next = NULL;
    return;
  }
  
  /*
   * Nope, this means wbp2 points to the PREVIOUS entry.
   */
  if (wbp2 == NULL) {
    wbp->next = WorkBufferWriteList;
    WorkBufferWriteList = wbp;
    return;
  }
  
  /*
   * well, we must be in the middle somewhere.
   */
  wbp->next = wbp1;
  wbp2->next = wbp;
}

static uint32 WaitforFrame = 0;
static uint32 WaitforLine = 0;

void
CheckWriteQueue(argv)
     char *argv[];
{
  NSWorkBuffer *wbp;
  int i;
  
  fprintf(stderr, "Waiting on frame %d line %d\n", WaitforFrame, WaitforLine);
  
  wbp = WorkBufferWriteList;
  while(wbp != NULL) {
    fprintf(stderr, "Write Queue: frame %d, lines %d - %d\n",
	    wbp->frame, wbp->start, wbp->end);
    wbp = wbp->next;
  }
  
  /*
   * Anything to do?
   */
  wbp = WorkBufferWriteList;
  if (wbp == NULL)
    return;
  
  /*
   * Check to see if this is what we were waiting for
   */
  while ((wbp != NULL) 
	 && (wbp->frame == WaitforFrame) && (wbp->start == WaitforLine)) {
    /*
     * If we get here, we have something to write out.
     */
    for (i = 0 ; i < wbp->lines ; i++)
      PictureWritePreprocessedLine(wbp->buf[i]);
    
    WaitforLine += wbp->lines;

    if (WaitforLine >= Screen.ysize) {
      WaitforLine = Screen.miny;
      WaitforFrame++;
      if (WaitforFrame <= Options.endframe) {
	PictureFrameEnd();
	RSStartFrame(i);
	PictureStart(argv);
      }
    }
    
    /*
     * move to next on the write queue
     */
    WorkBufferWriteList = wbp->next;
    
    wbp->next = WorkBufferFreeList;
    WorkBufferFreeList = wbp;
    wbp = WorkBufferWriteList;
  }
}

#endif

void
serial_render(argv)
     char *argv[];
{
  Float utime, stime, lasttime;
  int i;
  
  /*
   * Start the first frame.
   */
  RSStartFrame(Options.startframe);
  /*
   * Print more information than we'll ever need to know...
   */
  if (Options.verbose) {
    /* World object info. */
    AggregatePrintInfo(World, Stats.fstats);
    /* Print info about rendering options and the like. */
    RSOptionsList();
  }
  /*
   * Start new picture.
   */
  PictureStart(argv);
  /*
   * Print preprocessing time.
   */
  RSGetCpuTime(&utime, &stime);
  fprintf(Stats.fstats, "Preprocessing time:\t");
  fprintf(Stats.fstats, "%2.2fu  %2.2fs\n", utime, stime);
  fprintf(Stats.fstats, "Starting trace.\n");
  (void) fflush(Stats.fstats);
  lasttime = utime + stime;
  /*
   * Render the first frame
   */
  raytrace();
  /*
   * Render the remaining frames.
   */
  for (i = Options.startframe + 1; i <= Options.endframe; i++) {
    PictureFrameEnd();		/* End the previous frame */
    RSGetCpuTime(&utime, &stime);
    fprintf(Stats.fstats, "Total CPU time for frame %d: %2.2f \n",
	    i - 1, utime + stime - lasttime);
    PrintMemoryStats(Stats.fstats);
    (void) fflush(Stats.fstats);
    lasttime = utime + stime;
    RSStartFrame(i);
    if (Options.verbose) {
      AggregatePrintInfo(World, Stats.fstats);
      (void) fflush(Stats.fstats);
    }
    PictureStart(argv);
    raytrace();
  }
  /*
   * Close the image file.
   */
  PictureFrameEnd();		/* End the last frame */
  PictureEnd();
  StatsPrint();
}
