/*
FRACTAL - A program growing fractals to benchmark parallelization and drawing
libraries.

Copyright 2009-2021, Javier Burguete Tolosa.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY Javier Burguete Tolosa ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL Javier Burguete Tolosa OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/**
 * \file fractal.c
 * \brief Source file to define the fractal data and functions.
 * \author Javier Burguete Tolosa.
 * \copyright Copyright 2009-2021, Javier Burguete Tolosa.
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#if HAVE_SYSINFO
#include <sys/sysinfo.h>
#endif
#include <gsl/gsl_rng.h>
#include <libxml/parser.h>
#include <glib.h>
#if HAVE_GTOP
#include <glibtop.h>
#include <glibtop/close.h>
#endif
#ifdef G_OS_WIN32
#include <windows.h>
#endif
#include <libintl.h>
#include <GL/glew.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <gtk/gtk.h>
#include "config.h"
#include "fractal.h"
#include "simulator.h"

extern void draw ();

unsigned int width = WIDTH;     ///< Medium width.
unsigned int height = HEIGHT;   ///< Medium height.
unsigned int length = LENGTH;   ///< Medium length.
unsigned int area;              ///< Medium area.
unsigned int medium_bytes;      ///< Number of bytes used by the medium.
unsigned int breaking = 0;      ///< 1 on breaking, 0 otherwise.
unsigned int simulating = 0;    ///< 1 on simulating, 0 otherwise.
unsigned int animating = 1;     ///< 1 on animating, 0 otherwise.

unsigned int fractal_type = FRACTAL_TYPE_TREE;  ///< Fractal type.
unsigned int fractal_3D = 0;    ///< 1 on 3D fractals, 0 on 2D fractals.
unsigned int fractal_diagonal = 0;
///< 1 on diagonal point movement, 0 otherwise.

unsigned long t0;               ///< Computational time.

unsigned int max_d = 0;         ///< Maximum fractal size.
unsigned int *medium = NULL;    ///< Array of fractal points.
Point3D *point = NULL;          ///< Array of 3D points.
unsigned int npoints = 0;       ///< Number of points.

unsigned int random_algorithm = 0;
///< Type of random numbers generator algorithm.
unsigned int random_seed_type = 1;      ///< Type of random seed.
unsigned long random_seed = SEED;       ///< Random seed.
static void *(*parallel_fractal) (gsl_rng * rng);
///< Pointer to the function to calculate the fractal.

static const float color3f[16][3] = {
  {0., 0., 0.},
  {1., 0., 0.},
  {0., 1., 0.},
  {0., 0., 1.},
  {0.5, 0.5, 0.},
  {0.5, 0., 0.5},
  {0., 0.5, 0.5},
  {0.5, 0.25, 0.25},
  {0.25, 0.5, 0.25},
  {0.25, 0.25, 0.5},
  {0.75, 0.25, 0.},
  {0.75, 0., 0.25},
  {0.25, 0.75, 0.},
  {0., 0.75, 0.25},
  {0.25, 0., 0.75},
  {0., 0.25, 0.75}
};                              ///< Array of colors.

DialogOptions dialog_options[1];
  ///< DialogOptions to set the fractal options.
DialogSimulator dialog_simulator[1];
  ///< DialogSimulator to show the main window.

// PARALLELIZING DATA

unsigned int nthreads;          ///< Threads number.
GMutex mutex[1];                ///< Mutex to lock memory saves.

// END

/**
 * Function to return the square of an unsigned int.

 * \return square.
 */
static inline unsigned int
sqr (int x)                     ///< unsigned int.
{
  return x * x;
}

/**
 * Function to get the number of cores of the CPU.
 *
 * \return CPU number of cores.
 */
int
threads_number ()
{
#if HAVE_GTOP
  int ncores;
  glibtop *top;
  top = glibtop_init ();
  ncores = top->ncpu + 1;
  glibtop_close ();
  return ncores;
#elif HAVE_GET_NPROCS
  return get_nprocs ();
#elif defined(G_OS_WIN32)
  SYSTEM_INFO sysinfo;
  GetSystemInfo (&sysinfo);
  return sysinfo.dwNumberOfProcessors;
#else
  return (int) sysconf (_SC_NPROCESSORS_ONLN);
#endif
}

/**
 * Function to add a point to the array.
 */
static inline void
points_add (int x,              ///< Point x-coordinate.
            int y,              ///< Point y-coordinate.
            int z,              ///< Point z-coordinate.
            unsigned int c)     ///< Point color.
{
  Point3D *p;
  ++npoints;
  point = (Point3D *) g_realloc (point, npoints * sizeof (Point3D));
  p = point + npoints - 1;
  p->r[0] = x;
  p->r[1] = y;
  p->r[2] = z;
  memcpy (p->c, color3f[c], 3 * sizeof (float));
}

/**
 * Function to make a random 2D movement on a point.
 */
static inline void
point_2D_move (int *x,          ///< Point x-coordinate.
               int *y,          ///< Point y-coordinate.
               gsl_rng * rng)   ///< Pseudo-random number generator.
{
  register unsigned int k;
  static const int mx[4] = { 0, 0, 1, -1 }, my[4] = { 1, -1, 0, 0 };
  k = gsl_rng_uniform_int (rng, 4);
  *x += mx[k];
  *y += my[k];
}

/**
 * Function to make a random 2D movement on a point enabling diagonals.
 */
static inline void
point_2D_move_diagonal (int *x, ///< Point x-coordinate.
                        int *y, ///< Point y-coordinate.
                        gsl_rng * rng)  ///< Pseudo-random number generator.
{
  register unsigned int k;
  static const int mx[8] = { 1, 1, 1, 0, -1, -1, -1, 0 },
    my[8] = { 1, 0, -1, -1, -1, 0, 1, 1 };
  k = gsl_rng_uniform_int (rng, 8);
  *x += mx[k];
  *y += my[k];
}

/**
 * Function to make a random 3D movement on a point.
 */
static inline void
point_3D_move (int *x,          ///< Point x-coordinate.
               int *y,          ///< Point y-coordinate.
               int *z,          ///< Point z-coordinate.
               gsl_rng * rng)   ///< Pseudo-random number generator.
{
  register unsigned int k;
  static const int mx[6] = { 0, 1, -1, 0, 0, 0 },
    my[6] = { 0, 0, 0, 1, -1, 0 }, mz[6] = { 1, 0, 0, 0, 0, -1 };
  k = gsl_rng_uniform_int (rng, 6);
  *x += mx[k];
  *y += my[k];
  *z += mz[k];
}

/**
 * Function to make a random 3D movement on a point enabling diagonals.
 */
static inline void
point_3D_move_diagonal (int *x, ///< Point x-coordinate.
                        int *y, ///< Point y-coordinate.
                        int *z, ///< Point z-coordinate.
                        gsl_rng * rng)  ///< Pseudo-random number generator.
{
  register int k;
  static const int mx[26] = {
    1, 1, 1, 0, 0, 0, -1, -1, -1,
    1, 1, 1, 0, 0, -1, -1, -1,
    1, 1, 1, 0, 0, 0, -1, -1, -1
  }, my[26] = {
    1, 0, -1, 1, 0, -1, 1, 0, -1,
    1, 0, -1, 1, -1, 1, 0, -1, 1, 0, -1, 1, 0, -1, 1, 0, -1
  }, mz[26] = {
    1, 1, 1, 1, 1, 1, 1, 1, 1,
    0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1
  };
  k = gsl_rng_uniform_int (rng, 26);
  *x += mx[k];
  *y += my[k];
  *z += mz[k];
}

/**
 * Function to start a new 2D tree point.
 */
static inline void
tree_2D_point_new (int *x,      ///< Point x-coordinate.
                   int *y,      ///< Point y-coordinate.
                   gsl_rng * rng)       ///< Pseudo-random number generator.
{
  *x = gsl_rng_uniform_int (rng, width);
  *y = max_d;
#if DEBUG
  printf ("New point x %d y %d\n", *x, *y);
#endif
}

/**
 * Function to check the limits of a 2D tree point.
 */
static inline void
tree_2D_point_boundary (int *x, ///< Point x-coordinate.
                        int *y, ///< Point y-coordinate.
                        gsl_rng * rng)  ///< Pseudo-random number generator.
{
  if (*y < 0 || *y == (int) height)
    {
      tree_2D_point_new (x, y, rng);
      return;
    }
  if (*x < 0)
    *x = width - 1;
  else if (*x == (int) width)
    *x = 0;
#if DEBUG
  printf ("Boundary point x %d y %d\n", *x, *y);
#endif
}

/**
 * Function to fix a 2D tree point.
 *
 * \return 1 on fixing point, 0 on otherwise.
 */
static inline unsigned int
tree_2D_point_fix (int x,       ///< Point x-coordinate.
                   int y)       ///< Point y-coordinate.
{
  register unsigned int *point;
#if DEBUG
  printf ("x=%d y=%d max_d=%d width=%d height=%d\n", x, y, max_d, width,
          height);
#endif
  if (y > (int) max_d || x == 0 || y == 0 || x == (int) width - 1
      || y == (int) height - 1)
    return 0;
  point = medium + y * width + x;
  if (point[1] || point[-1] || point[width] || point[-(int) width])
    {
#if DEBUG
      printf ("fixing point\n");
#endif
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      point[0] = 2;
      points_add (x, y, 0, 2);
      g_mutex_unlock (mutex);
// END
      return 1;
    }
  return 0;
}

/**
 * Function to init a 2D tree.
 */
static inline void
tree_2D_init ()
{
  medium[width / 2] = 2;
}

/**
 * Function to check the end a 2D tree.
 *
 * \return 1 on ending, 0 on continuing.
 */
static inline unsigned int
tree_2D_end (int y)             ///< Point y-coordinate.
{
  if (y == (int) max_d)
    {
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      ++max_d;
      g_mutex_unlock (mutex);
// END
    }
  if (max_d >= height - 1)
    {
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      max_d = height - 1;
      g_mutex_unlock (mutex);
// END
      return 1;
    }
  return 0;
}

/**
 * Function to start a new 3D tree point.
 */
static inline void
tree_3D_point_new (int *x,      ///< Point x-coordinate.
                   int *y,      ///< Point y-coordinate.
                   int *z,      ///< Point z-coordinate.
                   gsl_rng * rng)       ///< Pseudo-random number generator.
{
  *x = gsl_rng_uniform_int (rng, length);
  *y = gsl_rng_uniform_int (rng, width);
  *z = max_d;
#if DEBUG
  printf ("New point x %d y %d z %d\n", *x, *y, *z);
#endif
}

/**
 * Function to check the limits of a 2D tree point.
 */
static inline void
tree_3D_point_boundary (int *x, ///< Point x-coordinate.
                        int *y, ///< Point y-coordinate.
                        int *z, ///< Point z-coordinate.
                        gsl_rng * rng)  ///< Pseudo-random number generator.
{
  if (*z < 0 || *z == (int) height)
    {
      tree_3D_point_new (x, y, z, rng);
      return;
    }
  if (*x < 0)
    *x = length - 1;
  else if (*x == (int) length)
    *x = 0;
  if (*y < 0)
    *y = width - 1;
  else if (*y == (int) width)
    *y = 0;
#if DEBUG
  printf ("New point x %d y %d z %d\n", *x, *y, *z);
#endif
}

/**
 * Function to fix a 2D tree point.
 *
 * \return 1 on fixing point, 0 on otherwise.
 */
static inline unsigned int
tree_3D_point_fix (int x,       ///< Point x-coordinate.
                   int y,       ///< Point y-coordinate.
                   int z)       ///< point z-coordinate.
{
  register unsigned int *point;
  if (z > (int) max_d || z == 0 || y == 0 || x == 0 || z == (int) height - 1
      || y == (int) width - 1 || x == (int) length - 1)
    return 0;
  point = medium + z * area + y * length + x;
  if (point[1] || point[-1] || point[length] || point[-(int) length]
      || point[area] || point[-(int) area])
    {
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      point[0] = 2;
      points_add (x, y, z, 2);
      g_mutex_unlock (mutex);
// END
      return 1;
    }
  return 0;
}

/**
 * Function to init a 3D tree.
 */
static inline void
tree_3D_init ()
{
  medium[length * (width / 2) + length / 2] = 2;
}

/**
 * Function to check the end a 3D tree.
 *
 * \return 1 on ending, 0 on continuing.
 */
static inline unsigned int
tree_3D_end (int z)             ///< Point z-coordinate.
{
  if (z == (int) max_d)
    {
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      ++max_d;
      g_mutex_unlock (mutex);
// END
    }
  if (max_d >= height - 1)
    {
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      max_d = height - 1;
      g_mutex_unlock (mutex);
// END
      return 1;
    }
  return 0;
}

/**
 * Function to check the limits of a 2D forest point.
 */
static inline void
forest_2D_point_boundary (int *x,       ///< Point x-coordinate.
                          int *y,       ///< Point y-coordinate.
                          gsl_rng * rng)
///< Pseudo-random number generator.
{
  if (*y == (int) height || *y < 0)
    {
      tree_2D_point_new (x, y, rng);
      return;
    }
  if (*x < 0)
    *x = width - 1;
  else if (*x == (int) width)
    *x = 0;
#if DEBUG
  printf ("Boundary point x %d y %d\n", *x, *y);
#endif
}

/**
 * Function to fix a 2D forest point.
 *
 * \return 1 on fixing point, 0 on otherwise.
 */
static inline unsigned int
forest_2D_point_fix (int x,     ///< Point x-coordinate.
                     int y,     ///< Point y-coordinate.
                     gsl_rng * rng)     ///< Pseudo-random number generator.
{
  register unsigned int k, *point;
  if (y > (int) max_d || x == 0 || x == (int) width - 1
      || y == (int) height - 1)
    return 0;
  point = medium + y * width + x;
  if (y == 0)
    {
      k = 1 + gsl_rng_uniform_int (rng, 15);
      goto forest;
    }
  k = point[1];
  if (k)
    goto forest;
  k = point[-1];
  if (k)
    goto forest;
  k = point[width];
  if (k)
    goto forest;
  k = point[-(int) width];
  if (k)
    goto forest;
  return 0;

forest:
// PARALLELIZING MUTEX
  g_mutex_lock (mutex);
  point[0] = k;
  points_add (x, y, 0, k);
  g_mutex_unlock (mutex);
// END
  return k;
}

/**
 * Function to check the limits of a 3D forest point.
 */
static inline void
forest_3D_point_boundary (int *x,       ///< Point x-coordinate.
                          int *y,       ///< Point y-coordinate.
                          int *z,       ///< Point z-coordinate.
                          gsl_rng * rng)
///< Pseudo-random number generator.
{
  if (*z == (int) height || *z < 0)
    {
      tree_3D_point_new (x, y, z, rng);
      return;
    }
  if (*y < 0)
    *y = width - 1;
  else if (*y == (int) width)
    *y = 0;
  if (*x < 0)
    *x = length - 1;
  else if (*x == (int) length)
    *x = 0;
#if DEBUG
  printf ("Boundary point x %d y %d z %d\n", *x, *y, *z);
#endif
}

/**
 * Function to fix a 3D forest point.
 *
 * \return 1 on fixing point, 0 on otherwise.
 */
static inline unsigned int
forest_3D_point_fix (int x,     ///< Point x-coordinate.
                     int y,     ///< Point y-coordinate.
                     int z,     ///< Point z-coordinate.
                     gsl_rng * rng)     ///< Pseudo-random number generator.
{
  register unsigned int k, *point;
  if (z > (int) max_d || y == 0 || x == 0 || z == (int) height - 1
      || y == (int) width - 1 || x == (int) length - 1)
    return 0;
  point = medium + z * area + y * length + x;
  if (z == 0)
    {
      k = 1 + gsl_rng_uniform_int (rng, 15);
      goto forest;
    }
  k = point[1];
  if (k)
    goto forest;
  k = point[-1];
  if (k)
    goto forest;
  k = point[length];
  if (k)
    goto forest;
  k = point[-(int) length];
  if (k)
    goto forest;
  k = point[area];
  if (k)
    goto forest;
  k = point[-(int) area];
  if (k)
    goto forest;
  return 0;

forest:
// PARALLELIZING MUTEX
  g_mutex_lock (mutex);
  point[0] = k;
  points_add (x, y, z, k);
  g_mutex_unlock (mutex);
// END
  return k;
}

/**
 * Function to start a new 2D neuron point.
 */
static inline void
neuron_2D_point_new (int *x,    ///< Point x-coordinate.
                     int *y,    ///< Point y-coordinate.
                     gsl_rng * rng)     ///< Pseudo-random number generator.
{
  register double angle;
  angle = 2 * M_PI * gsl_rng_uniform (rng);
  *x = width / 2 + max_d * cos (angle);
  *y = height / 2 + max_d * sin (angle);
#if DEBUG
  printf ("New point x %d y %d\n", *x, *y);
#endif
}

/**
 * Function to check the limits of a 2D neuron point.
 */
static inline void
neuron_2D_point_boundary (int *x,       ///< Point x-coordinate.
                          int *y,       ///< Point y-coordinate.
                          gsl_rng * rng)
///< Pseudo-random number generator.
{
  if (*y < 0 || *y == (int) height || *x < 0 || *x == (int) width)
    {
      neuron_2D_point_new (x, y, rng);
#if DEBUG
      printf ("Boundary point x %d y %d\n", *x, *y);
#endif
    }
}

/**
 * Function to fix a 2D neuron point.
 *
 * \return 1 on fixing point, 0 on otherwise.
 */
static inline unsigned int
neuron_2D_point_fix (int x,     ///< Point x-coordinate.
                     int y)     ///< Point y-coordinate.
{
  register unsigned int *point;
  if (x == 0 || y == 0 || x == (int) width - 1 || y == (int) height - 1)
    return 0;
  point = medium + y * width + x;
  if (point[1] || point[-1] || point[width] || point[-(int) width])
    {
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      point[0] = 2;
      points_add (x, y, 0, 2);
      g_mutex_unlock (mutex);
// END
      return 1;
    }
  return 0;
}

/**
 * Function to init a 2D neuron.
 */
static inline void
neuron_2D_init ()
{
  medium[(height / 2) * width + width / 2] = 2;
}

/**
 * Function to check the end a 2D neuron.
 *
 * \return 1 on ending, 0 on continuing.
 */
static inline unsigned int
neuron_2D_end (int x,           ///< Point x-coordinate.
               int y)           ///< Point y-coordinate.
{
  register int r, k;
  r = 1 + round (sqrt (sqr (x - width / 2) + sqr (y - height / 2)));
  if (r >= (int) max_d)
    {
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      ++max_d;
      g_mutex_unlock (mutex);
// END
    }
  if (height < width)
    k = height;
  else
    k = width;
  k = k / 2 - 1;
  if ((int) max_d >= k)
    {
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      max_d = k;
      g_mutex_unlock (mutex);
// END
      return 1;
    }
  return 0;
}

/**
 * Function to start a new 3D neuron point.
 */
static inline void
neuron_3D_point_new (int *x,    ///< Point x-coordinate.
                     int *y,    ///< Point y-coordinate.
                     int *z,    ///< Point z-coordinate.
                     gsl_rng * rng)     ///< Pseudo-random number generator.
{
  double c1, s1, c2, s2;
  sincos (2. * M_PI * gsl_rng_uniform (rng), &s1, &c1);
  sincos (asin (2. * gsl_rng_uniform (rng) - 1), &s2, &c2);
  *x = length / 2 + max_d * c1 * c2;
  *y = width / 2 + max_d * s1 * c2;
  *z = height / 2 + max_d * s2;
#if DEBUG
  printf ("New point x %d y %d z %d\n", *x, *y, *z);
#endif
}

/**
 * Function to check the limits of a 3D neuron point.
 */
static inline void
neuron_3D_point_boundary (int *x,       ///< Point x-coordinate.
                          int *y,       ///< Point y-coordinate.
                          int *z,       ///< Point z-coordinate.
                          gsl_rng * rng)
///< Pseudo-random number generator.
{
  if (*z < 0 || *y < 0 || *x < 0 || *z == (int) height || *y == (int) width
      || *x == (int) length)
    {
      neuron_3D_point_new (x, y, z, rng);
#if DEBUG
      printf ("Boundary point x %d y %d z %d\n", *x, *y, *z);
#endif
    }
}

/**
 * Function to fix a 3D neuron point.
 *
 * \return 1 on fixing point, 0 on otherwise.
 */
static inline unsigned int
neuron_3D_point_fix (int x,     ///< Point x-coordinate.
                     int y,     ///< Point y-coordinate.
                     int z)     ///< Point z-coordinate.
{
  register unsigned int *point;
  if (z == 0 || y == 0 || x == 0 || z == (int) height - 1
      || y == (int) width - 1 || x == (int) length - 1)
    return 0;
  point = medium + z * area + y * length + x;
  if (point[1] || point[-1] || point[length] || point[-(int) length] ||
      point[area] || point[-(int) area])
    {
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      point[0] = 2;
      points_add (x, y, z, 2);
      g_mutex_unlock (mutex);
// END
      return 1;
    }
  return 0;
}

/**
 * Function to init a 3D neuron.
 */
static inline void
neuron_3D_init ()
{
  medium[area * (height / 2) + length * (width / 2) + length / 2] = 2;
}

/**
 * Function to check the end a 3D neuron.
 *
 * \return 1 on ending, 0 on continuing.
 */
static inline unsigned int
neuron_3D_end (int x,           ///< Point x-coordinate.
               int y,           ///< Point y-coordinate.
               int z)           ///< Point z-coordinate.
{
  register int r, k;
  r = 1 + sqrt (sqr (x - length / 2) + sqr (y - width / 2)
                + sqr (z - height / 2));
  if (r >= (int) max_d)
    {
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      ++max_d;
      g_mutex_unlock (mutex);
// END
    }
  k = length;
  if ((int) width < k)
    k = width;
  if ((int) height < k)
    k = height;
  k = k / 2 - 1;
  if ((int) max_d >= k)
    {
// PARALLELIZING MUTEX
      g_mutex_lock (mutex);
      max_d = k;
      g_mutex_unlock (mutex);
// END
      return 1;
    }
  return 0;
}

/**
 * Function to stop the fractal simulation.
 */
void
fractal_stop ()
{
// PARALLELIZING MUTEX
  g_mutex_lock (mutex);
  breaking = 1;
  g_mutex_unlock (mutex);
// END
}

// PARALLELIZED FUNCTIONS

/**
 * Function to create a 2D fractal tree.
 *
 * \return NULL.
 */
void *
parallel_fractal_tree_2D (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y;
  long t0;
#if DEBUG
  printf ("parallel_fractal_tree_2D: start\n");
#endif
  t0 = time (NULL);
  do
    {
#if DEBUG
      printf ("creating point\n");
#endif
      tree_2D_point_new (&x, &y, rng);
#if DEBUG
      printf ("checking fix\n");
#endif
      while (!breaking && !tree_2D_point_fix (x, y))
        {
#if DEBUG
          printf ("moving point\n");
#endif
          point_2D_move (&x, &y, rng);
#if DEBUG
          printf ("checking boundary\n");
#endif
          tree_2D_point_boundary (&x, &y, rng);
        }
#if DEBUG
      printf ("checking end\n");
#endif
      if (animating && time (NULL) > t0)
        break;
      if (tree_2D_end (y))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

/**
 * Function to create a 3D fractal tree.
 *
 * \return NULL.
 */
void *
parallel_fractal_tree_3D (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y, z;
  long t0;
  t0 = time (NULL);
  do
    {
      tree_3D_point_new (&x, &y, &z, rng);
      while (!breaking && !tree_3D_point_fix (x, y, z))
        {
          point_3D_move (&x, &y, &z, rng);
          tree_3D_point_boundary (&x, &y, &z, rng);
        }
      if (animating && time (NULL) > t0)
        break;
      if (tree_3D_end (z))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

/**
 * Function to create a 2D fractal forest.
 *
 * \return NULL.
 */
void *
parallel_fractal_forest_2D (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y;
  long t0;
  t0 = time (NULL);
  do
    {
      tree_2D_point_new (&x, &y, rng);
      while (!breaking && !forest_2D_point_fix (x, y, rng))
        {
          point_2D_move (&x, &y, rng);
          forest_2D_point_boundary (&x, &y, rng);
        }
      if (animating && time (NULL) > t0)
        break;
      if (tree_2D_end (y))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

/**
 * Function to create a 3D fractal forest.
 *
 * \return NULL.
 */
void *
parallel_fractal_forest_3D (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y, z;
  long t0;
  t0 = time (NULL);
  do
    {
      tree_3D_point_new (&x, &y, &z, rng);
      while (!breaking && !forest_3D_point_fix (x, y, z, rng))
        {
          point_3D_move (&x, &y, &z, rng);
          forest_3D_point_boundary (&x, &y, &z, rng);
        }
      if (animating && time (NULL) > t0)
        break;
      if (tree_3D_end (z))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

/**
 * Function to create a 2D fractal neuron.
 *
 * \return NULL.
 */
void *
parallel_fractal_neuron_2D (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y;
  long t0;
  t0 = time (NULL);
  do
    {
      neuron_2D_point_new (&x, &y, rng);
      while (!breaking && !neuron_2D_point_fix (x, y))
        {
          point_2D_move (&x, &y, rng);
          neuron_2D_point_boundary (&x, &y, rng);
        }
      if (animating && time (NULL) > t0)
        break;
      if (neuron_2D_end (x, y))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

/**
 * Function to create a 3D fractal neuron.
 *
 * \return NULL.
 */
void *
parallel_fractal_neuron_3D (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y, z;
  long t0;
  t0 = time (NULL);
  do
    {
      neuron_3D_point_new (&x, &y, &z, rng);
      while (!breaking && !neuron_3D_point_fix (x, y, z))
        {
          point_3D_move (&x, &y, &z, rng);
          neuron_3D_point_boundary (&x, &y, &z, rng);
        }
      if (animating && time (NULL) > t0)
        break;
      if (neuron_3D_end (x, y, z))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

/**
 * Function to create a 2D fractal tree with diagonal movements.
 *
 * \return NULL.
 */
void *
parallel_fractal_tree_2D_diagonal (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y;
  long t0;
  t0 = time (NULL);
  do
    {
      tree_2D_point_new (&x, &y, rng);
      while (!breaking && !tree_2D_point_fix (x, y))
        {
          point_2D_move (&x, &y, rng);
          tree_2D_point_boundary (&x, &y, rng);
        }
      if (animating && time (NULL) > t0)
        break;
      if (tree_2D_end (y))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

/**
 * Function to create a 3D fractal tree with diagonal movements.
 *
 * \return NULL.
 */
void *
parallel_fractal_tree_3D_diagonal (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y, z;
  long t0;
  t0 = time (NULL);
  do
    {
      tree_3D_point_new (&x, &y, &z, rng);
      while (!breaking && !tree_3D_point_fix (x, y, z))
        {
          point_3D_move (&x, &y, &z, rng);
          tree_3D_point_boundary (&x, &y, &z, rng);
        }
      if (animating && time (NULL) > t0)
        break;
      if (tree_3D_end (z))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

/**
 * Function to create a 2D fractal forest with diagonal movements.
 *
 * \return NULL.
 */
void *
parallel_fractal_forest_2D_diagonal (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y;
  long t0;
  t0 = time (NULL);
  do
    {
      tree_2D_point_new (&x, &y, rng);
      while (!breaking && !forest_2D_point_fix (x, y, rng))
        {
          point_2D_move_diagonal (&x, &y, rng);
          forest_2D_point_boundary (&x, &y, rng);
        }
      if (animating && time (NULL) > t0)
        break;
      if (tree_2D_end (y))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

/**
 * Function to create a 3D fractal forest with diagonal movements.
 *
 * \return NULL.
 */
void *
parallel_fractal_forest_3D_diagonal (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y, z;
  long t0;
  t0 = time (NULL);
  do
    {
      tree_3D_point_new (&x, &y, &z, rng);
      while (!breaking && !forest_3D_point_fix (x, y, z, rng))
        {
          point_3D_move_diagonal (&x, &y, &z, rng);
          forest_3D_point_boundary (&x, &y, &z, rng);
        }
      if (animating && time (NULL) > t0)
        break;
      if (tree_3D_end (z))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

/**
 * Function to create a 2D fractal neuron with diagonal movements.
 *
 * \return NULL.
 */
void *
parallel_fractal_neuron_2D_diagonal (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y;
  long t0;
  t0 = time (NULL);
  do
    {
      neuron_2D_point_new (&x, &y, rng);
      while (!breaking && !neuron_2D_point_fix (x, y))
        {
          point_2D_move_diagonal (&x, &y, rng);
          neuron_2D_point_boundary (&x, &y, rng);
        }
      if (animating && time (NULL) > t0)
        break;
      if (neuron_2D_end (x, y))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

/**
 * Function to create a 3D fractal neuron with diagonal movements.
 *
 * \return NULL.
 */
void *
parallel_fractal_neuron_3D_diagonal (gsl_rng * rng)
///< Pseudo-random number generator.
{
  int x, y, z;
  long t0;
  t0 = time (NULL);
  do
    {
      neuron_3D_point_new (&x, &y, &z, rng);
      while (!breaking && !neuron_3D_point_fix (x, y, z))
        {
          point_3D_move_diagonal (&x, &y, &z, rng);
          neuron_3D_point_boundary (&x, &y, &z, rng);
        }
      if (animating && time (NULL) > t0)
        break;
      if (neuron_3D_end (x, y, z))
        fractal_stop ();
    }
  while (!breaking);
  g_thread_exit (NULL);
  return NULL;
}

//END OF PARALLELIZED FUNCTIONS

/**
 * Function to start the fractal functions and data.
 */
void
medium_start ()
{
  register int i, j;

#if DEBUG
  printf ("Deleting points\n");
#endif
  g_free (point);
  point = NULL;
  npoints = 0;

  j = area = width * length;
  if (fractal_3D)
    j *= length;
  medium_bytes = j * sizeof (unsigned int);
  medium = (unsigned int *) g_slice_alloc (medium_bytes);
  for (i = j; --i >= 0;)
    medium[i] = 0;
#if DEBUG
  printf ("Medium size=%d pointer=%ld\n", j, (size_t) medium);
#endif

#if DEBUG
  printf ("Setting functions\n");
#endif
  if (fractal_diagonal)
    {
      if (fractal_3D)
        {
          switch (fractal_type)
            {
            case FRACTAL_TYPE_TREE:
              tree_3D_init ();
              parallel_fractal = parallel_fractal_tree_3D_diagonal;
              max_d = 1;
              break;
            case FRACTAL_TYPE_FOREST:
              parallel_fractal = parallel_fractal_forest_3D_diagonal;
              max_d = 1;
              break;
            default:
              neuron_3D_init ();
              parallel_fractal = parallel_fractal_neuron_3D_diagonal;
              max_d = 2;
            }
        }
      else
        {
          switch (fractal_type)
            {
            case FRACTAL_TYPE_TREE:
              tree_2D_init ();
              parallel_fractal = parallel_fractal_tree_2D_diagonal;
              max_d = 1;
              break;
            case FRACTAL_TYPE_FOREST:
              parallel_fractal = parallel_fractal_forest_2D_diagonal;
              max_d = 1;
              break;
            default:
              neuron_2D_init ();
              parallel_fractal = parallel_fractal_neuron_2D_diagonal;
              max_d = 2;
            }
        }
    }
  else
    {
      if (fractal_3D)
        {
          switch (fractal_type)
            {
            case FRACTAL_TYPE_TREE:
              tree_3D_init ();
              parallel_fractal = parallel_fractal_tree_3D;
              max_d = 1;
              break;
            case FRACTAL_TYPE_FOREST:
              parallel_fractal = parallel_fractal_forest_3D;
              max_d = 1;
              break;
            default:
              neuron_3D_init ();
              parallel_fractal = parallel_fractal_neuron_3D;
              max_d = 2;
            }
        }
      else
        {
          switch (fractal_type)
            {
            case FRACTAL_TYPE_TREE:
              tree_2D_init ();
              parallel_fractal = parallel_fractal_tree_2D;
              max_d = 1;
              break;
            case FRACTAL_TYPE_FOREST:
              parallel_fractal = parallel_fractal_forest_2D;
              max_d = 1;
              break;
            default:
              neuron_2D_init ();
              parallel_fractal = parallel_fractal_neuron_2D;
              max_d = 2;
            }
        }
    }
}

/**
 * Function to get an unsigned integer number of a XML node property.
 *
 * \return Unsigned integer number value.
 */
static unsigned int
xml_node_get_uint_with_default (xmlNode * node, ///< XML node.
                                const xmlChar * prop,   ///< XML property.
                                unsigned int default_value,     ///< Default value.
                                int *error_code)        ///< Error code.
{
  unsigned int i = 0;
  xmlChar *buffer;
  buffer = xmlGetProp (node, prop);
  *error_code = 0;
  if (!buffer)
    return default_value;
  else
    {
      if (sscanf ((char *) buffer, "%u", &i) != 1)
        *error_code = 1;
      xmlFree (buffer);
    }
  return i;
}

/**
 * Function to get an unsigned long integer number of a XML node property.
 *
 * \return Unsigned long integer number value.
 */
static unsigned long
xml_node_get_ulong_with_default (xmlNode * node,        ///< XML node.
                                 const xmlChar * prop,  ///< XML property.
                                 unsigned long default_value,
                                 ///< Default value.
                                 int *error_code)       ///< Error code.
{
  unsigned long i = 0l;
  xmlChar *buffer;
  buffer = xmlGetProp (node, prop);
  *error_code = 0;
  if (!buffer)
    return default_value;
  else
    {
      if (sscanf ((char *) buffer, "%lu", &i) != 1)
        *error_code = 1;
      xmlFree (buffer);
    }
  return i;
}

/**
 * Function to open the fractal data on a file.
 *
 * \return 1 on success, 0 on error.
 */
int
fractal_input (char *filename)  ///< File name.
{
  xmlDoc *doc;
  xmlNode *node;
  xmlChar *buffer;
  const char *error_message;
  int error_code;
  buffer = NULL;
  xmlKeepBlanksDefault (0);
  doc = xmlParseFile ((const char *) filename);
  if (!doc)
    {
      error_message = _("Unable to parse the input file");
      goto exit_on_error;
    }
  node = xmlDocGetRootElement (doc);
  if (!node || xmlStrcmp (node->name, XML_FRACTAL))
    {
      error_message = _("Bad XML root node");
      goto exit_on_error;
    }
  width = xml_node_get_uint_with_default (node, XML_WIDTH, WIDTH, &error_code);
  if (error_code)
    {
      error_message = _("Bad width");
      goto exit_on_error;
    }
  height
    = xml_node_get_uint_with_default (node, XML_HEIGHT, HEIGHT, &error_code);
  if (error_code)
    {
      error_message = _("Bad height");
      goto exit_on_error;
    }
  length
    = xml_node_get_uint_with_default (node, XML_LENGTH, LENGTH, &error_code);
  if (error_code)
    {
      error_message = _("Bad length");
      goto exit_on_error;
    }
  random_seed
    = xml_node_get_ulong_with_default (node, XML_SEED, SEED, &error_code);
  if (error_code)
    {
      error_message = _("Bad random seed");
      goto exit_on_error;
    }
  nthreads
    = xml_node_get_uint_with_default (node, XML_THREADS, threads_number (),
                                      &error_code);
  if (!nthreads || error_code)
    {
      error_message = _("Bad threads number");
      goto exit_on_error;
    }
  buffer = xmlGetProp (node, XML_DIAGONAL);
  if (!buffer || !xmlStrcmp (buffer, XML_NO))
    fractal_diagonal = 0;
  else if (!xmlStrcmp (buffer, XML_YES))
    fractal_diagonal = 1;
  else
    {
      error_message = _("Bad diagonal movement");
      goto exit_on_error;
    }
  xmlFree (buffer);
  buffer = xmlGetProp (node, XML_3D);
  if (!buffer || !xmlStrcmp (buffer, XML_NO))
    fractal_3D = 0;
  else if (!xmlStrcmp (buffer, XML_YES))
    fractal_3D = 1;
  else
    {
      error_message = _("Bad 3D");
      goto exit_on_error;
    }
  xmlFree (buffer);
  buffer = xmlGetProp (node, XML_ANIMATE);
  if (!buffer || !xmlStrcmp (buffer, XML_YES))
    animating = 1;
  else if (!xmlStrcmp (buffer, XML_NO))
    animating = 0;
  else
    {
      error_message = _("Bad animation");
      goto exit_on_error;
    }
  xmlFree (buffer);
  buffer = xmlGetProp (node, XML_TYPE);
  if (!buffer || !xmlStrcmp (buffer, XML_TREE))
    fractal_type = FRACTAL_TYPE_TREE;
  else if (!xmlStrcmp (buffer, XML_FOREST))
    fractal_type = FRACTAL_TYPE_FOREST;
  else if (!xmlStrcmp (buffer, XML_NEURON))
    fractal_type = FRACTAL_TYPE_NEURON;
  else
    {
      error_message = _("Unknown fractal type");
      goto exit_on_error;
    }
  xmlFree (buffer);
  buffer = xmlGetProp (node, XML_RANDOM_SEED);
  if (!buffer || !xmlStrcmp (buffer, XML_CLOCK))
    random_seed_type = RANDOM_SEED_TYPE_CLOCK;
  else if (!xmlStrcmp (buffer, XML_DEFAULT))
    random_seed_type = RANDOM_SEED_TYPE_DEFAULT;
  else if (!xmlStrcmp (buffer, XML_FIXED))
    random_seed_type = RANDOM_SEED_TYPE_FIXED;
  else
    {
      error_message = _("Unknown random seed type");
      goto exit_on_error;
    }
  xmlFree (buffer);
  buffer = xmlGetProp (node, XML_RANDOM_TYPE);
  if (!buffer || !xmlStrcmp (buffer, XML_MT19937))
    random_algorithm = 0;
  else if (!xmlStrcmp (buffer, XML_RANLXS0))
    random_algorithm = 1;
  else if (!xmlStrcmp (buffer, XML_RANLXS1))
    random_algorithm = 2;
  else if (!xmlStrcmp (buffer, XML_RANLXS2))
    random_algorithm = 3;
  else if (!xmlStrcmp (buffer, XML_RANLXD1))
    random_algorithm = 4;
  else if (!xmlStrcmp (buffer, XML_RANLXD2))
    random_algorithm = 5;
  else if (!xmlStrcmp (buffer, XML_RANLUX))
    random_algorithm = 6;
  else if (!xmlStrcmp (buffer, XML_RANLUX389))
    random_algorithm = 7;
  else if (!xmlStrcmp (buffer, XML_CMRG))
    random_algorithm = 8;
  else if (!xmlStrcmp (buffer, XML_MRG))
    random_algorithm = 9;
  else if (!xmlStrcmp (buffer, XML_TAUS2))
    random_algorithm = 10;
  else if (!xmlStrcmp (buffer, XML_GFSR4))
    random_algorithm = 11;
  else
    {
      error_message = _("Unknown random algorithm");
      goto exit_on_error;
    }
  xmlFree (buffer);
  xmlFreeDoc (doc);
  return 1;

exit_on_error:
  show_error (error_message);
  xmlFree (buffer);
  xmlFreeDoc (doc);
  return 0;
}

/**
 * Function with the main bucle to draw the fractal.
 */
void
fractal ()
{
  const gsl_rng_type *random_type[N_RANDOM_TYPES] = {
    gsl_rng_mt19937,
    gsl_rng_ranlxs0,
    gsl_rng_ranlxs1,
    gsl_rng_ranlxs2,
    gsl_rng_ranlxd1,
    gsl_rng_ranlxd2,
    gsl_rng_ranlux,
    gsl_rng_ranlux389,
    gsl_rng_cmrg,
    gsl_rng_mrg,
    gsl_rng_taus2,
    gsl_rng_gfsr4
  };
  FILE *file;
  unsigned int i;

// PARALLELIZING DATA
  gsl_rng *rng[nthreads];
  GThread *thread[nthreads];

  t0 = time (NULL);
#if DEBUG
  printf ("t0=%lu\n", t0);
#endif

#if DEBUG
  printf ("Opening log file\n");
#endif
  file = fopen ("log", "w");

#if DEBUG
  printf ("Opening pseudo-random generators\n");
#endif
  for (i = 0; i < nthreads; ++i)
    {
      rng[i] = gsl_rng_alloc (random_type[random_algorithm]);
      switch (random_seed_type)
        {
        case RANDOM_SEED_TYPE_DEFAULT:
          break;
        case RANDOM_SEED_TYPE_CLOCK:
          gsl_rng_set (rng[i], (unsigned long) clock () + i);
          break;
        default:
          gsl_rng_set (rng[i], random_seed + i);
        }
    }

// END

  breaking = 0;
  simulating = 1;

#if DEBUG
  printf ("Updating simulator dialog\n");
#endif
  dialog_simulator_update ();

#if DEBUG
  printf ("Starting medium\n");
#endif
  medium_start ();

#if DEBUG
  printf ("Main bucle\n");
#endif
  do
    {
#if DEBUG
      printf ("Calculating fractal\n");
#endif
// PARALLELIZING CALLS
      for (i = 0; i < nthreads; ++i)
        thread[i] = g_thread_new (NULL, (void (*)) parallel_fractal, rng[i]);
      for (i = 0; i < nthreads; ++i)
        g_thread_join (thread[i]);
// END

#if DEBUG
      printf ("Updating simulator dialog\n");
#endif
      dialog_simulator_progress ();

// DISPLAYING DRAW
#if DEBUG
      printf ("Redisplaying draw\n");
#endif
      draw ();
// END

#if DEBUG
      printf ("Saving log data\n");
#endif
      fprintf (file, "%d %d\n", max_d, npoints);
    }
  while (!breaking);

#if DEBUG
  printf ("Closing log file\n");
#endif
  fclose (file);

#if DEBUG
  printf ("Updating simulator dialog\n");
#endif
  breaking = simulating = 0;
  dialog_simulator_update ();

#if DEBUG
  printf ("Freeing threads\n");
#endif
  for (i = 0; i < nthreads; ++i)
    gsl_rng_free (rng[i]);
  g_slice_free1 (medium_bytes, medium);
}


/* monte/gsl_monte_vegas.h
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Michael Booth
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* header for the gsl "vegas" routines.  Mike Booth, May 1998 */

#ifndef __GSL_MONTE_VEGAS_H__
#define __GSL_MONTE_VEGAS_H__

#include <gsl/gsl_rng.h>
#include <gsl/gsl_monte.h>

#undef __BEGIN_DECLS
#undef __END_DECLS
#ifdef __cplusplus
# define __BEGIN_DECLS extern "C" {
# define __END_DECLS }
#else
# define __BEGIN_DECLS /* empty */
# define __END_DECLS /* empty */
#endif

__BEGIN_DECLS

enum {GSL_VEGAS_MODE_IMPORTANCE = 1,
      GSL_VEGAS_MODE_IMPORTANCE_ONLY = 0,
      GSL_VEGAS_MODE_STRATIFIED = -1};

typedef struct {
  /* grid */
  size_t dim;
  size_t bins_max;
  unsigned int bins;
  unsigned int boxes; /* these are both counted along the axes */
  double * xi;
  double * xin;
  double * delx;
  double * weight;
  double vol;

  double * x;
  int * bin;
  int * box;

  /* distribution */
  double * d;

  /* control variables */
  double alpha;
  int mode;
  int verbose;
  unsigned int iterations;
  int stage;

  /* scratch variables preserved between calls to vegas1/2/3  */
  double jac;
  double wtd_int_sum;
  double sum_wgts;
  double chi_sum;
  double chisq;

  double result;
  double sigma;

  unsigned int it_start;
  unsigned int it_num;
  unsigned int samples;
  unsigned int calls_per_box;

  FILE * ostream;

} gsl_monte_vegas_state;

int gsl_monte_vegas_integrate(gsl_monte_function * f,
                              double xl[], double xu[],
                              size_t dim, size_t calls,
                              gsl_rng * r,
                              gsl_monte_vegas_state *state,
                              double* result, double* abserr);

gsl_monte_vegas_state* gsl_monte_vegas_alloc(size_t dim);

int gsl_monte_vegas_init(gsl_monte_vegas_state* state);

void gsl_monte_vegas_free (gsl_monte_vegas_state* state);

__END_DECLS

#endif /* __GSL_MONTE_VEGAS_H__ */


/* Output the current topic and POV assignments in text in text form
   to stdout, optionally iteratively maximizing the assignments first
   (to find a high-probability assignments). Even if performing
   maximization, the current assignments should be post-burn-in for
   best results. */

#include <assert.h>
#include <gsl/gsl_rng.h>
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "index.h"
#include "parse_mmaps.h"
#include "probability.h"
#include "sample.h"

int main(int argc, char **argv) {
  if (argc != 4) {
    printf("Usage: %s mmap_directory maximization_iterations threads\n",
           argv[0]);
    exit(1);
  }
  int maximization_iterations = atoi(argv[2]);
  int num_threads = atoi(argv[3]);
  struct mmap_info mmap_info = open_mmaps_readonly(argv[1]);
  int64_t count_revisions;
  struct revision_assignment* revision_assignments;
  get_revision_assignment_array(&mmap_info, &count_revisions, &revision_assignments);

  if (maximization_iterations > 0) {
    struct sample_threads sample_threads;
    initialize_threads(&sample_threads, num_threads, &mmap_info);
    for (int i = 0; i < maximization_iterations; ++i) {
      resample_maximize(&sample_threads);
    }
    destroy_threads(&sample_threads);
  }
  for (int64_t revision_num = 0; revision_num < count_revisions; ++revision_num) {
    if (revision_assignments[revision_num].pov < 0
	|| revision_assignments[revision_num].topic < 0) {
      continue;
    }
    printf("%" PRId64 " %d %d\n",
	   revision_num,
	   revision_assignments[revision_num].topic,
	   revision_assignments[revision_num].pov);
  }
  close_mmaps(mmap_info);
}


/*! \file allvars.h
 *  \brief declares global variables and structures.
 *
 *  This file declares all global variables and structures. Further variables should be added here, and declared as
 *  \e \b extern. The actual existence of these variables is provided by the file \ref allvars.cxx. To produce
 *  \ref allvars.cxx from \ref allvars.h, do the following:
 *
 *     \arg Erase all \#define's, typedef's, and enum's
 *     \arg add \#include "allvars.h", delete the \#ifndef ALLVARS_H conditional
 *     \arg delete all keywords 'extern'
 *     \arg delete all struct definitions enclosed in {...}, e.g.
 *        "extern struct global_data_all_processes {....} All;"
 *        becomes "struct global_data_all_processes All;"
 */

#ifndef ALLVARS_H
#define ALLVARS_H
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <cmath>
#include <string>
#include <getopt.h>
#include <sys/stat.h>
#include <sys/timeb.h>

//gsl stuff
#include <gsl/gsl_heapsort.h>

//nbody code
#include <NBody.h>
#include <NBodyMath.h>
#include <KDTree.h>

#ifdef USEOPENMP
#include <omp.h>
#endif

#ifdef USEMPI
#include <mpi.h>
///Includes external global variables used for MPI version of code
#include "mpivar.h"
#endif

#include "../../src/gadgetitems.h"
#include "../../src/tipsy_structs.h"

//for morphological classifications (where tidal tails, or completely disrupted)
#define NUMMORPHCLASS 3;
#define QTIDAL 0.60
#define STIDAL 0.40
#define QLIMTIDAL 0.30
#define SLIMTIDAL 0.25
#define VFTIDAL 0.85
#define VFLIMTIDAL 0.50
#define V2TIDAL 0.6
#define V3TIDAL 0.4
#define V2LIMTIDAL 0.35
#define V3LIMTIDAL 0.30

#define ETIDAL 0.90
#define ELIMTIDAL 0.50

//number of bands in which to calculate magnitude
#define NBANDS 9

///define the particle types
//@{
#define NPARTTYPES 5
#define ALLTYPE 4
#define GASTYPE 0
#define DMTYPE 1
#define STARTYPE 2
#define BHTYPE 3
//@}

///\todo need to alter how stats are stored if one can update the number of quantiles stored
///the number of values that is used to characterize a bulk distribution
///where idea is 0=mean,1=sd,2-4=quantiles of 0.16,0.5,0.84
#define NDISTRIB 5
///max number of quantiles
#define MAXNQUANTS 11

///max number of radial bins
#define NRAD 1000

/// Structures and external variables

///for stars,gas, define lower limits for Z and sfr
#define MINGASZ 1e-7
#define MINSTARZ 1e-7
#define MINSFR 1e-3

///define sphi io block points for gadget
//@{
///self energy
#define sphioblock_u 0
///density
#define sphioblock_d 1
#ifdef SPHCOOLING
///mean molecular weight
#define sphioblock_mmw 2
///neutral hydrogen abundance
#define sphioblock_nha 3
#endif
///smoothing lengths
#define sphioblock_h
//@}

///define different reference frames
//@{
///cm ref
#define ICMFRAME 1
///particle with largest potential
#define IPOTFRAME 2
///most bound particle based in input data
#define IMBFRAME 3
//@}

///define omp number of elements threshold
#define ompunbindnum 1000
#define omppropnum 50000

///minimum number of particles for properties to be calculated
#define MINNPROP 10
///minimum number of particles for a profile to be calculated
#define MINNPROFILE 100
///minimum mass fraction of candidate structure for enclosed quantities to be useful
#define MINMASSFRAC 0.05

///some useful constants for gas
//@{
///mass of helium relative to hydrogen
#define M_HetoM_H 4.0026
//@}

using namespace std;
using namespace Math;
using namespace NBody;

///for potential calculation
struct PotInfo
{
    ///bucket size for tree calculation
    int BucketSize;
    ///for tree potential calculation;
    Double_t TreeThetaOpen;
    ///gravitational softening (here simple plummer)
    Double_t eps;
    ///\todo When calculating the thermal internal energy of gas particles
    ///I should probably include some temperature floors and ceilings
    ///and perhaps some sound speed constrains. But I should produce
    ///properties of the baryonic material for all, gravitationally self-bound and grav+therm bound

    PotInfo(){
        BucketSize=8;
        TreeThetaOpen=0.7;
        eps=0.01;
    }
};

///Options structure
struct Options
{
    ///filenames
    //@{
    ///input particle data and output name
    char *fname,*outname;
    ///halo catalog base name
    char *halocatname;
    ///optional config file name
    char *configname;
    ///optional stellar synthesis model file name
    char *magname;
    //@}
    ///number of files per particle data snapshot, if zero then tipsy file
    int nfiles;
    ///number of files per halo catalog
    int nfileshalocat;
    ///number of sph blocks in gadget file
    int sphnio;
    ///defining units and cosmology
    //@{
    ///length,m,v,grav units pressure unit, temperature
    Double_t L, M, V, G, PUnit, TUnit;
    ///scalbodydata/utils/haloanalcode/e factor, Hubunit, h and cosmology
    Double_t a,H,h, Omega_m, Omega_Lambda,rhoc;
    ///softening length and period (comove)
    Double_t eps, p;
    ///background density and virial level
    Double_t rhobg, virlevel;
    int comove;
    ///for gas, mass fraction of hydrogen
    Double_t Xh;
    ///for gas, boltzmann constant
    Double_t Boltzmann;
    ///for gas, number of hydrogen atoms per sph particle
    Double_t N_H;
    ///for gas, correction for helium to mass of sph particle /mh ie: Number of particles = M_p / (M_H*Xh+M_He*(1-Xh)) or even M_H*Xh+M_He*(1-Xh)*Y_Li+M_Li*(1-Xh)*(1-Y_Li) ...
    Double_t N_He_corr;
    ///for gas, relation of specific internal energy to Temperature (u=3/2*k_B*(N_H*(Xh+(1-Xh)*N_He)*T
    Double_t utoT;
    ///for gas, temperature threshold when calculating X-ray based spectroscopic-like temperature)
    Double_t Tempthreshold;
    ///\todo may want some specific stuff for stars too
    //@}

    ///store number of particles
    Int_t npart[NPARTTYPES];

    ///structure that contains variables for potentialcalc
    PotInfo pinfo;
    Double_t error;

    ///quantile points for characterization of distribution
    int nquants;
    Double_t quants[MAXNQUANTS];

    ///minimum number a halo has to have to be analyzed
    Int_t minnum;
    ///Kinetic/Potential Energy ratio
    Double_t TVratio;
    ///number of mpi files that were written in parallel that must be read for group_catalog files
    int mpinum;

    ///for calculaion of CM
    Double_t cmfrac, cmadjustfac;
    PotInfo uinfo;

    ///for calculating kinetic energy to a reference frame
    int reframe;

    ///if no mass is stored
    Double_t MassValue;

    ///if zoom simulations, store effective resolution
    long int Neff;

    ///for verbose output
    int verbose;

    ///store whether potential has been already calculated as object has been moved to particle with deepest potential well.
    int ipotcalc;

    ///used to calculate kinetic reference frame about particle at bottom of potential well
    Int_t Nbref;
    Double_t fracbref;

    ///input STF/VELOCIraptor format flags
    //@{
    int iseparatefiles,ibinaryin;
    //@}

    ///output format
    int ibinaryout;

    Options()
    {
        fname=outname=halocatname=NULL;
        nfiles=1;
        sphnio=2;
        nfileshalocat=1;

        L = 1.0;
        M = 1.0;
        V = 1.0;
        PUnit=5.588e-14;//(assumes T in kelvin) makes pressure into ergs/cm^3 assuming standard gadget units (this is boltzmann's constant / proton mass *Massunit_g/(Lengthunit_cm)^3
        TUnit=1.0;
        MassValue=1.0;

        p = 0.0;
        a = 1.0;
        H = 0.1;
        h = 1.0;
        Omega_m = 1.0;
        Omega_Lambda = 0.0;
        rhobg = 1.0;
        virlevel = 50.;
        comove=0;
        G = 43.01;//for G in units of (km/s)^2 Mpc/1e10 Msun
        rhoc=27.753;//for same units as above, of course missing factors of h^2
        G = 43021.1349;//for G in units of (km/s)^2 kpc/1e10 Msun
        rhoc=2.7753e-8;

        Xh=0.76;
        Boltzmann=log(6.94107285)-70.0*log(10.0); //ln of boltzmann in units of K^(-1) (km/s)^2 (1e10 Msun)
        N_H=log(1.1892)+67.0*log(10.0); //number of hydrogen atoms per 10^10 Msun
        N_He_corr=-log(Xh+M_HetoM_H*(1.0-Xh));
        utoT=log(1.5)+Boltzmann+N_H+N_He_corr;
        Tempthreshold=7.0*log(10.0);

        error=1e-3;

        nquants=3;
        quants[0]=0.16;quants[1]=0.5;quants[2]=0.84;
        for (int i=0;i<NPARTTYPES;i++) npart[i]=0;

        minnum=20;
        TVratio=-1.0;
        mpinum=0;

        cmfrac=0.1;
        cmadjustfac=0.7;
        Neff=-1;

        reframe=IPOTFRAME;
        verbose=0;

        ipotcalc=1;
        Nbref=10;
        fracbref=0.05;

        iseparatefiles=0;
        ibinaryin=0;
        ibinaryout=0;

    }
};

///Radial profile data in spherical shells
struct RadPropData
{
    ///To store radial profiles
    //@{
    Int_t nbins;
    int ptype;
    Double_t *numinbin;
    Double_t *radval;
    Double_t *Menc;
    Coordinate *velenc;
    Matrix *sigmavenc;
    Coordinate *Jenc;
    Double_t *qenc;
    Double_t *senc;
    Matrix *eigvecenc;
    ///primarily for sph particles
    Double_t *Den;
    ///gas/star particle info
    Double_t *Tempenc;
    Double_t *Tdispenc;
    Double_t *Temenc;
    Double_t *Tslenc;
#ifdef RADIATIVE
    Double_t *Zmetenc;
    Double_t *Tageenc;
#endif
//@}
    RadPropData(){
        //for (int i=0;i<NRAD;i++) {numinbin[i]=radval[i]=Menc[i]=0.;Jenc[i]=Coordinate(0,0,0);velenc[i]=Coordinate(0,0,0);qenc[i]=senc[i]=1.0;}
        ptype=DMTYPE;
        numinbin=NULL;radval=NULL;Menc=NULL;velenc=NULL;sigmavenc=NULL;Jenc=NULL;qenc=NULL;senc=NULL;eigvecenc=NULL;
        Den=NULL;Tempenc=NULL;Tdispenc=NULL;Temenc=NULL;Tslenc=NULL;
#ifdef RADIATIVE
        Zmetenc=NULL;
        Tageenc=NULL;
#endif
    }
    void SetBins(int N) {
        nbins=N;
        if(numinbin!=NULL) delete[] numinbin;numinbin=new Double_t[nbins];
        if(radval!=NULL) delete[] radval;radval=new Double_t[nbins];
        if(Menc!=NULL) delete[] Menc;Menc=new Double_t[nbins];
        if(velenc!=NULL) delete[] velenc;velenc=new Coordinate[nbins];
        if(sigmavenc!=NULL) delete[] sigmavenc;sigmavenc=new Matrix[nbins];
        if(Jenc!=NULL) delete[] Jenc;Jenc=new Coordinate[nbins];
        if(qenc!=NULL) delete[] qenc;qenc=new Double_t[nbins];
        if(senc!=NULL) delete[] senc;senc=new Double_t[nbins];
        if(eigvecenc!=NULL) delete[] eigvecenc;eigvecenc=new Matrix[nbins];
        if(Tempenc!=NULL) delete[] Tempenc;Tempenc=new Double_t[nbins];
        if(Tdispenc!=NULL) delete[] Tdispenc;Tdispenc=new Double_t[nbins];
        if(Temenc!=NULL) delete[] Temenc;Temenc=new Double_t[nbins];
        if(Tslenc!=NULL) delete[] Tslenc;Tslenc=new Double_t[nbins];
        if(Den!=NULL) delete[] Den;Den=new Double_t[nbins];
#ifdef RADIATIVE
        if(Zmetenc!=NULL) delete[] Zmetenc;Zmetenc=new Double_t[nbins];
        if(Tageenc!=NULL) delete[] Tageenc;Tageenc=new Double_t[nbins];
#endif
        for (int i=0;i<nbins;i++) {numinbin[i]=radval[i]=Menc[i]=0;Jenc[i]=Coordinate(0,0,0);velenc[i]=Coordinate(0,0,0);qenc[i]=senc[i]=1.0;}
        for (int i=0;i<nbins;i++) {Tempenc[i]=Tdispenc[i]=Den[i]=Temenc[i]=Tslenc[i]=0.;}
    }
};

///Cylindrical profile data
struct CylPropData
{
    ///reference axis of cylinder
    Coordinate zref;
    ///To store cylindrical profiles
    //@{
    Int_t nbins;
    Double_t *numinbin;
    Double_t *radval;
    Double_t *Menc;
    Coordinate *Jenc;
    Coordinate *velenc;
    Matrix *sigmavenc;
    Double_t *zmean;
    Double_t *Rmean;
    Double_t *zdisp;
    Double_t *Rdisp;
    Double_t *qenc;
    Double_t *senc;
    Matrix *eigvecenc;
    ///primarily for sph particles
    Double_t *Den;
    ///gas/star particle info
    Double_t *Tempenc;
    Double_t *Tdispenc;
    Double_t *Temenc;
    Double_t *Tslenc;
#ifdef RADIATIVE
    Double_t *Zmetenc;
    Double_t *Tageenc;
#endif
    //@}
    CylPropData(){
        numinbin=NULL;radval=NULL;Menc=NULL;velenc=NULL;sigmavenc=NULL;Jenc=NULL;zmean=NULL;Rmean=NULL;zdisp=NULL;Rdisp=NULL;qenc=NULL;senc=NULL;eigvecenc=NULL;
        Den=NULL;Tempenc=NULL;Tdispenc=NULL;Temenc=NULL;Tslenc=NULL;
#ifdef RADIATIVE
        Zmetenc=NULL;
        Tageenc=NULL;
#endif
    }
    void SetBins(int N) {
        nbins=N;
        if(numinbin!=NULL) delete[] numinbin;numinbin=new Double_t[nbins];
        if(radval!=NULL) delete[] radval;radval=new Double_t[nbins];
        if(Menc!=NULL) delete[] Menc;Menc=new Double_t[nbins];
        if(velenc!=NULL) delete[] velenc;velenc=new Coordinate[nbins];
        if(sigmavenc!=NULL) delete[] sigmavenc;sigmavenc=new Matrix[nbins];
        if(Jenc!=NULL) delete[] Jenc;Jenc=new Coordinate[nbins];
        if(zmean!=NULL) delete[] zmean;zmean=new Double_t[nbins];
        if(Rmean!=NULL) delete[] Rmean;Rmean=new Double_t[nbins];
        if(zdisp!=NULL) delete[] zdisp;zdisp=new Double_t[nbins];
        if(Rdisp!=NULL) delete[] Rdisp;Rdisp=new Double_t[nbins];
        if(qenc!=NULL) delete[] qenc;qenc=new Double_t[nbins];
        if(senc!=NULL) delete[] senc;senc=new Double_t[nbins];
        if(eigvecenc!=NULL) delete[] eigvecenc;eigvecenc=new Matrix[nbins];
        if(Tempenc!=NULL) delete[] Tempenc;Tempenc=new Double_t[nbins];
        if(Tdispenc!=NULL) delete[] Tdispenc;Tdispenc=new Double_t[nbins];
        if(Temenc!=NULL) delete[] Temenc;Temenc=new Double_t[nbins];
        if(Tslenc!=NULL) delete[] Tslenc;Tslenc=new Double_t[nbins];
        if(Den!=NULL) delete[] Den;Den=new Double_t[nbins];
#ifdef RADIATIVE
        if(Zmetenc!=NULL) delete[] Zmetenc;Zmetenc=new Double_t[nbins];
        if(Tageenc!=NULL) delete[] Tageenc;Tageenc=new Double_t[nbins];
#endif
        for (int i=0;i<nbins;i++) {numinbin[i]=radval[i]=Menc[i]=0.;Jenc[i]=velenc[i]=Coordinate(0,0,0);qenc[i]=senc[i]=1.0;zmean[i]=Rmean[i]=zdisp[i]=Rdisp[i]=0.;}
        for (int i=0;i<nbins;i++) {Tempenc[i]=Tdispenc[i]=Den[i]=Temenc[i]=Tslenc[i]=0.;}
    }
};

///Based on PropData in \ref allvars.h of STF/VELOCIraptor
///Here the data also stores the type of particles it corresponds to
struct PropData
{
    ///type of structure, dm, gas, star, etc
    Int_t ptype;
    ///number of particles
    Int_t num;
    ///centre of mass
    Coordinate gcm, gcmvel;
    ///Position of most bound particle
    Coordinate gpos, gvel;
    ///physical properties regarding mass, size
    Double_t gmass,gsize,gMvir,gRvir,gRmbp,gmaxvel,gRmaxvel,gMmaxvel;
    ///physical properties for shape/mass distribution
    Double_t gq,gs;
    Matrix geigvec;
    ///physical properties for velocity
    Double_t gsigma_v;
    Matrix gveldisp;
    ///physical properties for dynamical state
    Double_t Efrac,Pot,T;
    ///physical properties for dynamical state for specific particle types
    Double_t Efractyped[NPARTTYPES],Pottyped[NPARTTYPES],Ttyped[NPARTTYPES];
    ///physical properties for angular momentum
    Coordinate gJ;

    ///metallicity, temperature, pressure,etc
    Double_t Temp[NDISTRIB];
#ifdef RADIATIVE
    Double_t Zmet[NDISTRIB];
    ///stellar age
    Double_t Tage[NDISTRIB];
    ///luminosity, which for gas which would probably be Infrared and radio bands
    Double_t LUM[NBANDS];
#endif

    ///To store cylindrical profiles
    RadPropData radprofile;
    ///To store cylindrical profiles
    CylPropData cylprofile;

    PropData(){
        num=0;
        gmass=gsize=gRmaxvel=Efrac=Pot=T=gcm[0]=gcm[1]=gcm[2]=gcmvel[0]=gcmvel[1]=gcmvel[2]=0.;
        gveldisp=Matrix(0.);
        gq=gs=1.0;
        gRmbp=0.0;

        for (int i=0;i<NDISTRIB;i++)Temp[i]=0;
#ifdef RADIATIVE
        for (int i=0;i<NDISTRIB;i++)Zmet[i]=Tage[i]=0.;
        for (int i=0;i<NBANDS;i++)LUM[i]=0;
#endif
    }
};

///used to store halo particle id data
struct HaloParticleData{
    ///store the halo id
    long unsigned haloID;
    ///number of particles, eventually used to store bound number
    long unsigned NumberofParticles;
    ///number of particles of different types, eventually used to store bound number
    Int_t NumofType[NPARTTYPES];
    ///number of all particles
    long unsigned AllNumberofParticles;
    ///number of all particles of different types
    Int_t AllNumofType[NPARTTYPES];

    ///offset of halo's particle in list of all particles in structures
    Int_t noffset;
    ///array of particle ids
    long unsigned *ParticleID;
    HaloParticleData(long unsigned numinhalo=0){
        AllNumberofParticles=NumberofParticles=numinhalo;
        if (NumberofParticles>0) {
            ParticleID=new long unsigned[NumberofParticles];
        }
        for (int i=0;i<NPARTTYPES;i++) AllNumofType[i]=NumofType[i]=0;
    }
    void Alloc(long unsigned ninhalos=0){
        if (AllNumberofParticles>0){
            delete[] ParticleID;
        }
        AllNumberofParticles=NumberofParticles=ninhalos;
        if (AllNumberofParticles>0) {
            ParticleID=new long unsigned[NumberofParticles];
        }
    }
    ~HaloParticleData(){
        if (NumberofParticles>0){
            delete[] ParticleID;
        }
    }
};

///External types for tipsy
//@{
#define TGASTYPE 0;
#define TDARKTYPE 1;
#define TSTARTYPE 2;
//@}

///note that for grouped particles type is 10+TYPE.
#define SUBSTRUCTTYPE 10;

#endif