/* 
 *     XaoS, a fast portable realtime fractal zoomer 
 *                  Copyright (C) 1996,1997 by
 *
 *      Jan Hubicka          (hubicka@paru.cas.cz)
 *      Thomas Marsh         (tmarsh@austin.ibm.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*#define STATISTICS */
#include <aconfig.h>
#include <string.h>
#include <config.h>
#include <fconfig.h>
#ifdef _plan9_
#include <u.h>
#include <libc.h>
#include <stdio.h>
#else
#include <stdio.h>
#ifndef _MAC
#ifndef NO_MALLOC_H
#include <malloc.h>
#endif
#endif
#ifndef _plan9_
#include <limits.h>
#include <assert.h>
#include <math.h>
#endif
#endif
#ifdef __EMX__
#include <float.h>
#include <sys/cdefs.h>
#endif
#include <filter.h>
#include <complex.h>
#include <plane.h>
#include "../include/timers.h"
#ifdef __GNUC__
#ifdef __i386__
#ifndef PC_64
#include <i386/ctrl87.h>
#endif
#endif
#endif
#ifdef __alpha__
#ifdef __linux__
#include <asm/fpu.h>
#endif
#endif
#ifndef M_PI
#define M_PI 3.1415
#endif
#include <xerror.h>

struct symetryinfo2 cursymetry;
struct palette cpalette;
struct image cimage;
struct fractal_context cfractalc;
struct formula cformula;

static symetry2 sym_lines[100];


static void
precalculate_rotation (fractal_context * c)
{
  c->sin = sin ((c->angle) * M_PI / 180);
  c->cos = cos ((c->angle) * M_PI / 180);
}
static void
recalc_view (fractal_context * c)
{
  number_t
    xs = c->s.rr, ys = c->s.ri * c->windowwidth / c->windowheight,
    xc = c->s.cr, yc = c->s.ci, size;
  precalculate_rotation (c);
  rotate (*c, xc, yc);
  /*assert(c->s.rr >= 0);
     assert(c->s.ri >= 0); */

  xs = myabs (xs);		/*do not crash in owerflowing cases */
  ys = myabs (ys);
  if (xs > ys)
    size = xs;
  else
    size = ys;
  c->rs.nc = xc - size / 2;
  c->rs.mc = xc + size / 2;
  c->rs.ni = yc - size * c->windowheight / c->windowwidth / 2;
  c->rs.mi = yc + size * c->windowheight / c->windowwidth / 2;
  if (c->rs.nc > c->rs.mc)
    xc = c->rs.nc, c->rs.nc = c->rs.mc, c->rs.mc = xc;
  if (c->rs.ni > c->rs.mi)
    xc = c->rs.ni, c->rs.ni = c->rs.mi, c->rs.mi = xc;
}
static void
set_view (fractal_context * c, CONST vinfo * s)
{
  c->s = *s;
  recalc_view (c);
}

/*FIXME most of this code is obsolette */
static void			/*INLINE */
combine_methods (void)
{
#ifdef __UNDEFINED__
  int i, j;
#endif
  int angle = (int) cfractalc.angle;
  CONST struct symetryinfo *s1 =
    cfractalc.currentformula->out + cfractalc.coloringmode, *s2 =
    cfractalc.currentformula->in + cfractalc.incoloringmode;
  if (angle < 0)
    {
      angle = 360 - ((-angle) % 360);
    }
  else
    angle %= 360;
  if (cfractalc.mandelbrot != cfractalc.currentformula->mandelbrot ||
      cfractalc.bre || cfractalc.bim)
    {
      cursymetry.xsym = (number_t) INT_MAX;
      cursymetry.ysym = (number_t) INT_MAX;
      cursymetry.nsymetries = 0;
      return;
    }
#ifdef __UNDEFINED__
  cursymetry.xmul = cimage.width / (cfractalc.rs.mc - cfractalc.rs.nc);
  cursymetry.ymul = cimage.height / (cfractalc.rs.mi - cfractalc.rs.ni);
  cursymetry.xdist = (cfractalc.rs.mc - cfractalc.rs.nc) / cimage.width / 6;
  cursymetry.ydist = (cfractalc.rs.mi - cfractalc.rs.ni) / cimage.height / 6;
#endif
  if (s1->xsym == s2->xsym)
    cursymetry.xsym = s1->xsym;
  else
    cursymetry.xsym = (number_t) INT_MAX;
  if (s1->ysym == s2->ysym)
    cursymetry.ysym = s1->ysym;
  else
    cursymetry.ysym = (number_t) INT_MAX;
  switch (cfractalc.plane)
    {
    case P_PARABOL:
      cursymetry.xsym = (number_t) INT_MAX;
      break;
    case P_LAMBDA:
      if (cursymetry.xsym == 0 && cursymetry.ysym == 0)
	cursymetry.xsym = (number_t) 1;
      else
	cursymetry.xsym = (number_t) INT_MAX;
      break;
    case P_INVLAMBDA:
      cursymetry.xsym = (number_t) INT_MAX;
      break;
    case P_TRANLAMBDA:
      if (cursymetry.xsym != 0 || cursymetry.ysym != 0)
	cursymetry.xsym = (number_t) INT_MAX;
      break;
    case P_MEREBERG:
      cursymetry.xsym = (number_t) INT_MAX;
      break;
    }
  cursymetry.symetry = sym_lines;
  cursymetry.nsymetries = 0;
  if ((number_t) angle == cfractalc.angle)
    {
      switch (angle)
	{
	case 0:
	  break;
	case 180:
	  cursymetry.xsym = -cursymetry.xsym;
	  cursymetry.ysym = -cursymetry.ysym;
	  break;
	case 90:
	  {
	    number_t tmp = cursymetry.xsym;
	    cursymetry.xsym = -cursymetry.ysym;
	    cursymetry.ysym = tmp;
	  }
	  break;
	case 210:
	  {
	    number_t tmp = cursymetry.xsym;
	    cursymetry.xsym = cursymetry.ysym;
	    cursymetry.ysym = -tmp;
	  }
	  break;
	default:
	  cursymetry.xsym = (number_t) INT_MAX;
	  cursymetry.ysym = (number_t) INT_MAX;
	}
    }
  else
    {
      cursymetry.xsym = (number_t) INT_MAX;
      cursymetry.ysym = (number_t) INT_MAX;
    }
  if (cursymetry.xsym == -(number_t) INT_MAX)
    cursymetry.xsym = (number_t) INT_MAX;
  if (cursymetry.ysym == -(number_t) INT_MAX)
    cursymetry.ysym = (number_t) INT_MAX;
}

void
update_view (fractal_context * context)
{
  set_view (context, &context->s);
}

void
set_fractalc (fractal_context * context, struct image *img)
{
  update_view (context);
  precalculate_rotation (context);
  cfractalc = *context;		/*its better to copy often accesed data into fixed memory locations */
  cpalette = *img->palette;
  cimage = *img;
  cformula = *context->currentformula;

  if (cfractalc.maxiter < 1)
    cfractalc.maxiter = 1;

  if (cfractalc.bailout < 0)
    cfractalc.bailout = 0;

  if (cfractalc.periodicity)
    {
      if (!cformula.hasperiodicity || cfractalc.incoloringmode
	  || !cfractalc.mandelbrot)
	cfractalc.periodicity = 0;
      else if (!cfractalc.plane)
	cfractalc.periodicity_limit =
	  (context->rs.mc - context->rs.nc) / (double) img->width;
      else
	{
	  int x, y;
	  number_t xstep =
	    ((context->rs.mc - context->rs.nc) / (double) img->width);
	  number_t ystep =
	    ((context->rs.mc - context->rs.nc) / (double) img->height);
	  number_t xstep2 = ((context->rs.mc - context->rs.nc) / 5);
	  number_t ystep2 = ((context->rs.mc - context->rs.nc) / 5);

	  for (x = 0; x < 5; x++)
	    for (y = 0; y < 5; y++)
	      {
		number_t x1 = context->rs.mc + xstep2 * x;
		number_t y1 = context->rs.mi + ystep2 * y;
		number_t x2 = context->rs.mc + xstep2 * x + xstep;
		number_t y2 = context->rs.mi + ystep2 * y + ystep;

		recalculate (cfractalc.plane, &x1, &y1);
		recalculate (cfractalc.plane, &x2, &y2);

		x1 = myabs (x2 - x1);
		y1 = myabs (y2 - y1);

		if (x == y && x == 0)
		  cfractalc.periodicity_limit = x1;
		if (cfractalc.periodicity > x1)
		  cfractalc.periodicity_limit = x1;
		if (cfractalc.periodicity > y1)
		  cfractalc.periodicity_limit = y1;
	      }
	}
    }

  combine_methods ();

  if (cursymetry.xsym == (number_t) INT_MAX)
    cursymetry.xsym = cfractalc.rs.mc + INT_MAX;

  if (cursymetry.ysym == (number_t) INT_MAX)
    cursymetry.ysym = cfractalc.rs.mi + INT_MAX;

#ifndef SLOWFUNCPTR
  if (cfractalc.coloringmode == 9 && cformula.smooth_calculate != NULL
      && (cpalette.type & (TRUECOLOR | TRUECOLOR16 | TRUECOLOR24 | GRAYSCALE |
			   LARGEITER)))
    {
      cfractalc.calculate[0] = cformula.smooth_calculate;
      if (cformula.smooth_calculate_periodicity && cfractalc.periodicity)
	cfractalc.calculate[1] = cformula.smooth_calculate_periodicity;
      else
	cfractalc.calculate[1] = cformula.smooth_calculate;
    }
  else
    {
      cfractalc.calculate[0] = cformula.calculate;
      if (cformula.calculate_periodicity && cfractalc.periodicity)
	cfractalc.calculate[1] = cformula.calculate_periodicity;
      else
	cfractalc.calculate[1] = cformula.calculate;
    }
#endif

}




void
set_formula (fractal_context * c, int num)
{
  assert (num < nformulas);
  assert (num >= 0);
  if (num >= nformulas)
    num = 0;
  if (c->currentformula != formulas + num)
    {
      c->currentformula = formulas + num;
      c->version++;
    }
  if (c->mandelbrot != c->currentformula->mandelbrot)
    {
      c->mandelbrot = c->currentformula->mandelbrot;
      c->version++;
    }
  if (c->currentformula->pre != c->pre)
    {
      c->pre = c->currentformula->pre;
      if (!c->mandelbrot)
	c->version++;
    }
  if (c->currentformula->pim != c->pim)
    {
      c->pim = c->currentformula->pim;
      if (!c->mandelbrot)
	c->version++;
    }
  if (c->angle)
    {
      c->angle = 0;
      c->version++;
    }
  if (c->s.cr != c->currentformula->v.cr ||
      c->s.ci != c->currentformula->v.ci ||
      c->s.rr != c->currentformula->v.rr ||
      c->s.ri != c->currentformula->v.ri)
    {
      c->s = c->currentformula->v;
      c->version++;
    }
  if (c->bre && c->bim)
    {
      c->bre = c->bim = 0;
      if (c->mandelbrot)
	c->version++;
    }
}


void
fractalc_resize_to (fractal_context * c, float wi, float he)
{
  c->windowwidth = wi;
  c->windowheight = he;
  recalc_view (c);
  return;
}



fractal_context *
make_fractalc (CONST int formula, float wi, float he)
{
  fractal_context *new_ctxt;

#ifndef __BEOS__
#ifdef __GNUC__
#ifdef __i386__
#ifndef NOASSEMBLY
  _control87 (PC_64 | MCW_EM | MCW_RC, MCW_PC | MCW_EM | MCW_RC);
#endif
#endif
#endif
#endif
#ifdef __alpha__
#ifdef __linux__
  extern void ieee_set_fp_control (unsigned long);
  /* ieee_set_fp_control(IEEE_TRAP_ENABLE_INV); */
  ieee_set_fp_control (0UL);	/* ignore everything possible */
#endif
#endif
#ifdef _plan9_
  {
    unsigned long fcr = 0;	/*getfcr(); */
    fcr |= FPRNR | FPPEXT;
    /*fcr &= ~(FPINEX | FPOVFL | FPUNFL | FPZDIV); */
    setfcr (fcr);
  }
#endif
  new_ctxt = (fractal_context *) calloc (1, sizeof (fractal_context));
  if (new_ctxt == NULL)
    return 0;
  new_ctxt->windowwidth = wi;
  new_ctxt->periodicity = 1;
  new_ctxt->windowheight = he;
  new_ctxt->maxiter = DEFAULT_MAX_ITER;
  new_ctxt->bailout = DEFAULT_BAILOUT;
  new_ctxt->coloringmode = 0;
  new_ctxt->intcolor = 0;
  new_ctxt->outtcolor = 0;
  new_ctxt->slowmode = 0;
  new_ctxt->range = 3;
  new_ctxt->angle = 0;
  set_formula (new_ctxt, formula);
  return (new_ctxt);
}

void
free_fractalc (fractal_context * c)
{
  free (c);
}

#ifdef NOASSEMBLY
#define rdtsc() 0
#else
#define rdtsc() ({unsigned long time; asm __volatile__ ("rdtsc":"=a"(time)); time; })
#endif

void
speed_test (fractal_context * c, struct image *img)
{
  //unsigned int sum;
  tl_timer *t;
  int time;
  unsigned int i;
  set_fractalc (c, img);
  t = tl_create_timer ();
  cfractalc.maxiter = 100;
#ifdef SLOWFUNCPTR
  i = calculateswitch (0.0, 0.0, 0.0, 0.0, 0);
#else
  cfractalc.currentformula->calculate (0.0, 0.0, 0.0, 0.0);
  if (cfractalc.currentformula->calculate_periodicity != NULL)
    cfractalc.currentformula->calculate_periodicity (0.0, 0.0, 0.0, 0.0);
  if (cfractalc.currentformula->smooth_calculate != NULL)
    cfractalc.currentformula->smooth_calculate (0.0, 0.0, 0.0, 0.0);
  if (cfractalc.currentformula->smooth_calculate_periodicity != NULL)
    cfractalc.currentformula->smooth_calculate_periodicity (0.0, 0.0, 0.0,
							    0.0);
#endif
  cfractalc.maxiter = 20000000;

  tl_update_time ();
  tl_reset_timer (t);
  /*sum = rdtsc (); */
#ifdef SLOWFUNCPTR
  i = calculateswitch (0.0, 0.0, 0.0, 0.0, 0);
#else
  i = cfractalc.currentformula->calculate (0.0, 0.0, 0.0, 0.0);
#endif
  /*sum -= rdtsc ();
     printf ("%f\n", (double) (-sum) / cfractalc.maxiter); */
  tl_update_time ();
  time = tl_lookup_timer (t);
  x_message ("Result:%i Formulaname:%s Time:%i Mloops per sec:%.2f",
	     (int) i,
	     cfractalc.currentformula->name[0], time,
	     cfractalc.maxiter / (double) time);

#ifndef SLOWFUNCPTR


  if (cfractalc.currentformula->smooth_calculate != NULL)
    {
      tl_update_time ();
      tl_reset_timer (t);
      i = cfractalc.currentformula->smooth_calculate (0.0, 0.0, 0.0, 0.0);
      tl_update_time ();
      time = tl_lookup_timer (t);
      x_message ("Result:%i Formulaname:%s Time:%i Mloops per sec:%.2f",
		 (int) i,
		 cfractalc.currentformula->name[0],
		 time, cfractalc.maxiter / (double) time);
    }

#endif

  tl_free_timer (t);
}
