
#ifdef _plan9_
#include <u.h>
#include <libc.h>
#ifdef _plan9v2_
#include <stdarg.h>		/* not needed in plan9v3 */
#endif
#else
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#endif

#include <fconfig.h>
#include <filter.h>
#include <fractal.h>
#include <ui_helper.h>
#include <misc-f.h>
#include <xmenu.h>
#include <xerror.h>

#ifdef HAVE_GETTEXT
#include <libintl.h>
#else
#define gettext(STRING) STRING
#endif

#define SILENT 0
#define ERRORS 1
#define MESSAGES 2
#define ALL 3
static int noiselevel;
static struct uih_context *uih, *gc;
static int newline = 1;
static int interrupt = 0;
static void
error (CONST char *str)
{
  if (noiselevel < ERRORS)
    return;
  if (!gc)
    x_error (gettext ("Error: %s"), str);
  uih_error (gc, str);
}
static void
uiherror (struct uih_context *c)
{
  if (noiselevel < ERRORS)
    return;
  if (!gc)
    {
      uih_printmessages (c);
    }
  else
    uih_error (gc, uih->errstring);
}
static void
printmsg (const char *text, ...)
{
  va_list ap;
  if (noiselevel < MESSAGES)
    return;
  va_start (ap, text);
  if (!gc)
    {
      vprintf (text, ap);
      printf ("\n");
    }
  else
    {
      char s[256];
      vsprintf (s, text, ap);
      uih_message (gc, s);
      interrupt |= gc->interrupt |= gc->passfunc (gc, 1, s, 100);
      uih_clearwindows (gc);
    }
}
static int
passfunc (struct uih_context *c, int display, CONST char *text, float percent)
{
  if (noiselevel < ALL)
    return 0;
  if (gc)
    {
      if (gc->passfunc != NULL)
	interrupt |= gc->interrupt |=
	  gc->passfunc (gc, display, text, percent);
      uih_clearwindows (gc);
      return interrupt;
    }
  else if (display)
    {
      {
	if (newline)
	  printf ("\n"), newline = 0;
	printf ("\r %s %3.2f%% ", text, (double) percent);
	fflush (stdout);
      }
    }
  return 0;
}


struct frame_info
{
  vrect rect;
  number_t angle;
  char *name;
  int newimage;
};
static void
save_frame_dist (uih_context * c, int backward, struct frame_info *f1,
		 struct frame_info *f2)
{
  xio_file f;
  int x1, y1;
  char str[256];
  if (!(f2->rect.mc - f2->rect.nc) || !(f2->rect.mi - f2->rect.ni))
    return;
  /*printf ("Frame\n"); */
  sprintf (str, "%s.%c", f1->name, backward ? 'b' : 'p');
  f = xio_wopen (str);
  if (f == NULL)
    {
      x_error (gettext ("Cannot open motion vector file!"));
      return;
    }
  for (y1 = 0; y1 < (c->image->height + 7) / 8; y1++)
    {
      for (x1 = 0; x1 < (c->image->width + 7) / 8; x1++)
	{
	  number_t x, y;
	  number_t x2, y2;
	  number_t tmp;
	  x =
	    f1->rect.nc + (x1 * 8 + 4) * (f1->rect.mc -
					  f1->rect.nc) / c->image->width;
	  y =
	    f1->rect.ni + (y1 * 8 + 4) * (f1->rect.mi -
					  f1->rect.ni) / c->image->height;

	  if (f2->angle != f1->angle)
	    {
	      tmp =
		x * cos (f2->angle - f1->angle) - y * sin (f2->angle -
							   f1->angle);
	      y =
		x * sin (f2->angle - f1->angle) + y * cos (f2->angle -
							   f1->angle);
	      x = tmp;
	    }
	  x2 =
	    (x - f2->rect.nc) * c->image->width / (f2->rect.mc - f2->rect.nc);
	  y2 =
	    (y - f2->rect.ni) * c->image->height / (f2->rect.mi -
						    f2->rect.ni);
	  sprintf (str, "%3.2g %3.2g  ",
		   ((int) ((x2 - (x1 * 8 + 4)) * 10)) / 10.0,
		   ((int) ((y2 - (y1 * 8 + 4)) * 10)) / 10.0);
	  xio_puts (str, f);
	}
      xio_putc ('\n', f);
    }
  xio_close (f);
  /*printf ("Frameend\n"); */
}

#define MAXBFRAMES 5
#define IFRAMEDIST (27)
static int iframedist;
static int mvectors;
static xio_file patf;
static void
uih_encodeframe (int startpos, int endpos, struct frame_info *curframe)
{
  static int lastiframe = -200;
  static struct frame_info lastp;
  static struct frame_info bframes[MAXBFRAMES];
  static int nbframes = 0;
  int i;
  char type;
  if (!gc)
    printf (" motion");
  fflush (stdout);
  if (endpos > startpos + 4)
    {
      if (endpos - lastiframe > iframedist)
	type = 'I';
      else
	type = 'P';
    }
  else
    {
      if (endpos - lastiframe > iframedist)
	type = 'I';
      else
	type = (startpos - lastiframe) % 3 ? 'B' : 'P';
      if (startpos != endpos)
	type = 'P';
    }
  if (curframe->newimage)
    type = 'I';
  if (mvectors)
    {
      switch (type)
	{
	case 'I':
	  if (startpos)
	    save_frame_dist (uih, 0, curframe, &lastp);
	  lastp = *curframe;
	  break;
	case 'P':
	  save_frame_dist (uih, 0, curframe, &lastp);
	  lastp = *curframe;
	  break;
	case 'B':
	  save_frame_dist (uih, 0, curframe, &lastp);
	  if (nbframes < MAXBFRAMES)
	    {
	      bframes[nbframes] = *curframe;
	      bframes[nbframes].name = mystrdup (curframe->name);
	      nbframes++;
	    }
	  break;
	}
    }
  xio_putc (type, patf);
  if (type == 'I')
    lastiframe = startpos;
  if (startpos != endpos)
    {
      while (startpos != endpos)
	xio_putc ('p', patf), startpos++;
    }
  if (!gc)
    printf (" %c", type);
  fflush (stdout);
  if (mvectors)
    {
      if (type != 'B' && nbframes)
	{
	  if (!gc)
	    printf (" backframes");
	  fflush (stdout);
	  for (i = 0; i < nbframes; i++)
	    {
	      save_frame_dist (uih, 1, bframes + i, curframe);
	      fflush (stdout);
	      free (bframes[i].name);
	    }
	  nbframes = 0;
	}
    }
  xio_flush (patf);
}

extern struct filteraction antialias_filter;
int
uih_renderanimation (struct uih_context *gc1, CONST char *basename,
		     CONST xio_constpath animation, int width, int height,
		     float pixelwidth, float pixelheight, int frametime,
		     int type, int antialias, int slowmode, int letterspersec,
		     CONST char *catalog, int motionvectors, int iframedist2)
{
  struct palette *pal =
    createpalette (0, 0, type, 0, 0, NULL, NULL, NULL, NULL, NULL);
  struct image *img;
  xio_file of;
  FILE *f;
  xio_file af;
  char s[200];
  int lastframenum = -1;
  int aliasnum = 0;
  static char *saveddata;
  int newimage;
  int y;

  struct frame_info curframe;
  int framenum = 0;


  noiselevel = ALL;
  gc = gc1;
  if (gc)
    gc->incalculation = 1;

  mvectors = motionvectors;
  printmsg (gettext ("Vectors: %i"), motionvectors);
  if (iframedist2)
    iframedist = iframedist2;
  else
    iframedist = 27;

  printmsg (gettext ("Initializing"));
  if (!(type & (TRUECOLOR24 | TRUECOLOR | TRUECOLOR16 | GRAYSCALE)))
    antialias = 0;

  while (uih_filters[aliasnum] != &antialias_filter)
    aliasnum++;

  if (!pal)
    {
      error (gettext ("Cannot create palette"));
      if (gc)
	gc->incalculation = 0;
      return 0;
    }
  if (!pixelwidth)
    pixelwidth = 29.0 / width;
  if (!pixelheight)
    pixelheight = 21.5 / height;
  img = create_image_mem (width, height, 2, pal, pixelwidth, pixelheight);
  if (!img)
    {
      error (gettext ("Cannot create image\n"));
      if (gc)
	gc->incalculation = 0;
      destroypalette (pal);
      return 0;
    }
  saveddata = (char *) malloc (img->width * img->height * img->bytesperpixel);
  if (saveddata == NULL)
    {
      error (gettext ("Cannot create checking buffer!"));
      if (gc)
	gc->incalculation = 0;
      destroy_image (img);
      destroypalette (pal);
      return 0;
    }
  uih = uih_mkcontext (0, img, passfunc, NULL, NULL);
  if (!uih)
    {
      error (gettext ("Cannot create context\n"));
      if (gc)
	gc->incalculation = 0;
      destroy_image (img);
      destroypalette (pal);
      free (saveddata);
      return 0;
    }
  uih->fcontext->slowmode = 1;
  uih_constantframetime (uih, frametime);
  af = xio_ropen (animation);
  if (af == NULL)
    {
      error (gettext ("Cannot open animation file\n"));
      if (gc)
	gc->incalculation = 0;
      uih_freecontext (uih);
      destroy_image (img);
      destroypalette (pal);
      free (saveddata);
      return 0;
    }

  if (!gc)
    {
      printmsg (gettext ("Loading catalogs"));
      if (!gc)
	{
	  uih_loadcatalog (uih, "english");
	  if (uih->errstring)
	    {
	      uiherror (uih);
	      if (gc)
		gc->incalculation = 0;
	      uih_freecontext (uih);
	      destroy_image (img);
	      destroypalette (pal);
	      free (saveddata);
	      xio_close (af);
	      return 0;
	    }
	}
      if (catalog != NULL)
	uih_loadcatalog (uih, catalog);
      if (uih->errstring)
	{
	  uiherror (uih);
	  if (gc)
	    gc->incalculation = 0;
	  uih_freecontext (uih);
	  destroy_image (img);
	  destroypalette (pal);
	  free (saveddata);
	  if (!gc)
	    uih_freecatalog (uih);
	  xio_close (af);
	  return 0;
	}
      printmsg (gettext ("Processing command line options"));
      {
	CONST menuitem *item;
	dialogparam *d;
	while ((item = menu_delqueue (&d)) != NULL)
	  {
	    menu_activate (item, uih, d);
	  }
      }
      if (uih->errstring)
	{
	  uiherror (uih);
	  if (gc)
	    gc->incalculation = 0;
	  uih_freecontext (uih);
	  destroy_image (img);
	  destroypalette (pal);
	  free (saveddata);
	  if (!gc)
	    uih_freecatalog (uih);
	  xio_close (af);
	  return 0;
	}
    }

  printmsg (gettext ("Enabling animation replay\n"));

  uih_replayenable (uih, af, animation, 1);

  sprintf (s, "%s.par", basename);
  of = xio_wopen (s);
  if (of == NULL)
    {
      error (gettext ("Cannot open image file"));
      if (gc)
	gc->incalculation = 0;
      uih_freecontext (uih);
      destroy_image (img);
      destroypalette (pal);
      free (saveddata);
      if (!gc)
	uih_freecatalog (uih);
      return 0;
    }
  sprintf (s, "%s.pat", basename);
  patf = xio_wopen (s);
  if (patf == NULL)
    {
      error (gettext ("Cannot open pattern file"));
      if (gc)
	gc->incalculation = 0;
      uih_freecontext (uih);
      destroy_image (img);
      destroypalette (pal);
      free (saveddata);
      if (!gc)
	uih_freecatalog (uih);
      xio_close (of);
      return 0;
    }
  uih_letterspersec (uih, letterspersec);



  if (!gc)
    x_message (gettext ("Entering calculation loop!"));
  else
    printmsg (gettext ("Entering calculation loop!"));

  while ((uih->play || uih->display) && !interrupt)
    {
      if (uih->errstring)
	{
	  uiherror (uih);
	  if (gc)
	    gc->incalculation = 0;
	  uih_freecontext (uih);
	  destroy_image (img);
	  destroypalette (pal);
	  free (saveddata);
	  if (!gc)
	    uih_freecatalog (uih);
	  xio_close (of);
	  xio_close (patf);
	  return 0;
	}
      fflush (stdout);
      tl_process_group (syncgroup, NULL);
      uih_update (uih, 0, 0, 0);


      if (uih->display)
	{

	  if (lastframenum < framenum - 1)
	    {
	      if (lastframenum == framenum - 1)
		printmsg (gettext ("Frame %i skipped."), framenum - 1);
	      else
		printmsg (gettext ("Frames %i - %i skipped."), lastframenum,
			  framenum - 1);
	    }

	  printmsg (gettext ("Frame %4i: "), framenum);

	  newline = 1;
	  newimage = 0;
	  if (uih->recalculatemode > 0)
	    {
	      if (!gc)
		printf ("calculating"), fflush (stdout);
	      if (slowmode)
		uih_newimage (uih), uih->fcontext->version++;
	    }
	  if (antialias && !uih->filter[aliasnum])
	    {
	      if (!gc)
		printf ("antialias ");
	      uih->aliasnum = aliasnum;
	      uih_enablefilter (uih, aliasnum);
	    }
	  uih_prepare_image (uih);

	  if (!gc)
	    printf (" rendering");
	  fflush (stdout);
	  uih_drawwindows (uih);

	  y = 0;
	  if (lastframenum >= 0)
	    {
	      for (; y < img->height; y++)
		if (memcmp
		    (saveddata + img->width * img->bytesperpixel * y,
		     uih->image->currlines[y],
		     img->width * img->bytesperpixel))
		  break;
	    }


	  if (y != img->height)
	    {
	      for (; y < img->height; y++)
		memcpy (saveddata + img->width * img->bytesperpixel * y,
			uih->image->currlines[y],
			img->width * img->bytesperpixel);
	      if (framenum)
		uih_encodeframe (lastframenum, framenum - 1, &curframe);
	      if (!gc)
		printf (" saving");
	      fflush (stdout);
	      sprintf (s, "%s%04i.png", basename, framenum);
	      curframe.rect = uih->fcontext->rs;
	      curframe.angle = uih->fcontext->angle;
	      curframe.name = s;
	      curframe.newimage = newimage;


	      f = fopen (s, "wb");
	      if (f == NULL)
		{
		  error (gettext ("Cannot open image file"));
		  if (gc)
		    gc->incalculation = 0;
		  uih_freecontext (uih);
		  destroy_image (img);
		  destroypalette (pal);
		  free (saveddata);
		  if (!gc)
		    uih_freecatalog (uih);
		  xio_close (of);
		  xio_close (patf);
		  return 0;
		}
	      writepng (f, uih->image);
	      printmsg (gettext (" done."));
	      uih_displayed (uih);
	      lastframenum = framenum;
	    }
	  else
	    {
	      printmsg (gettext (" skipping..."));
	      uih_displayed (uih);
	    }
	}
      xio_puts (s, of);
      xio_puts ("\n", of);
      xio_flush (of);
      framenum++;
    }
  curframe.newimage = 1;
  if (framenum)
    uih_encodeframe (lastframenum, framenum - 1, &curframe);
  if (uih->errstring)
    {
      uiherror (uih);
      if (gc)
	gc->incalculation = 0;
      uih_freecontext (uih);
      destroy_image (img);
      destroypalette (pal);
      free (saveddata);
      if (!gc)
	uih_freecatalog (uih);
      xio_close (of);
      xio_close (patf);
      return 0;
    }
  xio_close (of);
  free (saveddata);
  xio_close (patf);
  uih_freecontext (uih);
  destroy_image (img);
  destroypalette (pal);
  if (interrupt)
    error (gettext ("Calculation interrupted"));
  else
    {
      if (!gc)
	x_message (gettext ("Calculation finished"));
      else
	printmsg (gettext ("Calculation finished"));
    }
  if (gc)
    gc->incalculation = 0;
  if (!gc)
    uih_freecatalog (uih);
  return 1;
}

int
uih_renderimage (struct uih_context *gc1, xio_file af,
		 CONST xio_constpath path, struct image *img, int antialias,
		 CONST char *catalog, int noise)
{
  int aliasnum = 0;
  int ok = 1;
  noiselevel = noise;
  gc = gc1;
  if (gc)
    gc->incalculation = 1;

  while (uih_filters[aliasnum] != &antialias_filter)
    aliasnum++;

  uih = uih_mkcontext (0, img, passfunc, NULL, NULL);
  if (!uih)
    {
      error (gettext ("Cannot create context\n"));
      if (gc)
	gc->incalculation = 0;
      return 0;
    }
  uih->fcontext->slowmode = 1;
  uih_constantframetime (uih, 1000000 / 10);

  if (!gc)
    {
      printmsg (gettext ("Loading catalogs"));
      uih_loadcatalog (uih, "english");
      if (uih->errstring)
	{
	  fprintf (stderr, uih->errstring);
	  uih_clearmessages (uih);
	  uih->errstring = NULL;
	}
      if (catalog != NULL)
	uih_loadcatalog (uih, catalog);
      if (uih->errstring)
	{
	  fprintf (stderr, uih->errstring);
	  uih_clearmessages (uih);
	  uih->errstring = NULL;
	}
      if (uih->errstring)
	{
	  uih_freecatalog (uih);
	  uih_freecontext (uih);
	  uiherror (uih);
	  if (gc)
	    gc->incalculation = 0;
	  return 0;
	}
    }

  uih_load (uih, af, path);
  if (uih->errstring)
    {
      uiherror (uih);
      uih_freecatalog (uih);
      uih_freecontext (uih);
      if (gc)
	gc->incalculation = 0;
      return 0;
    }
  printmsg (gettext ("Entering calculation loop!"));

  tl_process_group (syncgroup, NULL);
  uih_update (uih, 0, 0, 0);

  uih_newimage (uih), uih->fcontext->version++;
  if (antialias && !uih->filter[aliasnum])
    {
      uih->aliasnum = aliasnum;
      uih_enablefilter (uih, aliasnum);
    }
  uih_prepare_image (uih);
  if (uih->errstring)
    ok = 0;
  uih_drawwindows (uih);
  if (uih->errstring)
    ok = 0;
  uih_freecontext (uih);
  uih_freecatalog (uih);
  if (interrupt)
    error (gettext ("Calculation interrupted"));
  else
    {
      printmsg (gettext ("Calculation finished"));
    }
  if (gc)
    gc->incalculation = 0;
  return 1;
}
