/*   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 "xsf.h"
#include "xsf_density.h"

#include <string.h>

#include <visu_tools.h>
#include <visu_basic.h>
#include <visu_dataloadable.h>
#include <visu_dataspin.h>
#include <coreTools/toolMatrix.h>
#include <coreTools/toolPhysic.h>


#define XSF_DESCRIPTION _("<span size=\"smaller\">" \
			  "This plug-in reads <b>XSF</b>"	\
			  " input files\n (position, forces and densities).</span>")
#define XSF_AUTHORS     "Caliste Damien"

/* Local methods */
static gboolean loadXsfIn(VisuDataLoader *self, VisuDataLoadable *data,
                          guint type, guint nSet,
                          GCancellable *cancel, GError **error);
static gboolean loadXsfSpin(VisuDataLoader *self, VisuDataLoadable *data,
                            guint type, guint nSet,
                            GCancellable *cancel, GError **error);
static int read_xsf_file(VisuDataLoadable *data, guint type,
                         int nSet, GError **error);
static void xsfStructuralInit();
static void xsfSpinInit();
/* Local variables */
static gchar *iconPath;
static VisuDataLoader *xsfLoader = NULL;
static VisuDataLoader *spinLoader = NULL;

/* Required methods for a loadable module. */
gboolean xsfInit()
{
  DBG_fprintf(stderr, "XSF: loading plug-in 'xsf'...\n");

  DBG_fprintf(stderr, "XSF: declare a new rendering load method.\n");
  xsfStructuralInit();

  DBG_fprintf(stderr, "XSF: declare a new spin load method.\n");
  xsfSpinInit();

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

  DBG_fprintf(stderr, "XSF: declare a new density load method.\n");
  xsfDensityInit();

  return TRUE;
}

const char* xsfGet_description()
{
  return XSF_DESCRIPTION;
}

const char* xsfGet_authors()
{
  return XSF_AUTHORS;
}

const char* xsfGet_icon()
{
  return iconPath;
}

static void xsfStructuralInit()
{
  const gchar *type[] = {"*.xsf", "*.axsf", (char*)0};
  
  xsfLoader = visu_data_loader_new(_("XCrysDen Structure File format"), type,
                                   FALSE, loadXsfIn, 70);
  visu_data_atomic_class_addLoader(xsfLoader);
}

static gboolean loadXsfIn(VisuDataLoader *self _U_, VisuDataLoadable *data,
                          guint type, guint nSet,
                          GCancellable *cancel _U_, GError **error)
{
  int res;

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

  res = read_xsf_file(data, type, nSet, error);

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

void xsf_reader_new(struct xsf_reader *rd)
{
  /* The storage for read line. */
  rd->flux      = tool_files_new();
  rd->line      = g_string_new("");

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

  rd->coords    = (float*)0;
  rd->forces    = (GArray*)0;
  /* rd->lst       = (GList*)0; */
}

void xsf_reader_free(struct xsf_reader *rd)
{
  /* GList *tmpLst; */

  visu_data_loader_iter_unref(rd->iter);
  if (rd->nodeTypes)
    g_free(rd->nodeTypes);
  if (rd->coords)
    g_free(rd->coords);
  if (rd->forces)
    g_array_free(rd->forces, TRUE);
  
  /* if (rd->lst) */
  /*   { */
  /*     tmpLst = rd->lst; */
  /*     while (tmpLst) */
  /*       { */
  /*         g_free(tmpLst->data); */
  /*         tmpLst = g_list_next(tmpLst); */
  /*       } */
  /*     g_list_free(rd->lst); */
  /*   }   */

  g_string_free(rd->line, TRUE);
  if (rd->flux)
    g_object_unref(rd->flux);
}

gboolean xsf_reader_skip_comment(struct xsf_reader *rd, GError **error)
{
  /* Eat blank or commentary lines. */
  do
    {
      rd->status = tool_files_read_line_string(rd->flux, rd->line, NULL, error);
      if (rd->status != G_IO_STATUS_NORMAL && rd->status != G_IO_STATUS_EOF)
	return FALSE;
      g_strstrip(rd->line->str);
    }
  while (rd->status != G_IO_STATUS_EOF &&
	 (rd->line->str[0] == '#' ||
	  rd->line->str[0] == '!' ||
	  rd->line->str[0] == '\0'));
  return TRUE;
}

gboolean xsf_reader_get_flag(struct xsf_reader *rd, gboolean *found, const gchar *flag,
                             int *value, gboolean  mandatory, GError **error)
{
  size_t len;

  *found = FALSE;
  len = strlen(flag);
  g_strstrip(rd->line->str);
  if (!strncmp(rd->line->str, flag, len))
    {
      if (mandatory && sscanf(rd->line->str + len, "%d", value) != 1 && *value <= 0)
	{
	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			       _("Wrong XSF format, '%s' flag has a"
				 " wrong value.\n"), flag);
	  return FALSE;
	}
      else if (!mandatory && sscanf(rd->line->str + len, "%d", value) == 1)
	{
	  if (*value <= 0)
	    {
	      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
				   _("Wrong XSF format, '%s' flag has a"
				     " wrong value.\n"), flag);
	      return FALSE;
	    }
	}
      *found = TRUE;
    }
  if (*found)
    return xsf_reader_skip_comment(rd, error);
  else
    return TRUE;
}

static gboolean read_periodicity(struct xsf_reader *rd, GError **error)
{
  gboolean found;

  if (!xsf_reader_get_flag(rd, &found, "MOLECULE", (int*)0, FALSE, error))
    return FALSE;
  if (found)
    rd->bc = VISU_BOX_FREE;

  if (!xsf_reader_get_flag(rd, &found, "SLAB", (int*)0, FALSE, error))
    return FALSE;
  if (found)
    rd->bc = VISU_BOX_SURFACE_XY;

  if (!xsf_reader_get_flag(rd, &found, "CRYSTAL", (int*)0, FALSE, error))
    return FALSE;
  if (found)
    rd->bc = VISU_BOX_PERIODIC;

  return TRUE;
}

gboolean xsf_reader_get_box(struct xsf_reader *rd, double cart[3][3], GError **error)
{
  int i;
  
  for (i = 0; i < 3; i++)
    {
      if (sscanf(rd->line->str, "%lf %lf %lf\n",
		 cart[i], cart[i] + 1, cart[i] + 2) != 3)
	{
	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			       _("Wrong XSF format, missing float values"
				 " after tag '%s'.\n"), "PRIMVEC");
	  return FALSE;
	}
      if (!xsf_reader_skip_comment(rd, error))
	return FALSE;
    }
  DBG_fprintf(stderr, "XSF: 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]);
  return TRUE;
}

static gboolean read_coords(struct xsf_reader *rd, GError **error)
{
  int i, zele, nb;
  float rcov, f[3];
  gchar *name, *ptChar;
  VisuElement *type;

  if (sscanf(rd->line->str, "%d", &rd->nNodes) != 1 && rd->nNodes <= 0)
    {
      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			   _("Wrong XSF format, missing or wrong integer values"
			     " after tag '%s'.\n"), "PRIMCOORD");
      return FALSE;
    }
  if (!xsf_reader_skip_comment(rd, error))
    return FALSE;
  rd->nodeTypes = g_malloc(sizeof(VisuElement*) * rd->nNodes);
  rd->coords    = g_malloc(sizeof(float) * 3 * rd->nNodes);
  DBG_fprintf(stderr, "XSF: read coordinates.\n");
  for  (i = 0; i < rd->nNodes; i++)
    {
      name = g_strdup(rd->line->str);
      g_strstrip(name);
      ptChar = strchr(name, ' ');
      if (!ptChar)
	{
	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			       _("Wrong XSF format, can't read coordinates at line '%s'.\n"),
                               name);
	  return FALSE;
	}
      *ptChar = '\0';
      /* The first three values are coordinates.
	 Possible three others are forces. */
      nb = sscanf(ptChar + 1, "%f %f %f %f %f %f\n", rd->coords + 3 * i,
		  rd->coords + 3 * i + 1, rd->coords + 3 * i + 2, f + 0, f + 1, f + 2);
      if (nb != 3 && nb != 6)
	{
	  *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			       _("Wrong XSF format, can't read coordinates from '%s' (only %d read).\n"),
                               ptChar + 1, nb);
	  return FALSE;
	}
      if (nb == 6)
	{
	  /* If forces where not used, we allocate then now. */
	  if (!rd->forces)
            rd->forces = g_array_sized_new(FALSE, FALSE, sizeof(float), rd->nNodes * 3);
          g_array_insert_vals(rd->forces, 3 * i, f, 3);
	}
      /* Try to find a z number instead of a name. */
      rcov = -1.f;
      if (sscanf(name, "%d", &zele) == 1 &&
	  tool_physic_getSymbolFromZ(&ptChar, &rcov, (float*)0, zele))
	{
	  g_free(name);
	  name = g_strdup(ptChar);
	}
      /* adding nomloc to the hashtable */
      type = visu_element_retrieveFromName(name, (gboolean*)0);
      rd->nodeTypes[i] = type;
      visu_data_loader_iter_addNode(rd->iter, type);
      if (!xsf_reader_skip_comment(rd, error))
	return FALSE;
    }
  DBG_fprintf(stderr, " | read OK.\n");
  return TRUE;
}


static int read_xsf_file(VisuDataLoadable *data, guint type, int nSet, GError **error)
{
  struct xsf_reader rd;
  gboolean found;
  int valInt;
  double box[3][3];
  int nSets, iNodes;
  VisuBox *boxObj;
  float xyz[3];

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

  xsf_reader_new(&rd);

  if (!tool_files_open(rd.flux, visu_data_loadable_getFilename(data, type), error))
    {
      xsf_reader_free(&rd);
      return -1;
    }
  
  /* We read the file completely to find the number of sets of points
     and we store only the one corresponding to @nSet. */
  nSets     = -1;
  if (!xsf_reader_skip_comment(&rd, error))
    {
      xsf_reader_free(&rd);
      return -1;
    }
  do
    {
      /* Test the periodicity mode. */
      if (!read_periodicity(&rd, error))
	{
	  xsf_reader_free(&rd);
	  return 1;
	}

      /* If ANIMSTEPS is found, we are in animated mode. */
      if (!xsf_reader_get_flag(&rd, &found, "ANIMSTEPS", &valInt, TRUE, error))
	{
	  xsf_reader_free(&rd);
	  return 1;
	}
      if (found)
	{
	  DBG_fprintf(stderr, "XSF: found the 'ANIMSTEPS' flag (%d).\n", valInt);
	  if (nSets > 0)
	    {
	      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
				   _("Wrong XSF format, '%s' tag already"
				     " defined.\n"), "ANIMSTEPS");
	      xsf_reader_free(&rd);
	      return 1;
	    }
	  else
	    nSets = valInt;
	}
/*       fprintf(stderr, "'%s'\n", rd.line->str); */

      /* If PRIMVEC is found, we store the box. */
      valInt = -1;
      if (!xsf_reader_get_flag(&rd, &found, "PRIMVEC", &valInt, FALSE, error))
	{
	  xsf_reader_free(&rd);
	  return 1;
	}
      if (found && (valInt < 0 || (valInt - 1) == nSet))
	{
	  if (rd.bc == VISU_BOX_FREE)
	    {
	      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
				   _("Wrong XSF format, primitive vectors found"
				     " with free boundary conditions.\n"));
	      xsf_reader_free(&rd);
	      return 1;
	    }
	  DBG_fprintf(stderr, "XSF: found the 'PRIMVEC' flag (%d).\n", valInt);
	  /* We read the box. */
	  if (!xsf_reader_get_box(&rd, box, error))
	    {
	      xsf_reader_free(&rd);
	      return 1;
	    }
	  /* We set nSets to 1 if not already set. */
	  if (nSets < 0)
	    nSets = 1;
	}
/*       fprintf(stderr, "'%s'\n", rd.line->str); */

      /* If PRIMCOORD is found, we store the coordinates. */
      valInt = -1;
      if (!xsf_reader_get_flag(&rd, &found, "PRIMCOORD", &valInt, FALSE, error))
	{
	  xsf_reader_free(&rd);
	  return 1;
	}
      if (found && (valInt < 0 || (valInt - 1) == nSet))
	{
	  if (rd.bc == VISU_BOX_FREE)
	    {
	      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
				   _("Wrong XSF format, primitive coordinates found"
				     " with free boundary conditions.\n"));
	      xsf_reader_free(&rd);
	      return 1;
	    }
	  DBG_fprintf(stderr, "XSF: found the 'PRIMCOORD' flag (%d).\n", valInt);
	  /* We read the coords. */
	  if (!read_coords(&rd, error))
	    {
	      xsf_reader_free(&rd);
	      return 1;
	    }
	  /* We set nSets to 1 if not already set. */
	  if (nSets < 0)
	    nSets = 1;
	}

      /* If ATOMS is found, we store the coordinates. */
      valInt = -1;
      if (!xsf_reader_get_flag(&rd, &found, "ATOMS", &valInt, FALSE, error))
	{
	  xsf_reader_free(&rd);
	  return 1;
	}
      if (found && (valInt < 0 || (valInt - 1) == nSet))
	{
	  if (rd.bc != VISU_BOX_FREE)
	    {
	      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
				   _("Wrong XSF format, atom coordinates found"
				     " with free boundary conditions.\n"));
	      xsf_reader_free(&rd);
	      return 1;
	    }
	  DBG_fprintf(stderr, "XSF: found the 'ATOMS' flag (%d).\n", valInt);
	  /* We read the coords. */
	  if (!read_coords(&rd, error))
	    {
	      xsf_reader_free(&rd);
	      return 1;
	    }
	  /* We set nSets to 1 if not already set. */
	  if (nSets < 0)
	    nSets = 1;
	}

      /* We go on. */
      if (!xsf_reader_skip_comment(&rd, error))
	{
	  xsf_reader_free(&rd);
	  return 1;
	}
/*       fprintf(stderr, "'%s'\n", rd.line->str); */
    }
  while(rd.status != G_IO_STATUS_EOF && rd.coords == (float*)0);
  
  /* Begin the storage into VisuData. */
  if (!visu_data_loader_iter_allocate(rd.iter, VISU_NODE_ARRAY(data)))
    {
      xsf_reader_free(&rd);
      return -1;
    }

  /* Allocate the space for the nodes. */
  visu_data_loadable_setNSets(data, nSets);

  boxObj = visu_box_new_full(box, rd.bc);
  if (visu_box_getGeometry(boxObj, VISU_BOX_DXX) == G_MAXFLOAT)
    {
      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
                           _("Wrong XSF format, basis-set is degenerated.\n"));
      g_object_unref(boxObj);
      xsf_reader_free(&rd);
      return 1;
    }
  visu_box_setUnit(boxObj, TOOL_UNITS_ANGSTROEM);
  visu_boxed_setBox(VISU_BOXED(data), VISU_BOXED(boxObj));
  g_object_unref(boxObj);
  for(iNodes = 0; iNodes < rd.nNodes; iNodes++)
    {
      visu_box_convertFullToCell(boxObj, xyz, rd.coords + 3 * iNodes);
      visu_data_addNodeFromElement(VISU_DATA(data), rd.nodeTypes[iNodes],
                                   xyz, FALSE);
    }

  /* We store the forces as a property to be used later by the spin
     loading method. */
  if (rd.forces)
    visu_node_values_vector_set
      (visu_data_atomic_getForces(VISU_DATA_ATOMIC(data), TRUE), rd.forces);

  /* Free the local data. */
  xsf_reader_free(&rd);
  
  return 0;
}

static void xsfSpinInit()
{
  const gchar *type[] = {"*.xsf", "*.axsf", (char*)0};

  spinLoader = visu_data_loader_new(_("XCrysDen Structure File format"), type,
                                    FALSE, loadXsfSpin, 40);
  visu_data_spin_class_addLoader(spinLoader);
}

static gboolean loadXsfSpin(VisuDataLoader *self _U_, VisuDataLoadable *data,
                            guint type _U_, guint nSet _U_,
                            GCancellable *cancel _U_, GError **error)
{
  VisuNodeValuesVector *forces;
  VisuNodeValuesIter iter;
  gboolean valid;
  float vals[3], *fxyz;

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

  forces = visu_data_atomic_getForces(VISU_DATA_ATOMIC(data), FALSE);
  if (!forces)
    return FALSE;

  /* We check that spin and position are the same. */
  /* TODO... */

  for (valid = visu_node_values_iter_new(&iter, ITER_NODES_BY_TYPE, VISU_NODE_VALUES(forces));
       valid; valid = visu_node_values_iter_next(&iter))
    {
      fxyz = (float*)g_value_get_pointer(&iter.value);
      visu_box_convertFullToCell(visu_boxed_getBox(VISU_BOXED(data)), vals, fxyz);
      visu_data_spin_setAt(VISU_DATA_SPIN(data), iter.iter.node, vals);
    }

  /* Everything is OK. */
  *error = (GError*)0;
  return TRUE;
}
