/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)
  
	Adresse mèl :
	BILLARD, non joignable par 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 : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)

	E-mail address:
	BILLARD, not reachable any more ;
	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 <stdlib.h>

#include "atomic_d3.h"
#include <visu_dataloadable.h>
#include <coreTools/toolFortran.h>
#include <coreTools/toolFiles.h>

/**
 * SECTION:atomic_d3
 * @short_description: Method to load d3 position file.
 *
 * <para>D3 format is a binary format to store elements and their
 * position.</para>
 */

static gboolean read_nattyp_natnom(ToolFiles *flux, GError **error,
				   ToolFortranEndianId endian, gboolean store);
static int free_read_d3();
static int read_d3_file(VisuDataLoadable *data, ToolFiles *flux, int set, GError **error);

static guint nat, ntypeD3;
static GArray *nattyp;
static char **natnom;
static GArray *x, *y, *z;
static GList *lst;
static VisuDataLoader *d3Loader = NULL;

static gboolean loadD3(VisuDataLoader *loader, VisuDataLoadable *data,
                       guint type, guint nSet,
                       GCancellable *cancel, GError **error);

/******************************************************************************/

/**
 * visu_data_loader_d3_getStatic:
 *
 * Retrieve the instance of the atomic loader used to parse d3 files.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a #VisuDataLoader owned by V_Sim.
 **/
VisuDataLoader* visu_data_loader_d3_getStatic(void)
{
  const gchar *typeD3[] = {"*.d3", "*-posi.d3", "*.d3-posi", (char*)0};

  if (d3Loader)
    return d3Loader;

  return d3Loader = visu_data_loader_new(_("Native binary format"), typeD3,
                                         FALSE, loadD3, 10);
}

/******************************************************************************/

static gboolean read_nattyp_natnom(ToolFiles *flux, GError **error,
				   ToolFortranEndianId endian, gboolean store)
{
  guint k;
  gboolean valid;
  gsize ncheck;
  gchar *name;

  g_return_val_if_fail((store && !nattyp && natnom) || !store, FALSE);

  DBG_fprintf(stderr, "Atomic D3: read nattyp and nattom (%d).\n", store);
  if (!tool_files_fortran_readFlag(flux, &ncheck, endian, error))
    return FALSE;
  valid = tool_files_fortran_readIntegerArray(flux, store ? &nattyp : NULL, ntypeD3,
                                              endian, FALSE, error);
  if (!valid) return valid;
  for(k = 0; k < ntypeD3; k++)
    {
      name = (store)?natnom[k]:(gchar*)0;
      valid = tool_files_fortran_readCharacter(flux, name, 8, error,
                                               endian, FALSE, store);
      if (!valid) return valid;
      if (store)
	{
	  natnom[k][8] = '\0';
	  g_strchomp(natnom[k]);
	}
    }
  return tool_files_fortran_checkFlag(flux, ncheck, endian, error);
}
static gboolean read_npas_temps(ToolFiles *flux, GError **error, ToolFortranEndianId endian,
                                guint *npas)
{
  if (!tool_files_fortran_readInteger(flux, (gint*)npas, endian, error))
    return FALSE;
  if (!tool_files_fortran_readDouble(flux, NULL, endian, error))
    return FALSE;
  return TRUE;
}
static gboolean read_npas_temps_energy(ToolFiles *flux, GError **error, ToolFortranEndianId endian,
                                       guint *npas, gdouble *energy)
{
  if (!tool_files_fortran_readInteger(flux, (gint*)npas, endian, error))
    return FALSE;
  if (!tool_files_fortran_readDouble(flux, NULL, endian, error))
    return FALSE;
  if (!tool_files_fortran_readDouble(flux, energy, endian, error))
    return FALSE;
  return TRUE;
}
static gboolean read_unit(ToolFiles *flux, GError **error, ToolUnits *unit)
{
  gchar units[17];

  DBG_fprintf(stderr, "Atomic D3 : read units.\n");
  if (tool_files_read(flux, units, 16, error) != G_IO_STATUS_NORMAL)
    return FALSE;
  units[16] = '\0';
  g_strchomp(units);
  *unit = tool_physic_getUnitFromName(units);
  DBG_fprintf(stderr, " | '%s' (%d)\n", units, *unit);
  
  return TRUE;
}
static gboolean read_periodicity(ToolFiles *flux, GError **error,
                                 VisuBoxBoundaries *bc)
{
  gchar periodicity[5];

  DBG_fprintf(stderr, "Atomic D3 : read periodicity.\n");
  if (tool_files_read(flux, periodicity, 4, error) != G_IO_STATUS_NORMAL)
    return FALSE;
  periodicity[4] = '\0';
  g_strchomp(periodicity);
  DBG_fprintf(stderr, " | '%s'\n", periodicity);
  if (!g_ascii_strcasecmp(periodicity, "Oxyz"))
    *bc = VISU_BOX_PERIODIC;
  else if (!g_ascii_strcasecmp(periodicity, "Ozx"))
    *bc = VISU_BOX_SURFACE_ZX;
  else if (!g_ascii_strcasecmp(periodicity, "Oxy"))
    *bc = VISU_BOX_SURFACE_XY;
  else if (!g_ascii_strcasecmp(periodicity, "Oyz"))
    *bc = VISU_BOX_SURFACE_YZ;
  else if (!g_ascii_strcasecmp(periodicity, "Ox"))
    *bc = VISU_BOX_WIRE_X;
  else if (!g_ascii_strcasecmp(periodicity, "Oy"))
    *bc = VISU_BOX_WIRE_Y;
  else if (!g_ascii_strcasecmp(periodicity, "Oz"))
    *bc = VISU_BOX_WIRE_Z;
  else if (!g_ascii_strcasecmp(periodicity, "O"))
    *bc = VISU_BOX_FREE;
  else
    *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
                         _("unknwon peridicity flag '%s'"), periodicity);
  return (*error == (GError*)0);
}

/******************************************************************************/

static gboolean loadD3(VisuDataLoader *loader _U_, VisuDataLoadable *data,
                       guint type, guint nSet,
                       GCancellable *cancel _U_, GError **error)
{
  int res;
  ToolFiles *readFrom;

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

  readFrom = tool_files_new();
  if (!tool_files_fortran_open(readFrom, visu_data_loadable_getFilename(data, type), error))
    return FALSE;

  res = read_d3_file(data, readFrom, nSet, error);

  g_object_unref(G_OBJECT(readFrom));

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

static int read_d3_file(VisuDataLoadable *data, ToolFiles *flux, int set, GError **error)
{
  int iSet;
  char title[129];
  guint snat;
  VisuElement *type;
  GArray *types;
  gchar *infoUTF8;
  gboolean valid, store, readDouble, storeDouble;
  ToolFortranEndianId endian;
  guint i, j;
  gsize ncheck;
  GArray *boxGeometry;
  float xyz[3];
  GList *tmpLst;
  VisuBox *box;
  ToolUnits unit;
  VisuBoxBoundaries bc;
  gdouble energy;
  guint npas;

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

  natnom = (gchar**)0;
  nattyp = (GArray*)0;
  x = (GArray*)0;
  y = (GArray*)0;
  z = (GArray*)0;
  iSet = 0;
  storeDouble = TRUE;
  unit = TOOL_UNITS_UNDEFINED;
  bc = VISU_BOX_PERIODIC;

  /* Check the first element to find the endianness.
     The first element is an int equal to 128. */
  DBG_fprintf(stderr, "Atomic D3 : test for endianness.\n");
  if (!tool_files_fortran_testEndianness(flux, 128, &endian))
    return -1;

  /* We read the set number @set and store the number of sets in
     nSet. */
  lst = (GList*)0;
  do
    {
      store = (iSet == set);

      /* TITLE */
      DBG_fprintf(stderr, "Atomic D3 : read title.\n");
      if (tool_files_read(flux, title, 128, error) != G_IO_STATUS_NORMAL)
	return free_read_d3();
      if (!tool_files_fortran_checkFlag(flux, 128, endian, error))
	return free_read_d3();
      title[128] = '\0';
      g_strchomp(title);
      DBG_fprintf(stderr, " | '%s'\n", title);

      /* NPAS-TEMPS(-ENERGY) (optional) */
      energy = G_MAXDOUBLE;
      npas = G_MAXUINT;
      valid = tool_files_fortran_readFlag(flux, &ncheck, endian, error);
      if (!valid)
	return free_read_d3();
      if (ncheck == 12)
        valid = read_npas_temps(flux, error, endian, &npas);
      else if (ncheck == 16)
        valid = read_npas_temps_energy(flux, error, endian, &npas, &energy);
      else
        {
          valid = (ncheck == 8);
          if (!valid)
            g_set_error_literal(error, VISU_DATA_LOADABLE_ERROR,
                                DATA_LOADABLE_ERROR_FORMAT,
                                _("wrong d3 syntax, awaiting Nat and Ntypes."));
        }
      if (!valid)
        return free_read_d3();
      if (ncheck != 8 && !tool_files_fortran_checkFlag(flux, ncheck, endian, error))
        return free_read_d3();
      
      readDouble = (npas == G_MAXUINT);
      if (store && energy != G_MAXDOUBLE)
        g_object_set(G_OBJECT(data), "totalEnergy", energy, NULL);
      if (npas != G_MAXUINT)
        lst = g_list_prepend(lst, g_strdup_printf("%04d - %s", npas, title));
      else
        lst = g_list_prepend(lst, g_strdup(title));

      /* NAT-NTYPE */  
      DBG_fprintf(stderr, "Atomic D3 : read nat and ntype.\n");
      valid = tool_files_fortran_readInteger(flux, (gint*)&nat, endian, error);
      if (!valid)
	return free_read_d3();
      valid = tool_files_fortran_readInteger(flux, (gint*)&ntypeD3, endian, error);
      if (!valid)
	return free_read_d3();
      if (!tool_files_fortran_checkFlag(flux, 8, endian, error))
        return free_read_d3();
      DBG_fprintf(stderr, " | %d %d\n", nat, ntypeD3);
               
      /* NATI-NOM */
      if (store)
	{
          DBG_fprintf(stderr, " | store nattyp and natom.\n");
	  natnom = g_malloc(ntypeD3 * sizeof(char *));
	  for(i=0; i<ntypeD3; i++)
	    natnom[i] = g_malloc(9 * sizeof(char));
	}
      valid = read_nattyp_natnom(flux, error, endian, store);
      if(!valid)
	{
	  DBG_fprintf(stderr, "! invalid nattyp or natnom.\n");
	  return free_read_d3();
	}
      else
        DBG_fprintf(stderr, " | read nattyp natom OK.\n");
      if (store)
	{
	  DBG_fprintf(stderr, " | check coherence in number of atoms.\n");
	  snat = 0;
	  for(i=0; i<ntypeD3; i++)
	    snat = snat + g_array_index(nattyp, guint, i);
	  if(snat != nat)
	    {
	      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
				   _("wrong d3 syntax,"
				     " sum of nattyp (%d) is not equal to nat (%d)\n"),
                                   snat, nat);
	      return free_read_d3();
	    }
	}
         
      /* DXX-DYX-DYY-DZX-DZY-DZZ */
      valid = tool_files_fortran_readDoubleArray(flux, &boxGeometry, 6,
                                                 endian, TRUE, error);
      if (!valid)
	{
	  DBG_fprintf(stderr, "! invalid box '%s'.\n", (*error)->message);
	  return free_read_d3();
	}

      /* X */
      if (readDouble)
	valid = tool_files_fortran_readDoubleArray(flux, store ? &x : NULL,
                                                   nat, endian, TRUE, error);
      else
	valid = tool_files_fortran_readRealArray(flux, store ? &x : NULL,
                                                 nat, endian, TRUE, error);
      if (!valid)
	{
	  DBG_fprintf(stderr, "! invalid x coordinates '%s' (%d).\n", (*error)->message, readDouble);
	  return free_read_d3();
	}
   
      /* Y */      
      if (readDouble)
	valid = tool_files_fortran_readDoubleArray(flux, store ? &y : NULL,
                                                   nat, endian, TRUE, error);
      else
	valid = tool_files_fortran_readRealArray(flux, store ? &y : NULL,
                                                 nat, endian, TRUE, error);
      if (!valid)
	{
	  DBG_fprintf(stderr, "! invalid y coordinates '%s'.\n", (*error)->message);
	  return free_read_d3();
	}
   
      /* Z */      
      if (readDouble)
	valid = tool_files_fortran_readDoubleArray(flux, store ? &z : NULL,
                                                   nat, endian, TRUE, error);
      else
	valid = tool_files_fortran_readRealArray(flux, store ? &z : NULL,
                                                 nat, endian, TRUE, error);
      if (!valid)
	{
	  DBG_fprintf(stderr, "! invalid z coordinates '%s'.\n", (*error)->message);
	  return free_read_d3();
	}

      iSet += 1;

      /* Go to the next fortran flag holding 128. */
      do
        {
          /* There is a strange thing with Fortran file, they
             seems to finish with '0x0a' character... */
          valid = tool_files_fortran_readFlag(flux, &ncheck, endian, error);
          if (!valid)
            g_clear_error(error);
          else if (ncheck == 16)
            {
              /* Special case of units. */
              if (!read_unit(flux, error, &unit) ||
                  !tool_files_fortran_checkFlag(flux, ncheck, endian, error))
                return free_read_d3();
            }
          else if (ncheck == 4)
            {
              /* Special case of periodicity. */
              if (!read_periodicity(flux, error, &bc) ||
                  !tool_files_fortran_checkFlag(flux, ncheck, endian, error))
                return free_read_d3();
            }
          else if (ncheck != 128)
            {
              if (tool_files_skip(flux, sizeof(gchar) * ncheck, error) != G_IO_STATUS_NORMAL ||
                  !tool_files_fortran_checkFlag(flux, ncheck, endian, error))
                return free_read_d3();
            }
          valid = valid && (ncheck == 128);
        }
      while (!tool_files_atEnd(flux) && !valid);
    }
  while (!tool_files_atEnd(flux) && valid);
  lst = g_list_reverse(lst);

  if(iSet < set)
    {
      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			   _("input file has no dataset number %d"
			     " (%d have been read).\n"), set + 1, iSet);
      return free_read_d3();
    }

  DBG_fprintf(stderr, "Atomic d3 : nat = %d | ntypeD3 = %d\n", nat, ntypeD3);
  for(i = 0; i < ntypeD3; i++)
    DBG_fprintf(stderr, " | natnom %d = '%s', nb %d\n", i, natnom[i],
                g_array_index(nattyp, guint, i));
  DBG_fprintf(stderr, " | box = %g %g %g\n",
              g_array_index(boxGeometry, gdouble, 0),
              g_array_index(boxGeometry, gdouble, 1),
              g_array_index(boxGeometry, gdouble, 2));
  DBG_fprintf(stderr, " |       %g %g %g\n",
              g_array_index(boxGeometry, gdouble, 3),
              g_array_index(boxGeometry, gdouble, 4),
              g_array_index(boxGeometry, gdouble, 5));
  /* Now we have all the element and the number of atoms per element.
     So we allocate the memory. */
  types = g_array_sized_new(FALSE, FALSE, sizeof(VisuElement*), ntypeD3);
  for(i = 0; i < ntypeD3; i++)
    {
      /* adding nomloc to the hashtable */
      type = visu_element_retrieveFromName(natnom[i], (gboolean*)0);
      if (!type)
	{
          g_warning("Cannot create a new type for '%s'\n", natnom[i]);
          return 1;
	}
      g_array_insert_val(types, i, type);
    }

  DBG_fprintf(stderr, " | begin to transfer data to VisuData.\n");
  /* Begin the storage into VisuData. */
  visu_data_loadable_setNSets(data, iSet);

  /* Set the commentaries. */
  for (iSet = 0, tmpLst = lst; tmpLst; iSet++, tmpLst = g_list_next(tmpLst))
    {
      infoUTF8 = g_locale_to_utf8((gchar*)tmpLst->data, -1, NULL, NULL, NULL);
      if (infoUTF8)
	{
	  visu_data_loadable_setSetLabel(data, infoUTF8, iSet);
	  g_free(infoUTF8);
	}
      else
	g_warning("Can't convert '%s' to UTF8.\n", (gchar*)tmpLst->data);
    }

  visu_node_array_allocate(VISU_NODE_ARRAY(data), types, nattyp);
  g_array_free(types, TRUE);

  nat = 0;
  for (i = 0; i < ntypeD3; i++)
    {
      for (j = 0; j < g_array_index(nattyp, guint, i); j++)
	{
	  xyz[0] = storeDouble ? g_array_index(x,  double, nat) : g_array_index(x,  float, nat);
	  xyz[1] = storeDouble ? g_array_index(y,  double, nat) : g_array_index(y,  float, nat);
	  xyz[2] = storeDouble ? g_array_index(z,  double, nat) : g_array_index(z,  float, nat);
	  visu_data_addNodeFromIndex(VISU_DATA(data), i, xyz, FALSE);
	  nat++;
	}
    }

  /* We finish with the geometry. */
  box = visu_box_new((double*)boxGeometry->data, bc);
  g_array_unref(boxGeometry);
  visu_box_setUnit(box, unit);
  visu_boxed_setBox(VISU_BOXED(data), VISU_BOXED(box));
  g_object_unref(box);
  
  free_read_d3();
   
  return 0;
}

static int free_read_d3()
{
  guint i;
  
  DBG_fprintf(stderr, "Atomic d3: freeing tmp variables.\n");
  if (natnom)
    {
      for(i = 0; i < ntypeD3; i++)
	if (natnom[i])
	  g_free(natnom[i]);
      g_free(natnom);
    }
  if(nattyp)
    g_array_free(nattyp, TRUE);
  if (x)
    g_array_unref(x);
  if (y)
    g_array_unref(y);
  if (z)
    g_array_unref(z);
  if (lst)
    g_list_free_full(lst, g_free);
  return 1;
}

/******************************************************************************/ 
/******************************************************************************/
