/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (2017)
  
	Adresses mèl :
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Damien
	CALISTE, laboratoire L_Sim, (2017)

	E-mail addresses :
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/

#include "sfielddata.h"

/**
 * SECTION:sfielddata
 * @short_description: Defines a specialised #VisuScalarField class
 * based on data on a grid.
 *
 * <para>This kind of scalar fields is a simple scalar field with data
 * available on a grid, regular or not. Data can be read from a file
 * or set directly.</para>
 */


struct _VisuScalarFieldDataPrivate
{
  /* Datas. */
  gboolean empty;
  double ***data;
  GArray *arr;

  /* Minimum and maximum values. */
  double min, max;
};

static void visu_scalar_field_data_finalize(GObject* obj);
static gboolean _isEmpty(const VisuScalarField *self);
static gboolean _setGridSize(VisuScalarField *self, const guint grid[3]);
static double _getAt(const VisuScalarField *self, int i, int j, int k);
static gboolean _getValue(const VisuScalarField *self, float xyz[3],
                          double *value, float extension[3]);
static void _getMinMax(const VisuScalarField *self, double minmax[2]);

static gboolean scalarFieldLoad_fromAscii(VisuScalarFieldMethod *meth,
                                          VisuScalarFieldMethodData *data,
                                          GCancellable *cancel, GError **error);

G_DEFINE_TYPE_WITH_CODE(VisuScalarFieldData, visu_scalar_field_data,
                        VISU_TYPE_SCALAR_FIELD,
                        G_ADD_PRIVATE(VisuScalarFieldData))

static void visu_scalar_field_data_class_init(VisuScalarFieldDataClass *klass)
{
  const gchar *type[] = {"*.pot", "*.dat", (char*)0};
  const gchar *descr = _("Potential/density files");

  DBG_fprintf(stderr, "VisuScalarFieldData: creating the class of the object.\n");

  /* DBG_fprintf(stderr, "                - adding new signals ;\n"); */

  /* Connect freeing methods. */
  G_OBJECT_CLASS(klass)->finalize = visu_scalar_field_data_finalize;
  VISU_SCALAR_FIELD_CLASS(klass)->isEmpty = _isEmpty;
  VISU_SCALAR_FIELD_CLASS(klass)->setGridSize = _setGridSize;
  VISU_SCALAR_FIELD_CLASS(klass)->getAt = _getAt;
  VISU_SCALAR_FIELD_CLASS(klass)->getValue = _getValue;
  VISU_SCALAR_FIELD_CLASS(klass)->getMinMax = _getMinMax;

  visu_scalar_field_method_new(descr, type, scalarFieldLoad_fromAscii,
                               G_PRIORITY_LOW);
}

static void visu_scalar_field_data_init(VisuScalarFieldData *obj)
{
  DBG_fprintf(stderr, "VisuScalarFieldData: creating a new scalar field operator (%p).\n", (gpointer)obj);

  obj->priv = visu_scalar_field_data_get_instance_private(obj);

  obj->priv->empty   = TRUE;
  obj->priv->data    = (double***)0;
  obj->priv->arr     = (GArray*)0;
  obj->priv->min     = G_MAXFLOAT;
  obj->priv->max     = -G_MAXFLOAT;
}
static void visu_scalar_field_data_finalize(GObject* obj)
{
  guint i;
  guint grid[3];
  VisuScalarFieldData *self = VISU_SCALAR_FIELD_DATA(obj);
  DBG_fprintf(stderr, "VisuScalarFieldData: finalize object %p.\n", (gpointer)obj);

  if (self->priv->data)
    {
      visu_scalar_field_getGridSize(VISU_SCALAR_FIELD(obj), grid);
      for (i = 0; i < grid[0]; i++)
        g_free(self->priv->data[i]);
      g_free(self->priv->data);
    }
  if (self->priv->arr)
    g_array_unref(self->priv->arr);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_scalar_field_data_parent_class)->finalize(obj);
}

/**
 * visu_scalar_field_data_new_fromFile:
 * @filename: (type filename): the path to the file to be loaded ;
 * @table: (allow-none): a set of different options (can be NULL).
 * @cancel: (allow-none): a #GCancellable object.
 * @callback: (allow-none): a callback when the load finishes.
 * @user_data: (scope async): some user data to pass to the callback.
 *
 * Read the given file and try to load it as a scalar field file. If succeed,
 * all read fields are appended to the @fieldList argument. If an error
 * occurs, it is stored into @error. When entering the routine, *@error must be NULL.
 * If @table is given, it means that the caller routine gives some options to the loader
 * routine. These options are a set of names and values.
 *
 * If the file contains several fields, they must be loaded and added to @fieldList.
 *
 * Returns: (transfer full) (element-type VisuScalarField*):
 * a #GList to store read field(s).
 */
GList* visu_scalar_field_data_new_fromFile(const gchar *filename, GHashTable *table,
                                           GCancellable *cancel,
                                           GAsyncReadyCallback callback,
                                           gpointer user_data)
{
  return visu_scalar_field_method_load((VisuScalarFieldMethod*)0, filename, table,
                                       cancel, callback, user_data);
}
/**
 * visu_scalar_field_data_new_fromFileSync:
 * @filename: (type filename): the path to the file to be loaded ;
 * @table: (allow-none): a set of different options (can be NULL).
 * @cancel: (allow-none): a #GCancellable object.
 * @error: (allow-none): an error location.
 *
 * Like visu_scalar_field_data_new_fromFile(), but synchronous.
 *
 * Since: 3.8
 *
 * Returns: (transfer full) (element-type VisuScalarField*):
 * a #GList to store read field(s).
 */
GList* visu_scalar_field_data_new_fromFileSync(const gchar *filename, GHashTable *table,
                                               GCancellable *cancel, GError **error)
{
  return visu_scalar_field_method_loadSync((VisuScalarFieldMethod*)0, filename,
                                           table, cancel, error);
}

static gboolean _isEmpty(const VisuScalarField *self)
{
  g_return_val_if_fail(VISU_IS_SCALAR_FIELD_DATA(self), TRUE);

  return VISU_SCALAR_FIELD_DATA(self)->priv->empty;
}

static void _getMinMax(const VisuScalarField *self, double minmax[2])
{
  VisuScalarFieldData *field;
  
  g_return_if_fail(VISU_IS_SCALAR_FIELD_DATA(self));

  field = VISU_SCALAR_FIELD_DATA(self);
  minmax[0] = field->priv->min;
  minmax[1] = field->priv->max;
}

static gboolean _emitEmpty(gpointer data)
{
  g_object_notify(G_OBJECT(data), "empty");
  return G_SOURCE_REMOVE;
}

static gboolean _emitChanged(gpointer data)
{
  g_signal_emit_by_name(G_OBJECT(data), "changed");
  return G_SOURCE_REMOVE;
}

static gboolean _setGridSize(VisuScalarField *self, const guint grid[3])
{
  guint i, j;
  guint nElements[3];
  VisuScalarFieldData *field;
  
  g_return_val_if_fail(VISU_IS_SCALAR_FIELD_DATA(self), FALSE);

  visu_scalar_field_getGridSize(self, nElements);
  if (!VISU_SCALAR_FIELD_CLASS(visu_scalar_field_data_parent_class)->setGridSize(self, grid))
    return FALSE;

  field = VISU_SCALAR_FIELD_DATA(self);
  /* If data was already allocated, we free it. */
  if (field->priv->data)
    {
      DBG_fprintf(stderr, " | free the previous data allocation.\n");
      for (i = 0; i < nElements[0]; i++)
        g_free(field->priv->data[i]);
      g_free(field->priv->data);
    }
  if (field->priv->arr)
    g_array_unref(field->priv->arr);

  DBG_fprintf(stderr, "Visu ScalarFieldData: allocating data array (%d,%d,%d).\n",
              grid[0], grid[1], grid[2]);
  field->priv->arr  = g_array_sized_new(FALSE, FALSE, sizeof(double),
                                        grid[0] * grid[1] * grid[2]);
  g_array_set_size(field->priv->arr, grid[0] * grid[1] * grid[2]);
  field->priv->data = g_malloc(sizeof(double **) * grid[0]);
  for(i = 0; i < grid[0]; i++)
    {
      field->priv->data[i] = g_malloc(sizeof(double *) * grid[1]);
      for(j = 0; j < grid[1]; j++)
        field->priv->data[i][j] = &g_array_index(field->priv->arr, double,
                                                 ((i * grid[1] + j) * grid[2]));
    }
  DBG_fprintf(stderr, " | allocation done.\n");
  return TRUE;
}
/**
 * visu_scalar_field_data_set:
 * @field: a #VisuScalarFieldData object ;
 * @data: (element-type double): an array containing data to be copied ;
 * @xyzOrder: a boolean.
 *
 * Set the data of the given @field. The array @data should be stored in z direction
 * first, followed by y and x if @xyzOrder is FALSE, or in the other
 * order when TRUE. The number of elements in the x, y and z directions
 * are read from field->priv->nElements. Then use visu_scalar_field_setGridSize()
 * before using this method.
 */
void visu_scalar_field_data_set(VisuScalarFieldData *field, GArray *data,
                                VisuScalarFieldDataOrder xyzOrder)
{
  guint i, j, k, ii;
  guint grid[3];
  
  g_return_if_fail(VISU_IS_SCALAR_FIELD_DATA(field) && data);

  visu_scalar_field_getGridSize(VISU_SCALAR_FIELD(field), grid);
  g_return_if_fail(data->len == grid[0] * grid[1] * grid[2]);
  
  DBG_fprintf(stderr, "VisuScalarField: set data from array %p (%d ele.).\n",
              (gpointer)data, data->len);
  field->priv->min = G_MAXFLOAT;
  field->priv->max = -G_MAXFLOAT;
  ii = 0;
  if (xyzOrder == VISU_SCALAR_FIELD_DATA_XYZ)
    for (k = 0 ; k < grid[2] ; k++)
      for (j = 0 ; j < grid[1] ; j++)
	for (i = 0 ; i < grid[0] ; i++)
	  {
	    field->priv->data[i][j][k] = ((double*)data->data)[ii];
	    field->priv->min = MIN(field->priv->data[i][j][k], field->priv->min);
	    field->priv->max = MAX(field->priv->data[i][j][k], field->priv->max);
	    ii += 1;
	  }
  else
    for (i = 0 ; i < grid[0] ; i++)
      for (j = 0 ; j < grid[1] ; j++)
	for (k = 0 ; k < grid[2] ; k++)
	  {
	    field->priv->data[i][j][k] = ((double*)data->data)[ii];
	    field->priv->min = MIN(field->priv->data[i][j][k], field->priv->min);
	    field->priv->max = MAX(field->priv->data[i][j][k], field->priv->max);
	    ii += 1;
	  }
  DBG_fprintf(stderr, " | done (%p).\n", (gpointer)g_thread_self());
  field->priv->empty = FALSE;
  g_idle_add(_emitEmpty, field);
  g_idle_add(_emitChanged, field);
}
/**
 * visu_scalar_field_data_getArray:
 * @field: a #VisuScalarFieldData object.
 *
 * The data are stored z first in a flatten array.
 *
 * Since: 3.7
 *
 * Returns: (transfer none) (element-type double): a pointer on the allocated data array.
 */
const GArray* visu_scalar_field_data_getArray(const VisuScalarFieldData *field)
{
  g_return_val_if_fail(VISU_IS_SCALAR_FIELD(field), (const GArray*)0);

  return field->priv->arr;
}
static double _getAt(const VisuScalarField *self, int i, int j, int k)
{
  guint grid[3];
  VisuScalarFieldData *field;

  g_return_val_if_fail(VISU_IS_SCALAR_FIELD_DATA(self), -1.);

  if (_isEmpty(self))
    return 0.;

  visu_scalar_field_getMeshInside(self, grid, i, j, k);

  field = VISU_SCALAR_FIELD_DATA(self);
  g_return_val_if_fail(field->priv->data, -1.);
  return field->priv->data[grid[0]][grid[1]][grid[2]];
}

static gboolean _getValue(const VisuScalarField *self, float xyz[3],
                          double *value, float extension[3])
{
  gfloat factor[3], x1, x2, x3;
  guint ijk[3], dijk[3];
  VisuScalarFieldData *field;

  g_return_val_if_fail(VISU_IS_SCALAR_FIELD_DATA(self), FALSE);

  *value = 0.;
  if (_isEmpty(self))
    return FALSE;

  /* lower left is ijk. */
  /* upper right is dijk. */
  if (!visu_scalar_field_getCoordInside(self, ijk, dijk, factor, xyz, extension))
    return FALSE;
  
  /* weight is factor. */
  x1 = factor[0];
  x2 = factor[1];
  x3 = factor[2];

  field = VISU_SCALAR_FIELD_DATA(self);
  g_return_val_if_fail(field->priv->data, FALSE);
  /* calculation of the density value */
  *value  = 0.f;
  *value += field->priv->data[ ijk[0]][ ijk[1]][ ijk[2]] *
    (1.f - x1) * (1.f - x2) * (1.f - x3);
  *value += field->priv->data[dijk[0]][ ijk[1]][ ijk[2]] * x1 * (1.f - x2) * (1.f - x3);
  *value += field->priv->data[ ijk[0]][dijk[1]][ ijk[2]] * (1.f - x1) * x2 * (1.f - x3);
  *value += field->priv->data[ ijk[0]][ ijk[1]][dijk[2]] * (1.f - x1) * (1.f - x2) * x3;
  *value += field->priv->data[dijk[0]][dijk[1]][ ijk[2]] * x1 * x2 * (1.f - x3);
  *value += field->priv->data[ ijk[0]][dijk[1]][dijk[2]] * (1.f  -x1) * x2 * x3;
  *value += field->priv->data[dijk[0]][ ijk[1]][dijk[2]] * x1 * (1.f - x2) * x3;
  *value += field->priv->data[dijk[0]][dijk[1]][dijk[2]] * x1 * x2 * x3;

  return TRUE;
}

#include <string.h>

#define MESH_FLAG             "meshType"
#define MESH_FLAG_UNIFORM     "uniform"
#define MESH_FLAG_NON_UNIFORM "nonuniform"

static gboolean scalarFieldLoad_fromAscii(VisuScalarFieldMethod *meth _U_,
                                          VisuScalarFieldMethodData *data,
                                          GCancellable *cancel _U_, GError **error)
{
  FILE *in;
  char rep[TOOL_MAX_LINE_LENGTH], flag[TOOL_MAX_LINE_LENGTH];
  char format[TOOL_MAX_LINE_LENGTH], period[TOOL_MAX_LINE_LENGTH];
  char *feed;
  gchar *comment;
  int res;
  guint i, j, k;
  guint size[3];
  double box[6], *mesh;
  VisuScalarFieldData *field;
  VisuBox *boxObj;
  gboolean periodic;
  VisuScalarFieldMeshFlags meshtype;

  g_return_val_if_fail(data, FALSE);
  g_return_val_if_fail(!error || (*error == (GError*)0), FALSE);

  DBG_fprintf(stderr, "VisuScalarField : try to read '%s' as a ASCII scalar"
	      " field data file.\n", visu_scalar_field_method_data_getFilename(data));

  in = fopen(visu_scalar_field_method_data_getFilename(data), "r");
  if (!in)
    {
      g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_ACCES,
		  _("impossible to open the file.\n"));
      return FALSE;
    }

  /* 1st line (comment) */
  if (!fgets(rep, TOOL_MAX_LINE_LENGTH, in))
    {
      fclose(in);
      return FALSE;
    }
  rep[strlen(rep)-1] = 0; /* skipping \n */
  comment = g_locale_to_utf8(rep, -1, NULL, NULL, NULL);
  if (!comment)
    comment = g_strdup("");

  if (!fgets(rep, TOOL_MAX_LINE_LENGTH, in) ||
      sscanf(rep, "%u %u %u", size, size + 1, size + 2) != 3)
    {
      /* Not a valid ASCII format. */
      g_free(comment);
      fclose(in);
      return FALSE;
    }

  if (!fgets(rep, TOOL_MAX_LINE_LENGTH, in) ||
      sscanf(rep, "%lf %lf %lf", box, box + 1, box + 2) != 3)
    {
      /* Not a valid ASCII format. */
      g_free(comment);
      fclose(in);
      return FALSE;
    }
  if (!fgets(rep, TOOL_MAX_LINE_LENGTH, in) ||
      sscanf(rep, "%lf %lf %lf", box + 3, box + 4, box + 5) != 3)
    {
      /* Not a valid ASCII format. */
      g_free(comment);
      fclose(in);
      return FALSE;
    }

  if (!fgets(rep, TOOL_MAX_LINE_LENGTH, in) ||
      sscanf(rep, "%s %s", format, period) < 1 ||
      (strcmp(format, "xyz") && strcmp(format, "zyx")))
    {
      /* Not a valid ASCII format. */
      g_free(comment);
      fclose(in);
      return FALSE;
    }
  periodic = !strcmp(period, "periodic");

  /* OK, from now on, the format is supposed to be ASCII. */
  field = g_object_new(VISU_TYPE_SCALAR_FIELD_DATA, "label", visu_scalar_field_method_data_getFilename(data), NULL);
  if (!field)
    {
      g_warning("impossible to create a VisuScalarField object.");
      g_free(comment);
      fclose(in);
      return FALSE;
    }
  visu_scalar_field_setCommentary(VISU_SCALAR_FIELD(field), comment);
  boxObj = visu_box_new(box, (periodic)?VISU_BOX_PERIODIC:VISU_BOX_FREE);
  visu_box_setMargin(boxObj, 0.f, FALSE);
  visu_boxed_setBox(VISU_BOXED(field), VISU_BOXED(boxObj));
  g_object_unref(boxObj);
  visu_scalar_field_method_data_addField(data, VISU_SCALAR_FIELD(field));
  g_object_ref(field);

  visu_scalar_field_method_data_ready(data);

  /* by default the meshtype is set to uniform to keep working previous version.*/
  meshtype = VISU_SCALAR_FIELD_MESH_UNIFORM;
  feed = fgets(rep, TOOL_MAX_LINE_LENGTH, in);
  while (feed && rep[0] == '#')
    {
      if (strncmp(rep + 2, MESH_FLAG, strlen(MESH_FLAG)) == 0)
	{
	  DBG_fprintf(stderr, "VisuScalarField: found flag '%s'.\n", MESH_FLAG);
	  res = sscanf(rep + 2 + strlen(MESH_FLAG) + 1, "%s", flag);
	  if (res == 1 && strcmp(flag, MESH_FLAG_UNIFORM) == 0)
	    meshtype = VISU_SCALAR_FIELD_MESH_UNIFORM;
	  else if (res == 1 && strcmp(flag, MESH_FLAG_NON_UNIFORM) == 0)
	    meshtype = VISU_SCALAR_FIELD_MESH_NON_UNIFORM;
	  else if (res == 1)
	    {
              g_set_error(error, TOOL_FILE_FORMAT_ERROR, TOOL_FILE_FORMAT_ERROR_FORMAT,
			  _("wrong '%s' value for flag '%s'.\n"), flag, MESH_FLAG);
              fclose(in);
              g_object_unref(field);
              return TRUE;
	    }
	}
      feed = fgets(rep, TOOL_MAX_LINE_LENGTH, in);
    }
  visu_scalar_field_setMeshtype(VISU_SCALAR_FIELD(field), meshtype);

  _setGridSize(VISU_SCALAR_FIELD(field), size);
  if (meshtype == VISU_SCALAR_FIELD_MESH_NON_UNIFORM)
    for (k = 0; k < 3; k++)
      {
        DBG_fprintf(stderr, "VisuScalarField : Start to read mesh %d.\n", k);
        mesh = g_malloc(sizeof(double) * size[k]);
        for ( i = 0; i < size[0]; i++ )
          {
            if (!feed)
              {
                g_set_error(error, TOOL_FILE_FORMAT_ERROR, TOOL_FILE_FORMAT_ERROR_FORMAT,
                            _("not enough meshx values.\n"));
                fclose(in);
                g_object_unref(field);
                return TRUE;
              }
            res = sscanf(rep, "%lf", &mesh[i]);
            do feed = fgets(rep, TOOL_MAX_LINE_LENGTH, in); while (rep[0] == '#' && feed);
            if (res != 1)
              {
                g_set_error(error, TOOL_FILE_FORMAT_ERROR, TOOL_FILE_FORMAT_ERROR_FORMAT,
                            _("impossible to read meshx values.\n"));
                fclose(in);
                g_object_unref(field);
                return TRUE;
              }
          }
        visu_scalar_field_setMesh(VISU_SCALAR_FIELD(field), mesh, k);
        g_free(mesh);
      }

  DBG_fprintf(stderr, "Visu ScalarField: Start to read data.\n");

  field->priv->min = G_MAXFLOAT;
  field->priv->max = G_MINFLOAT;
  if(!strcmp(format, "xyz"))
    {
      for ( k = 0; k < size[2]; k++ ) 
	for ( j = 0; j < size[1]; j++ ) 
	  for ( i = 0; i < size[0]; i++ ) 
	    {
              if (g_cancellable_set_error_if_cancelled(cancel, error))
                {
		  fclose(in);
                  g_object_unref(field);
		  return TRUE;
                }
	      if (!feed)
		{
		  g_set_error(error, TOOL_FILE_FORMAT_ERROR, TOOL_FILE_FORMAT_ERROR_FORMAT,
			      _("not enough density values.\n"));
		  fclose(in);
                  g_object_unref(field);
		  return TRUE;
		}

	      res = sscanf(rep, "%lf", &field->priv->data[i][j][k]);

	      do feed = fgets(rep, TOOL_MAX_LINE_LENGTH, in); while (rep[0] == '#' && feed);
	      if (res != 1)
		{
		  g_set_error(error, TOOL_FILE_FORMAT_ERROR, TOOL_FILE_FORMAT_ERROR_FORMAT,
			      _("impossible to read density values.\n"));
		  fclose(in);
                  g_object_unref(field);
		  return TRUE;
		}
	      field->priv->min = MIN(field->priv->data[i][j][k], field->priv->min);
	      field->priv->max = MAX(field->priv->data[i][j][k], field->priv->max);
	    }
    }
  else
    {
      for ( i = 0; i < size[0]; i++ ) 
	for ( j = 0; j < size[1]; j++ ) 
	  for ( k = 0; k < size[2]; k++ ) 
	    {
              if (g_cancellable_set_error_if_cancelled(cancel, error))
                {
		  fclose(in);
                  g_object_unref(field);
		  return TRUE;
                }
	      if (!feed)
		{
		  g_set_error(error, TOOL_FILE_FORMAT_ERROR, TOOL_FILE_FORMAT_ERROR_FORMAT,
			      _("not enough density values.\n"));
		  fclose(in);
                  g_object_unref(field);
		  return TRUE;
		}
	      res = sscanf(rep, "%lf", &field->priv->data[i][j][k]);
	      do feed = fgets(rep, TOOL_MAX_LINE_LENGTH, in); while (rep[0] == '#' && feed);
	      if (res != 1)
		{
		  g_set_error(error, TOOL_FILE_FORMAT_ERROR, TOOL_FILE_FORMAT_ERROR_FORMAT,
			      _("impossible to read density values.\n"));
		  fclose(in);
                  g_object_unref(field);
		  return TRUE;
		}
	      field->priv->min = MIN(field->priv->data[i][j][k], field->priv->min);
	      field->priv->max = MAX(field->priv->data[i][j][k], field->priv->max);
	    }
    }
  fclose(in);

  DBG_fprintf(stderr, "Visu ScalarField: field completed.\n");
  field->priv->empty = FALSE;
  g_idle_add(_emitEmpty, field);
  g_idle_add(_emitChanged, field);
  DBG_fprintf(stderr, "Visu ScalarField: emission done.\n");
  
  g_object_unref(field);
  return TRUE;
}

/* Local variables. */
static GList *loadMethods;

enum _dataStatus
  {
    PREPARING,
    READY,
    FAILED
  };

struct _VisuScalarFieldMethodData
{
  const gchar *filename;
  GHashTable *options;
  GList *fieldList;
  GMutex mutex;
  enum _dataStatus status;
};

static VisuScalarFieldMethodData* data_copy(VisuScalarFieldMethodData *orig)
{
  VisuScalarFieldMethodData *copy;

  copy = g_malloc(sizeof(VisuScalarFieldMethodData));
  *copy = *orig;
  return copy;
}

GType visu_scalar_field_method_data_get_type(void)
{
  static GType g_define_type_id = 0;

  if (g_define_type_id == 0)
    g_define_type_id = g_boxed_type_register_static("VisuScalarFieldMethodData", 
                                                    (GBoxedCopyFunc)data_copy,
                                                    (GBoxedFreeFunc)g_free);
  return g_define_type_id;
}

static VisuScalarFieldMethodData* visu_scalar_field_method_data_new(const gchar *filename, GHashTable *options)
{
  VisuScalarFieldMethodData *data;
  
  data = g_malloc(sizeof(VisuScalarFieldMethodData));
  data->filename = filename;
  data->options = options;
  data->fieldList = (GList*)0;
  g_mutex_init(&data->mutex);
  data->status = PREPARING;

  return data;
}

/**
 * visu_scalar_field_method_data_getFilename:
 * @data: a #VisuScalarFieldMethodData structure.
 *
 * Get the filename that is currently parsed.
 *
 * Since: 3.8
 *
 * Returns: a filename.
 **/
const gchar* visu_scalar_field_method_data_getFilename(const VisuScalarFieldMethodData *data)
{
  g_return_val_if_fail(data, (const gchar*)0);

  return data->filename;
}
/**
 * visu_scalar_field_method_data_addField:
 * @data: a #VisuScalarFieldMethodData structure.
 * @field: (transfer none): a #VisuScalarField object.
 *
 * When parsing a file with several scalar field, use this method to
 * store a new one.
 *
 * Since: 3.8
 **/
void visu_scalar_field_method_data_addField(VisuScalarFieldMethodData *data,
                                            VisuScalarField *field)
{
  g_return_if_fail(data);

  data->fieldList = g_list_append(data->fieldList, field);
}
/**
 * visu_scalar_field_method_data_ready:
 * @data: a #VisuScalarFieldMethodData structure.
 *
 * While parsing a file, call this method when all scalar fields are
 * known, but not yet populated.
 *
 * Since: 3.8
 **/
void visu_scalar_field_method_data_ready(VisuScalarFieldMethodData *data)
{
  g_return_if_fail(data);

  data->status = READY;
  g_mutex_unlock(&data->mutex);
}
/**
 * visu_scalar_field_method_data_waitReady:
 * @data: a #VisuScalarFieldMethodData structure.
 *
 * Block while reading for all scalar fields to be known, if not populated.
 *
 * Since: 3.8
 *
 * Returns: (element-type VisuScalarField) (transfer none): The list
 * of read #VisuScalarField objects.
 **/
static GList* visu_scalar_field_method_data_waitReady(VisuScalarFieldMethodData *data)
{
  /* Wait for initialisation completion. */
  DBG_fprintf(stderr, "Visu ScalarFieldMethod: waiting for thread start.\n");
  g_mutex_lock(&data->mutex);
  while (data->status == PREPARING)
    {
      g_mutex_unlock(&data->mutex);
      g_mutex_lock(&data->mutex);
    }
  g_mutex_unlock(&data->mutex);
  g_mutex_clear(&data->mutex);
  
  DBG_fprintf(stderr, "Visu ScalarFieldMethod: thread completed with status %d.\n",
              data->status);
  DBG_fprintf(stderr, "Visu ScalarFieldMethod: found %d fields.\n",
              g_list_length(data->fieldList));

  return data->fieldList;
}

G_DEFINE_TYPE(VisuScalarFieldMethod, visu_scalar_field_method, TOOL_TYPE_FILE_FORMAT)

static void visu_scalar_field_method_class_init(VisuScalarFieldMethodClass *klass _U_)
{
  DBG_fprintf(stderr, "Visu VisuScalarField: creating the class of the object.\n");

  loadMethods = (GList*)0;
}

static void visu_scalar_field_method_init(VisuScalarFieldMethod *obj)
{
  DBG_fprintf(stderr, "Visu VisuScalarField: initializing a new object (%p).\n",
	      (gpointer)obj);

  obj->load = (VisuScalarFieldMethodLoadFunc)0;
  obj->priority = G_PRIORITY_LOW;

  loadMethods = g_list_prepend(loadMethods, obj);
}

static gint compareLoadPriority(gconstpointer a, gconstpointer b)
{
  if (VISU_SCALAR_FIELD_METHOD(a)->priority <
      VISU_SCALAR_FIELD_METHOD(b)->priority)
    return (gint)-1;
  else if (VISU_SCALAR_FIELD_METHOD(a)->priority >
	   VISU_SCALAR_FIELD_METHOD(b)->priority)
    return (gint)+1;
  else
    return (gint)0;
}

/**
 * visu_scalar_field_method_new:
 * @descr: the name of the method ;
 * @patterns: (array zero-terminated=1): a NULL terminated list of strings ;
 * @method: (scope call): a #VisuScalarFieldMethodLoadFunc method ;
 * @priority: a priority value (the lower value, the higher priority).
 *
 * This routine is used to add a new method to load scalar field. The priority uses
 * the scale of the GLib (G_PRIORITY_DEFAULT is 0, G_PRIORITY_LOW is
 * 300 for instance).
 *
 * Returns: (transfer full): a newly create method to load scalar fields.
 */
VisuScalarFieldMethod* visu_scalar_field_method_new(const gchar* descr,
                                                    const gchar** patterns,
                                                    VisuScalarFieldMethodLoadFunc method,
                                                    int priority)
{
  VisuScalarFieldMethod *meth;

  g_return_val_if_fail(descr && method && patterns, (VisuScalarFieldMethod*)0);

  meth = VISU_SCALAR_FIELD_METHOD(g_object_new(VISU_TYPE_SCALAR_FIELD_METHOD,
                                               "name", descr, "ignore-type", FALSE, NULL));
  tool_file_format_addPatterns(TOOL_FILE_FORMAT(meth), patterns);
  meth->load     = method;
  meth->priority = priority;

  loadMethods = g_list_sort(loadMethods, compareLoadPriority);

  return meth;
}

static void setToolFileFormatOption(gpointer key, gpointer value, gpointer data)
{
  DBG_fprintf(stderr, "Visu ScalarFieldMethod: transfer option '%s' to file format.\n",
              (gchar*)key);
  tool_file_format_addOption(TOOL_FILE_FORMAT(data), tool_option_copy((ToolOption*)value));
}

static void _loadThread(GTask *task, VisuScalarFieldMethod *self,
                        VisuScalarFieldMethodData *data,
                        GCancellable *cancel)
{
  GError *error;
  VisuScalarFieldMethod *meth;
  gboolean validFormat;
  GList *tmpLst, single;

  g_return_if_fail(data && data->fieldList == (GList*)0);

  DBG_fprintf(stderr, "Visu ScalarFieldMethod: try all known formats (%p).\n",
              (gpointer)self);
  error = (GError*)0;

  g_mutex_lock(&data->mutex);

  /* Try all supported format. */
  single.data = self;
  single.next = (GList*)0;
  validFormat = FALSE;
  for (tmpLst = self ? &single : visu_scalar_field_method_getAll();
       tmpLst && !validFormat; tmpLst = g_list_next(tmpLst))
    {
      meth = VISU_SCALAR_FIELD_METHOD(tmpLst->data);
      /* Transfer option from table to ToolFileFormat. */
      if (data->options)
        g_hash_table_foreach(data->options, setToolFileFormatOption, tmpLst->data);
      DBG_fprintf(stderr, " | try format '%s'.\n",
                  tool_file_format_getName(TOOL_FILE_FORMAT(tmpLst->data)));
      g_clear_error(&error);
      validFormat = meth->load(meth, data, cancel, &error);
      DBG_fprintf(stderr, " | %d (%p).\n", validFormat, (gpointer)error);
    }
  if (data->status == PREPARING)
    {
      data->status = validFormat ? READY : FAILED;
      g_mutex_unlock(&data->mutex);
    }
  
  if (!validFormat)
    g_set_error(&error, TOOL_FILE_FORMAT_ERROR,
                TOOL_FILE_FORMAT_ERROR_UNKNOWN_FORMAT, 
                _("unknown density/potential format.\n"));
  
  if (error)
    g_task_return_error(task, error);
  else
    g_task_return_boolean(task, TRUE);
}

/**
 * visu_scalar_field_method_load:
 * @fmt: a #VisuScalarFieldMethod object ;
 * @filename: (type filename): a path ;
 * @options: (allow-none): some key / value options.
 * @cancel: (allow-none): a #GCancellable object.
 * @callback: (allow-none): a method to call on load finished.
 * @user_data: (scope async): some user data.
 *
 * Call the load routine of the given scalar field file format @fmt.
 *
 * Since: 3.7
 *
 * Returns: (element-type VisuScalarField) (transfer full): a list of
 * #VisuScalarField on success.
 */
GList* visu_scalar_field_method_load(VisuScalarFieldMethod *fmt,
                                     const gchar *filename,
                                     GHashTable *options,
                                     GCancellable *cancel,
                                     GAsyncReadyCallback callback,
                                     gpointer user_data)
{
  VisuScalarFieldMethodData *data;
  GList *list;
  GTask *task;

  g_return_val_if_fail(filename, (GList*)0);

  data = visu_scalar_field_method_data_new(filename, options);
  task = g_task_new(fmt, cancel, callback, user_data);
  g_task_set_task_data(task, data, g_free);
  g_task_run_in_thread(task, (GTaskThreadFunc)_loadThread);

  list = visu_scalar_field_method_data_waitReady(data);
  g_object_unref(task);

  return list;
}
/**
 * visu_scalar_field_method_loadSync:
 * @fmt: a #VisuScalarFieldMethod object ;
 * @filename: (type filename): a path ;
 * @options: (allow-none): some key / value options.
 * @cancel: (allow-none): a #GCancellable object.
 * @error: (allow-none): an error location.
 *
 * Same as visu_scalar_field_method_load(), but blocking variant.
 *
 * Since: 3.8
 *
 * Returns: (element-type VisuScalarField) (transfer full): a list of
 * #VisuScalarField on success.
 */
GList* visu_scalar_field_method_loadSync(VisuScalarFieldMethod *fmt,
                                         const gchar *filename,
                                         GHashTable *options,
                                         GCancellable *cancel,
                                         GError **error)
{
  VisuScalarFieldMethodData *data;
  GList *list;
  GTask *task;

  g_return_val_if_fail(filename, (GList*)0);

  data = visu_scalar_field_method_data_new(filename, options);
  task = g_task_new(fmt, cancel, NULL, NULL);
  g_task_set_task_data(task, data, g_free);
  g_task_run_in_thread_sync(task, (GTaskThreadFunc)_loadThread);
  g_task_propagate_boolean(task, error);

  list = visu_scalar_field_method_data_waitReady(data);
  g_object_unref(task);
  
  return list;
}

/**
 * visu_scalar_field_method_getAll:
 *
 * This routine gives access to all the registered load method for scamlar fields.
 *
 * Returns: (transfer none) (element-type VisuScalarFieldMethod*):
 * returns a list of V_Sim owned #VisuScalarFieldMethod objects.
 */
GList* visu_scalar_field_method_getAll(void)
{
  return loadMethods;
}
/**
 * visu_scalar_field_method_class_finalize: (skip)
 *
 * Free allocated memory for all known #VisuScalarFieldMethod.
 *
 * Since: 3.8 
 **/
void visu_scalar_field_method_class_finalize(void)
{
  g_list_free_full(loadMethods, (GDestroyNotify)g_object_unref);
  loadMethods = (GList*)0;
}
