/*
 * mand.c
 */

#include <sys/wait.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <wms.h>
#include <wms/rnd.h>
#include <wms/clp.h>

#include "engine.h"
#include "colors.h"
#include "mand.h"


/**********************************************************************
 * Forward Declarations
 **********************************************************************/
static void  readCommandLine(Clp **clp, char *argv[]);
static void  stealth(int exitVal);
static void computePerf(void);
static void restartEngine(Mand *m);
static int  calcMaxZoom(int w, int h);
static void autozoom(Mand *m, int latecomers);
static void  zoomIn(Mand *m);
static void  zoomOut(Mand *m);


/**********************************************************************
 * Functions
 **********************************************************************/
int main(int argc, char *argv[]) {
  Mand m;
  int i, latecomers;
  time_t startTime, prevTime, currentTime;

  readCommandLine(&m.clp, argv);
  if (clp_getBool(m.clp, "version")) {
    printf("Backfract version " VERSION " by William Shubert - " DATE "\n");
    exit(0);
  }
  if (clp_getBool(m.clp, "time")) {
    computePerf();
    exit(0);
  }

  /*
   * Start up the engine!
   */
  if (clp_getBool(m.clp, "stealth"))
    stealth(0);
  nice(clp_getInt(m.clp, "nice"));
  m.rnd = rnd_create(time(NULL));
  io_init(&m.io, clp_getStr(m.clp, "display"),
	  clp_getInt(m.clp, "colors"), m.rnd);
  m.maxZoom = calcMaxZoom(m.io.w, m.io.h);
  restartEngine(&m);
  m.result = wms_malloc(m.io.w * 3 * sizeof(UInt8));
  m.delay = clp_getInt(m.clp, "delay");
  m.sels = wms_malloc((m.io.h - 2) * sizeof(Selector));
  prevTime = startTime = time(NULL);
  for (;;) {
    latecomers = 0;
    for (i = 0;  i < m.io.h;  ++i) {
      latecomers += engine(&m.e, i, m.result + (i % 3) * m.io.w);
      io_update(&m.io, i, m.result + (i % 3) * m.io.w);
      if (i > 1) {
	selector_read(&m.sels[i - 2],
		      m.result + ((i - 2) % 3) * m.io.w,
		      m.result + ((i - 1) % 3) * m.io.w,
		      m.result + ((i    ) % 3) * m.io.w,
		      m.io.w, m.rnd);
      }
    }
    autozoom(&m, latecomers);
    if (m.delay) {
      currentTime = time(NULL);
      if ((currentTime - startTime) / m.delay ==
	  (prevTime - startTime) / m.delay) {
	sleep(m.delay - ((currentTime - startTime) % m.delay));
	currentTime += m.delay - ((currentTime - startTime) % m.delay);
      }
      prevTime = currentTime;
    }
  }
  return(0);
}


static void  readCommandLine(Clp **clp, char *argv[]) {
  static const ClpSetup  commandLine[] = {
    CLPSETUP_MSG("Backfract " VERSION " (c) "DATE" William Shubert"),
    CLPSETUP_MSG("See \"http://www.hevanet.com/wms/comp/backfract/\" "
		 "for more info"),
    CLPSETUP_MSG(""),
    {"version,-version", "f", "Print version information",
     CLPSETUP_BOOL|CLPSETUP_NOSAVE, NULL},
    {"display", NULL, "Display to use", CLPSETUP_NOSAVE, NULL},
    {"colors", "128", "Number of colors to use (default = 128)",
     CLPSETUP_SHOWARG, NULL},
    {"delay", "900", "Minimum time (in seconds) between zooms (default = 900)",
     CLPSETUP_SHOWARG, NULL},
    {"nice", "10", "Amount to lower priority (default = 10)",
     CLPSETUP_SHOWARG, NULL},
    {"time", "f", "Don't draw anything; just time how long it takes to",
     CLPSETUP_BOOL, NULL},
    {NULL, "", "                       compute a 1024x1024 image."},
    {"stealth", "t", "Stealth mode", CLPSETUP_SHOWBOOL|CLPSETUP_BOOL, NULL},
    CLPSETUP_END};

  *clp = clp_create(commandLine);
  clp_rCmdline(*clp, argv);
}


static void  stealth(int exitVal)  {
  int pid;
#ifdef  TIOCNOTTY
  int  tty;
#endif
  
  pid = fork();
  if (pid < 0)  {
    perror("cgoban: fork() failed");
    return;
  } else if (pid > 0) {
    /* Parent just exits. */
    printf("%d\n", pid);
    exit(0);
  }
  /* Go stealth (ditch our controlling tty). */
#ifdef  TIOCNOTTY
  tty = open("/dev/tty", 0);
  if (tty < 0)
    return;
  ioctl(tty, TIOCNOTTY, 0);
  close(tty);
#endif
}


static void computePerf(void) {
  Engine e;
  struct timeval  start, end;
  struct timezone  tz;
  float  workTime;
  UInt8  *result;
  int  i;

  e.centerR = 0.0;
  e.centerI = 0.0;
  e.radius = 2.0;
  e.w = 1024;
  e.h = 1024;
  e.limit = 10000;
  result = wms_malloc(e.w * sizeof(UInt8));
  printf("Computing a %dx%d fractal with a max depth of %d.\n",
	 e.w, e.h, e.limit);
  gettimeofday(&start, &tz);
  for (i = 0;  i < e.h;  ++i) {
    engine(&e, i, result);
  }
  gettimeofday(&end, &tz);
  workTime = (float)(end.tv_sec - start.tv_sec) +
    1.0e-6 * (float)(end.tv_usec - start.tv_usec);
  printf("Computing this set consumed %g seconds of real time.\n",
	 workTime);
}


static void restartEngine(Mand *m) {
  m->e.centerR = 0.0;
  m->e.centerI = 0.0;
  m->e.radius = 2.0;
  m->e.w = m->io.w;
  m->e.h = m->io.h;
  m->e.limit = 100;

  m->currentZoom = 0;
  m->targetZoom = (rnd_int(m->rnd) % m->maxZoom) + 1;
}


static int  calcMaxZoom(int w, int h) {
  /*
   * We must use volatile here or else the compiler can optomize out our
   *   loop to detect the limits of our computability in such a way that
   *   our errors show up at the wrong time!  E.g., gcc on an x86 will hold
   *   all calculations in registers that are long doubles if we don't make
   *   these volatile.
   */
  volatile Real  x, t, two = 2.0;
  int  numRads;

  if (w > h)
    two *= (Real)h;
  else
    two *= (Real)w;
  for (numRads = 0, x = 2.0;;  ++numRads, x *= 0.5) {
    t = two + x;
    if (t == two)
      break;
  }
  return(numRads);
}


static void autozoom(Mand *m, int latecomers) {
  if (latecomers * 400 > m->e.w * m->e.h) {
    m->e.limit *= 2;
  } else if (latecomers * 2000 < m->e.w * m->e.h) {
    m->e.limit /= 2;
    if (m->e.limit < 100)
      m->e.limit = 100;
  }
  if (m->currentZoom == m->targetZoom) {
    if ((m->currentZoom != 0) &&
	((rnd_int(m->rnd) % 10) == 0)) {
      m->targetZoom = 0;
    } else {
      m->targetZoom = rnd_int(m->rnd) % (m->maxZoom - 1);
      if (m->targetZoom >= m->currentZoom)
	++m->targetZoom;
    }
    io_recolor(&m->io, m->rnd);
  }
  if (m->targetZoom > m->currentZoom) {
    zoomIn(m);
  } else {
    zoomOut(m);
  }
}


static void  zoomIn(Mand *m) {
  int  x, y, w = m->e.w, h = m->e.h;
  Real  pixelStep;

  if (w > h)
    pixelStep = m->e.radius / (Real)(h >> 1);
  else
    pixelStep = m->e.radius / (Real)(w >> 1);
  x = w;
  y = h;
  selector_pick(&x, &y, m->sels, h - 2, m->rnd);
  m->e.centerR += (Real)(x - (w/2)) * pixelStep;
  m->e.centerI += (Real)(y - (h/2)) * pixelStep;
  m->e.radius *= 0.5;
  ++m->currentZoom;
  return;
}


static void  zoomOut(Mand *m) {
  m->e.radius *= 2.0;
  --m->currentZoom;
}
