/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien CALISTE, laboratoire L_Sim, (2001-2008)
  
	Adresse 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, L_Sim laboratory, (2001-2008)

	E-mail address:
	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 <visu_basic.h>
#include <visu_dataloadable.h>
#include <visu_dataatomic.h>
#include <renderingMethods/elementAtomic.h>
#include <coreTools/toolMatrix.h>
#include <coreTools/toolPhysic.h>
#include <coreTools/toolFiles.h>
#include <extraFunctions/sfielddata.h>

#define CUBE_DESCRIPTION _("<span size=\"smaller\">"			\
			   "This plug-in reads <b>Cube</b>"		\
			   " files (see \n<u><span color=\"blue\">"	\
			   "http://local.wasp.uwa.edu.au/\n"		\
			   "~pbourke/dataformats/cube/"			\
			   "</span></u>),\nboth structural and"		\
			   " and volumetric data.</span>")
#define CUBE_AUTHORS     "Caliste Damien"

/* http://local.wasp.uwa.edu.au/~pbourke/dataformats/cube/ */

struct cube_reader
{
  const gchar  *filename;
  GString      *line;
  gchar        *comment;
  double        box[3][3];
  float         shift[3];
  guint         natom;
  VisuDataLoaderIter *iter;
  VisuElement **nodeTypes;
  float        *coords;
  guint         mesh[3];
  GArray       *density;
  VisuScalarFieldData *field;
  GIOStatus     status;
  ToolFiles    *flux;
  GCancellable *cancel;
};

static gboolean cubeAtomicLoad(VisuDataLoader *self, VisuDataLoadable *data,
                               guint type, guint nSet,
                               GCancellable *cancel, GError **error);
static gboolean cubeDensityLoad(VisuScalarFieldMethod *meth,
                                VisuScalarFieldMethodData *data,
                                GCancellable *cancel, GError **error);
/* static gboolean xvAtomicLoad(VisuData *data, const gchar* filename, */
/* 			     ToolFileFormat *format, int nSet, GError **error); */

/* Local variables */
static gchar *iconPath;
static VisuDataLoader *cubeLoader = NULL;

gboolean cubeInit()
{
  const gchar *typeCube[] = {"*.cube", "*.cub", (char*)0};
/*   char *typeXV[] = {"*.XV", "*.STRUCT_OUT", (char*)0}; */
/*   char *nameXV   = "Siesta output format"; */
/*   char *descrXV  = _("Siesta geometric output format"); */

  cubeLoader = visu_data_loader_new(_("Gaussian structural/volumetric format"), typeCube,
                                    FALSE, cubeAtomicLoad, 60);
  visu_data_atomic_class_addLoader(cubeLoader);

  visu_scalar_field_method_new(_("Gaussian structural/volumetric format"), typeCube,
                               cubeDensityLoad, G_PRIORITY_HIGH - 10);

/*   fmt = tool_file_format_new(descrXV, typeXV); */

/*   meth = g_malloc(sizeof(VisuRenderingFormatLoad)); */
/*   meth->name = nameXV; */
/*   meth->fmt = fmt; */
/*   meth->priority = 61; */
/*   meth->load = xvAtomicLoad; */
/*   visu_rendering_atomic_addLoadMethod(meth); */

  iconPath = g_build_filename(V_SIM_PIXMAPS_DIR, "cube.png", NULL);

  return TRUE;
}
const char* cubeGet_description()
{
  return CUBE_DESCRIPTION;
}

const char* cubeGet_authors()
{
  return CUBE_AUTHORS;
}

const char* cubeGet_icon()
{
  return iconPath;
}


static void reader_free(struct cube_reader *rd)
{
  DBG_fprintf(stderr, "Cube: free the reader structure.\n");

  g_string_free(rd->line, TRUE);
  if (rd->comment)
    g_free(rd->comment);
  visu_data_loader_iter_unref(rd->iter);
  if (rd->nodeTypes)
    g_free(rd->nodeTypes);
  if (rd->coords)
    g_free(rd->coords);
  if (rd->density)
    g_array_unref(rd->density);
  if (rd->field)
    g_object_unref(rd->field);
  
  g_object_unref(rd->flux);
}

static gboolean read_line(struct cube_reader *rd, GError **error)
{
  rd->status = tool_files_read_line_string(rd->flux, rd->line, NULL, error);
  return (rd->status == G_IO_STATUS_NORMAL);
}

static gboolean read_comment(struct cube_reader *rd, GError **error)
{
  /* Read the two comment line. */
  if (!read_line(rd, error))
    {
      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			   _("Wrong Cube format, missing comment lines.\n"));
      return FALSE;
    }
  g_strstrip(rd->line->str);
  rd->comment = g_strdup(rd->line->str);
  if (!read_line(rd, error))
    {
      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			   _("Wrong Cube format, missing comment lines.\n"));
      return FALSE;
    }
  g_strstrip(rd->line->str);
  g_string_prepend(rd->line, " ");
  g_string_prepend(rd->line, rd->comment);
  g_free(rd->comment);
  rd->comment = g_strdup(rd->line->str);
  DBG_fprintf(stderr, "Cube: comment '%s'.\n", rd->comment);
  return TRUE;
}

static gboolean read_natom(struct cube_reader *rd, GError **error)
{
  /* Read the number of atoms. */
  if (!read_line(rd, error))
    {
      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			   _("Wrong Cube format, missing the atom line.\n"));
      return FALSE;
    }
  if (sscanf(rd->line->str, "%u %f %f %f", &rd->natom,
	     rd->shift, rd->shift + 1, rd->shift + 2) != 4)
    {
      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			   _("Wrong Cube format, missing the number of"
			     " atoms on the third line.\n"));
      return FALSE;
    }
  DBG_fprintf(stderr, "Cube: number of atoms %d.\n", rd->natom);
  return TRUE;
}

static gboolean read_box(struct cube_reader *rd, GError **error)
{
  int i;
  gchar dir[3] = "XYZ";

  /* Read the box definition. */
  for (i = 0; i < 3; i++)
    {
      if (!read_line(rd, error))
	{
	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			       _("Wrong Cube format, missing the %d box line.\n"),
			       i + 1);
	  return FALSE;
	}
      if (sscanf(rd->line->str, "%u %lf %lf %lf\n",
		 rd->mesh + i, rd->box[i], rd->box[i] + 1, rd->box[i] + 2) != 4)
	{
	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			       _("Wrong Cube format, missing float values"
				 " for box definition.\n"));
	  return FALSE;
	}
      if (rd->mesh[i] <= 0)
	{
	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			       _("Wrong Cube format, wrong mesh"
				 " size in %c direction.\n"), dir[i]);
	  return FALSE;
	}
      rd->box[i][0] *= rd->mesh[i];
      rd->box[i][1] *= rd->mesh[i];
      rd->box[i][2] *= rd->mesh[i];
    }
  DBG_fprintf(stderr, "Cube: read box %8g %8g %8g\n"
	      "               %8g %8g %8g\n"
	      "               %8g %8g %8g\n",
	      rd->box[0][0], rd->box[0][1], rd->box[0][2],
	      rd->box[1][0], rd->box[1][1], rd->box[1][2],
	      rd->box[2][0], rd->box[2][1], rd->box[2][2]);
  return TRUE;
}

static gboolean read_coords(struct cube_reader *rd, GError **error)
{
  guint i, zele, nb;
  float rcov, trash;
  gchar *name, *ptChar;
  VisuElement *type;
  gboolean new;

  rd->nodeTypes = g_malloc(sizeof(VisuElement*) * rd->natom);
  rd->coords    = g_malloc(sizeof(float) * 3 * rd->natom);

  DBG_fprintf(stderr, "Cube: read coordinates.\n");
  for  (i = 0; i < rd->natom; i++)
    {
      if (g_cancellable_set_error_if_cancelled(rd->cancel, error))
        return FALSE;
      if (!read_line(rd, error))
	{
	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			       _("Wrong Cube format, missing"
				 " line %d with coordinates.\n"),
			       i + 1);
	  return FALSE;
	}
      nb = sscanf(rd->line->str, "%u %f %f %f %f\n", &zele, &trash,
		  rd->coords + 3 * i, rd->coords + 3 * i + 1, rd->coords + 3 * i + 2);
      if (nb != 5)
	{
	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			       _("Wrong Cube format, can't read coordinates.\n"));
	  return FALSE;
	}
      /* rd->coords[3 * i + 0] -= rd->shift[0]; */
      /* rd->coords[3 * i + 1] -= rd->shift[1]; */
      /* rd->coords[3 * i + 2] -= rd->shift[2]; */
      /* Try to find a z number instead of a name. */
      rcov = -1.f;
      if (!tool_physic_getSymbolFromZ(&ptChar, &rcov, (float*)0, zele))
        {
	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			       _("Wrong Cube format, one atomic number is 0 or lower,"
                                 " or greater than 103, check your input file.\n"));
	  return FALSE;
        }
      name = g_strdup(ptChar);
      /* adding nomloc to the hashtable */
      type = visu_element_retrieveFromName(name, &new);
      if (new && rcov > 0.f)
	visu_element_atomic_setRadius(visu_element_atomic_getFromPool(type),
                                      rcov * 0.52917720859);
      g_return_val_if_fail(type, FALSE);
      rd->nodeTypes[i] = type;
      visu_data_loader_iter_addNode(rd->iter, type);
    }
  DBG_fprintf(stderr, " | read OK.\n");
  return TRUE;
}

static gboolean read_density(struct cube_reader *rd, GError **error)
{
  int i, j, n;
  gchar **tokens;
#if DEBUG == 1
  GTimer *timer;
  gulong fractionTimer;
#endif

#if DEBUG == 1
  timer = g_timer_new();
  g_timer_start(timer);
#endif

  n = rd->mesh[0] * rd->mesh[1] * rd->mesh[2];
  rd->density = g_array_sized_new(FALSE, FALSE, sizeof(double), n);
  g_array_set_size(rd->density, n);

  DBG_fprintf(stderr, "Cube: read density (%gMo).\n",
              sizeof(double) * n / 1024. / 1024.);
  i = 0;
  do
    {
      if (g_cancellable_set_error_if_cancelled(rd->cancel, error))
        return FALSE;
      if (!read_line(rd, error))
	{
	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
	        	       _("Wrong Cube format, missing density lines.\n"));
	  return FALSE;
	}
      tokens = g_strsplit(rd->line->str, " ", 0);
      for (j = 0; tokens[j] && i < n; j++)
        if (tokens[j][0])
          {
            if (sscanf(tokens[j], "%lf", &g_array_index(rd->density, double, i)) != 1)
              {
                *error = g_error_new(VISU_DATA_LOADABLE_ERROR,
                                     DATA_LOADABLE_ERROR_FORMAT,
                                     _("Wrong Cube format, unreadable float value"
                                       " %d for density.\n"), i);
                g_strfreev(tokens);
                return FALSE;
              }
            i += 1;
          }
      g_strfreev(tokens);
    }
  while(i < n && rd->status == G_IO_STATUS_NORMAL);
#if DEBUG == 1
  g_timer_stop(timer);
  fprintf(stderr, "Cube: density parsed in %g milli-s.\n", g_timer_elapsed(timer, &fractionTimer)/1e-3);
  g_timer_destroy(timer);
  fprintf(stderr, " | %d elements.\n", rd->density->len);
#endif
  if (rd->status != G_IO_STATUS_NORMAL)
    {
      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			   _("Wrong Cube format, missing float values"
			     " for density (%d read, %d awaited).\n"),
			   i, rd->mesh[0] * rd->mesh[1] * rd->mesh[2]);
      return FALSE;
    }
  return TRUE;
}

static int read_cube_file(struct cube_reader *rd,
                          GCancellable *cancel, GError **error,
			  VisuScalarFieldMethodData *data)
{
  VisuBox *boxObj;

  /* We read every line that corresponds to this schema : "%s %f %f %f" */
  DBG_fprintf(stderr, "Cube: reading file as a cube file.\n");

  /* The storage for read line. */
  rd->line      = g_string_new("");
  rd->comment   = (gchar*)0;
  rd->cancel    = cancel;

  /* Storage of number of elements per types. */
  rd->iter      = visu_data_loader_iter_new();
  rd->nodeTypes = (VisuElement**)0;

  rd->coords    = (float*)0;
  rd->density   = (GArray*)0;
  rd->field     = (VisuScalarFieldData*)0;

  /* We read the all the file. */
  if (!read_comment(rd, error))
    {
      DBG_fprintf(stderr, "Cube: no comment, not a cube file.\n");
      return -1;
    }
  if (!read_natom(rd, error))
    {
      DBG_fprintf(stderr, "Cube: no number of atoms, not a cube file.\n");
      return -1;
    }
  if (!read_box(rd, error))
    {
      DBG_fprintf(stderr, "Cube: no box, not a cube file.\n");
      return -1;
    }

  if (data)
    {
      rd->field = g_object_new(VISU_TYPE_SCALAR_FIELD_DATA, "label", rd->filename, NULL);
      g_object_ref(rd->field);
      visu_scalar_field_setCommentary(VISU_SCALAR_FIELD(rd->field), rd->comment);
      boxObj = visu_box_new_full(rd->box, VISU_BOX_PERIODIC);
      visu_box_setMargin(boxObj, 0.f, FALSE);
      visu_boxed_setBox(VISU_BOXED(rd->field), VISU_BOXED(boxObj));
      g_object_unref(boxObj);
      visu_scalar_field_method_data_addField(data, VISU_SCALAR_FIELD(rd->field));
      visu_scalar_field_method_data_ready(data);
    }

  if (!read_coords(rd, error))
    return 1;
  if (rd->field)
    {
      if (!read_density(rd, error))
	return 1;
    }
  return 0;
}

static gboolean cubeAtomicLoad(VisuDataLoader *self _U_, VisuDataLoadable *data,
                               guint type, guint nSet _U_,
                               GCancellable *cancel, GError **error)
{
  int res;
  guint iNodes;
  struct cube_reader rd;
  gboolean *flag;
  VisuBox *boxObj;
  float xyz[3];

  g_return_val_if_fail(error && *error == (GError*)0, FALSE);

  rd.filename = visu_data_loadable_getFilename(data, type);
  rd.flux = tool_files_new();
  if (!tool_files_open(rd.flux, rd.filename, error))
    {
      g_object_unref(rd.flux);
      return FALSE;
    }

  res = read_cube_file(&rd, cancel, error, NULL);

  if (res == 0)
    {
      /* Allocate the space for the nodes. */
      visu_data_loader_iter_allocate(rd.iter, VISU_NODE_ARRAY(data));
      DBG_fprintf(stderr, " | begin to transfer data to VisuData.\n");
      /* Store the coordinates */
      boxObj = visu_box_new_full(rd.box, VISU_BOX_PERIODIC);
      if (visu_box_getGeometry(boxObj, VISU_BOX_DXX) != G_MAXFLOAT)
        visu_boxed_setBox(VISU_BOXED(data), VISU_BOXED(boxObj));
      else
        {
          *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
                               _("Wrong Cube format, basis-set is degenerated.\n"));
          res = +1;
        }
      g_object_unref(boxObj);
      if (res == 0)
        {
          for(iNodes = 0; iNodes < rd.natom; iNodes++)
            {
              visu_box_convertFullToCell(boxObj, xyz, rd.coords + 3 * iNodes);
              visu_data_addNodeFromElement(VISU_DATA(data), rd.nodeTypes[iNodes],
                                           xyz, FALSE);
            }
          visu_box_setOrigin(boxObj, rd.shift);
          visu_data_setDescription(VISU_DATA(data), rd.comment);

          flag = g_malloc(sizeof(gboolean));
          *flag = TRUE;
          g_object_set_data_full(G_OBJECT(data), VISU_SCALAR_FIELD_DEFINED_IN_STRUCT_FILE,
                                 (gpointer)flag, g_free);
        }
    }

  /* Free the local data. */
  reader_free(&rd);

  if (res < 0)
    /* The file is not a Cube file. */
    return FALSE;
  else if (res > 0)
    /* The file is a Cube file but some errors occured. */
    return TRUE;
  /* Everything is OK. */
  *error = (GError*)0;
  return TRUE;
}

static gboolean cubeDensityLoad(VisuScalarFieldMethod *meth _U_,
                                VisuScalarFieldMethodData *data,
                                GCancellable *cancel, GError **error)
{
  int res;
  struct cube_reader rd;
  float shift[3];

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

  rd.filename = visu_scalar_field_method_data_getFilename(data);
  rd.flux = tool_files_new();
  if (!tool_files_open(rd.flux, rd.filename, error))
    {
      g_object_unref(rd.flux);
      return FALSE;
    }

  res = read_cube_file(&rd, cancel, error, data);

  if (res == 0)
    {
      visu_scalar_field_setGridSize(VISU_SCALAR_FIELD(rd.field), rd.mesh);
      shift[0] = - rd.shift[0];
      shift[1] = - rd.shift[1];
      shift[2] = - rd.shift[2];
      visu_pointset_setTranslation(VISU_POINTSET(rd.field), shift, TRUE);
      DBG_fprintf(stderr, "Cube: transfer density into field object.\n");
      visu_scalar_field_data_set(rd.field, rd.density, VISU_SCALAR_FIELD_DATA_ZYX);
    }

  /* Free the local data. */
  reader_free(&rd);  

  if (res < 0)
    /* The file is not a Cube file. */
    return FALSE;
  else if (res > 0)
    /* The file is a Cube file but some errors occured. */
    return TRUE;
  /* Everything is OK. */
  *error = (GError*)0;
  return TRUE;
}

/* static gboolean xvAtomicLoad(VisuData *data, const gchar* filename, */
/* 			     ToolFileFormat *format, int nSet, GError **error) */
/* { */
/*   GIOChannel *flux; */
/*   GIOStatus status; */
/*   GString *line; */
/*   int i, n; */
/*   double cart[3][3]; */
/*   float box[6]; */

/*   g_return_val_if_fail(error && *error == (GError*)0, FALSE); */
/*   g_return_val_if_fail(data && filename, FALSE); */

/*   flux = g_io_channel_new_file(filename, "r", error); */
/*   if (!flux) */
/*     return FALSE; */

/*   for (i = 0; i < 3; i++) */
/*     { */
/*       status = g_io_channel_read_line_string(flux, line, NULL, error); */
/*       if (status != G_IO_STATUS_NORMAL) */
/* 	{ */
/* 	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT, */
/* 			       _("Wrong Siesta format, missing the %d box line.\n"), */
/* 			       i + 1); */
/* 	  return FALSE; */
/* 	} */
/*       g_strstrip(line->str); */

/*       if (sscanf(line->str, "%lf %lf %lf\n", */
/* 		 cart[i], cart[i] + 1, cart[i] + 2) != 4) */
/* 	{ */
/* 	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT, */
/* 			       _("Wrong Siesta format, missing float values" */
/* 				 " for box definition.\n")); */
/* 	  return FALSE; */
/* 	} */
/*     } */
/*   DBG_fprintf(stderr, "Siesta: read box %8g %8g %8g\n" */
/* 	      "              %8g %8g %8g\n" */
/* 	      "              %8g %8g %8g\n", */
/* 	      cart[0][0], cart[0][1], cart[0][2], */
/* 	      cart[1][0], cart[1][1], cart[1][2], */
/* 	      cart[2][0], cart[2][1], cart[2][2]); */
/*   status = g_io_channel_read_line_string(flux, line, NULL, error); */
/*   if (status != G_IO_STATUS_NORMAL) */
/*     { */
/*       *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT, */
/* 			   _("Wrong Siesta format, missing the number" */
/* 			     " of elements line.\n")); */
/*       return FALSE; */
/*     } */
/*   g_strstrip(line->str); */
/*   if (sscanf(line->str, "%d\n", &n)) */
/*     { */
/*       *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT, */
/* 			   _("Wrong Siesta format, missing the number" */
/* 			     " of elements.\n")); */
/*       return FALSE; */
/*     } */


/*   if (!tool_matrix_reducePrimitiveVectors(box, cart)) */
/*     { */
/*       *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT, */
/* 			   _("Wrong Siesta format, primitive vectors are not 3D.\n")); */
/*       return FALSE; */
/*     } */
  


/*   if (g_io_channel_shutdown(flux, FALSE, (GError**)0) != G_IO_STATUS_NORMAL) */
/*     g_warning("Cube: can't close file."); */
/* } */
