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

	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_dataspin.h"

#include "coreTools/toolFortran.h"

/**
 * SECTION: visu_dataspin
 * @short_description: a class of nodes representing spin data and
 * providing associated loading methods.
 *
 * <para>This class provides #VisuDataLoader for spin data representation.</para>
 */

#define SPIN_PROP_ID _("Spin (\316\270, \317\206, mod.)")
#define SPIN_MAX_MOD_ID "spin_max_modulus_id"

enum
  {
    PROP_0,
    PROP_FILE,
    PROP_FORMAT,
    N_PROP,
    PROP_LABEL,
    N_PROP_TOT
  };
static GParamSpec *_properties[N_PROP];

struct _VisuDataSpinPrivate
{
  gchar *file;
  VisuDataLoader *format;

  /* These are cached pointers to the values. */
  GArray *maxModulus;
  VisuNodeValuesVector *spin;
};

static GList *_spinFormats = NULL;


static void visu_data_spin_finalize    (GObject* obj);
static void visu_data_spin_get_property(GObject* obj, guint property_id,
                                        GValue *value, GParamSpec *pspec);
static void visu_data_spin_set_property(GObject* obj, guint property_id,
                                        const GValue *value, GParamSpec *pspec);
static const gchar* visu_data_spin_getFilename(const VisuDataLoadable *self,
                                               guint fileType);
static gboolean visu_data_spin_load(VisuDataLoadable *self, guint iSet,
                                    GCancellable *cancel, GError **error);

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

static void _initSpinFormats(void)
{
  const gchar *typeSpin[] = {"*.spin", "*.sp", NULL};
  const gchar *typeBinary[] = {"*.bspin", "*.bsp", NULL};

  visu_data_spin_class_addLoader(visu_data_loader_new
                                 (_("Ascii spin files"), typeSpin, FALSE,
                                  read_spin_file, 100));
  visu_data_spin_class_addLoader(visu_data_loader_new
                                 (_("Binary spin files"), typeBinary, FALSE,
                                  read_binary_file, 10));
}

/* Local methods. */

G_DEFINE_TYPE_WITH_CODE(VisuDataSpin, visu_data_spin, VISU_TYPE_DATA_ATOMIC,
                        G_ADD_PRIVATE(VisuDataSpin))

static void visu_data_spin_class_init(VisuDataSpinClass *klass)
{
  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->finalize     = visu_data_spin_finalize;
  G_OBJECT_CLASS(klass)->get_property = visu_data_spin_get_property;
  G_OBJECT_CLASS(klass)->set_property = visu_data_spin_set_property;
  VISU_DATA_LOADABLE_CLASS(klass)->load = visu_data_spin_load;
  VISU_DATA_LOADABLE_CLASS(klass)->getFilename = visu_data_spin_getFilename;

  g_object_class_override_property(G_OBJECT_CLASS(klass), PROP_LABEL, "label");

  /**
   * VisuDataAtomic::spin-filename:
   *
   * The path to the source filename.
   *
   * Since: 3.8
   */
  _properties[PROP_FILE] =
    g_param_spec_string("spin-filename", "Spin filename", "source filename",
                        (gchar*)0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
  /**
   * VisuDataAtomic::spin-format:
   *
   * The format of the source filename, if known.
   *
   * Since: 3.8
   */
  _properties[PROP_FORMAT] =
    g_param_spec_object("spin-format", "Spin format", "source format",
                        VISU_TYPE_DATA_LOADER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROP, _properties);

  _initSpinFormats();
}
static void visu_data_spin_init(VisuDataSpin *obj)
{
  obj->priv = visu_data_spin_get_instance_private(obj);

  /* Private data. */
  obj->priv->file    = (gchar*)0;
  obj->priv->format  = (VisuDataLoader*)0;
}
static void visu_data_spin_finalize(GObject* obj)
{
  VisuDataSpin *data;

  data = VISU_DATA_SPIN(obj);

  g_free(data->priv->file);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_data_spin_parent_class)->finalize(obj);
}
static void visu_data_spin_get_property(GObject* obj, guint property_id,
                                          GValue *value, GParamSpec *pspec)
{
  gchar *at, *sp;
  VisuDataSpin *self = VISU_DATA_SPIN(obj);

  switch (property_id)
    {
    case PROP_LABEL:
      at = g_path_get_basename(visu_data_atomic_getFile(VISU_DATA_ATOMIC(self),
                                                        (VisuDataLoader**)0));
      sp = g_path_get_basename(self->priv->file);
      g_value_take_string(value, g_strdup_printf("%s \302\240\342\200\224 %s", at, sp));
      g_free(at);
      g_free(sp);
      break;
    case PROP_FILE:
      g_value_set_string(value, self->priv->file);
      break;
    case PROP_FORMAT:
      g_value_set_object(value, self->priv->format);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_data_spin_set_property(GObject* obj, guint property_id,
                                        const GValue *value, GParamSpec *pspec)
{
  VisuDataSpin *self = VISU_DATA_SPIN(obj);

  switch (property_id)
    {
    case PROP_FILE:
      self->priv->file = tool_path_normalize(g_value_get_string(value));
      break;
    case PROP_FORMAT:
      self->priv->format = g_value_get_object(value);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_data_spin_new:
 * @atomic: a filename.
 * @spin: a filename.
 * @atomicFormat: (allow-none): a #VisuDataLoader format, if known.
 * @spinFormat: (allow-none): a #VisuDataLoader format, if known.
 *
 * Creates a #VisuDataSpin object and set @atomic as its atomic file
 * source and @file for the spin source.
 *
 * Since: 3.8
 *
 * Returns: a newly allocated #VisuDataSpin object.
 **/
VisuDataSpin* visu_data_spin_new(const gchar *atomic, const gchar *spin,
                                 VisuDataLoader *atomicFormat,
                                 VisuDataLoader *spinFormat)
{
  return g_object_new(VISU_TYPE_DATA_SPIN, "n-files", 2,
                      "atomic-filename", atomic, "spin-filename", spin,
                      "atomic-format", atomicFormat, "spin-format", spinFormat, NULL);
}

/**
 * visu_data_spin_class_addLoader:
 * @loader: (transfer full): a #VisuDataSpin object.
 *
 * Add @loader to the list of #VisuDataLoader to be used when
 * visu_data_loadable_load() is called.
 *
 * Since: 3.8
 **/
void visu_data_spin_class_addLoader(VisuDataLoader *loader)
{
  if (g_list_find(_spinFormats, loader))
    return;

  _spinFormats = g_list_prepend(_spinFormats, loader);
  _spinFormats = g_list_sort(_spinFormats,
                               (GCompareFunc)visu_data_loader_comparePriority);
}

/**
 * visu_data_spin_class_getLoaders:
 *
 * Returns a list of available #VisuDataLoader.
 *
 * Since: 3.8
 *
 * Returns: (transfer none) (element-type VisuDataLoader): a list of
 * #VisuDataLoader owned by V_Sim.
 **/
GList* visu_data_spin_class_getLoaders(void)
{
  return _spinFormats;
}
/**
 * visu_data_spin_class_getFileDescription:
 *
 * Returns a translated string describing what is files loaded by
 * #VisuDataSpin objects.
 *
 * Since: 3.8
 *
 * Returns: a string owned by V_Sim.
 **/
const gchar* visu_data_spin_class_getFileDescription(void)
{
  return _("Spin files");
}
/**
 * visu_data_spin_class_finalize:
 *
 * Empty the list of known loaders.
 *
 * Since: 3.8
 **/
void visu_data_spin_class_finalize(void)
{
  g_list_free_full(_spinFormats, (GDestroyNotify)g_object_unref);
  _spinFormats = (GList*)0;
}

static const gchar* visu_data_spin_getFilename(const VisuDataLoadable *self,
                                               guint fileType)
{
  g_return_val_if_fail(VISU_IS_DATA_SPIN(self) && fileType < 2, (const gchar*)0);

  if (fileType == 1)
    return visu_data_spin_getFile(VISU_DATA_SPIN(self), (VisuDataLoader**)0);
  else
    return visu_data_atomic_getFile(VISU_DATA_ATOMIC(self), (VisuDataLoader**)0);
}
/**
 * visu_data_spin_getFile:
 * @data: a #VisuDataSpin object.
 * @format: (out caller-allocates): a location to store the format.
 *
 * Retrieve the spin filename. Optionally provides also the format of
 * this file. If the file has been parsed this is the detected
 * format. If not, this is the supposed format, as proposed by user.
 *
 * Since: 3.8
 *
 * Returns: a filename.
 **/
const gchar* visu_data_spin_getFile(VisuDataSpin *data, VisuDataLoader **format)
{
  g_return_val_if_fail(VISU_IS_DATA_SPIN(data), (const gchar*)0);

  if (format)
    *format = data->priv->format;
  return data->priv->file;
}

static gboolean visu_data_spin_load(VisuDataLoadable *self, guint iSet,
                                    GCancellable *cancel, GError **error)
{
  VisuDataSpin *data;
  GList *lst;
  VisuDataLoader *loader;

  g_return_val_if_fail(VISU_IS_DATA_SPIN(self), FALSE);

  if (!VISU_DATA_LOADABLE_CLASS(visu_data_spin_parent_class)->load(self, iSet,
                                                                   cancel, error))
    return FALSE;

  data = VISU_DATA_SPIN(self);

  if (!visu_data_loadable_checkFile(self, 1, error))
    return FALSE;

  for (lst = _spinFormats; lst; lst = g_list_next(lst))
    {
      loader = VISU_DATA_LOADER(lst->data);

      /* Each load may set error even if the format is not recognise
	 and loadOK is FALSE, then we need to free the error. */
      g_clear_error(error);

      if (!data->priv->format || loader == data->priv->format)
	{
	  DBG_fprintf(stderr,"Data Spin: testing '%s' with format: %s.\n",
		      data->priv->file, tool_file_format_getName(TOOL_FILE_FORMAT(loader)));
	  if (visu_data_loader_load(loader, self, 1, iSet, cancel, error))
            return TRUE;
          if (*error && (*error)->domain == G_FILE_ERROR)
            return FALSE;
        }
    }
  g_clear_error(error);
  g_set_error(error, VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_UNKNOWN,
              _("Impossible to load '%s', unrecognised format.\n"), data->priv->file);
  return FALSE;
}

static void initMaxModulus(VisuElement *ele _U_, GValue *val)
{
  DBG_fprintf(stderr, " | init max modulus of val %p.\n", (gpointer)val);
  g_value_init(val, G_TYPE_FLOAT);
  g_value_set_float(val, -G_MAXFLOAT);
}

/**
 * visu_data_spin_get:
 * @dataObj: a #VisuDataSpin object.
 *
 * Retrieve the #VisuNodeValuesVector object stroing the spin
 * components per #VisuNode.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the #VisuNodeValuesVector storing the spi
 * components of @dataObj.
 **/
const VisuNodeValuesVector* visu_data_spin_get(const VisuDataSpin *dataObj)
{
  return VISU_NODE_VALUES_VECTOR(visu_data_getNodeProperties(VISU_DATA(dataObj),
                                                             SPIN_PROP_ID));
}

/**
 * visu_data_spin_setAt:
 * @dataObj: a #VisuDataSpin object.
 * @node: a #VisuNode object.
 * @vals: (array fixed-size=3): a vector in cartesian coordinates.
 *
 * Store @vals as the spin representation for @node in @dataObj.
 *
 * Since: 3.8
 **/
void visu_data_spin_setAt(VisuDataSpin *dataObj,
                          const VisuNode *node, const gfloat vals[3])
{
  GValue *val;
  const gfloat* sph;

  g_return_if_fail(VISU_IS_DATA_SPIN(dataObj));

  if (!dataObj->priv->spin)
    {
      dataObj->priv->spin = visu_node_values_vector_new(VISU_NODE_ARRAY(dataObj),
                                                        SPIN_PROP_ID);
      visu_data_addNodeProperties(VISU_DATA(dataObj),
                                  VISU_NODE_VALUES(dataObj->priv->spin));
    }
  visu_node_values_vector_setAt(dataObj->priv->spin, node, vals);
  sph = visu_node_values_vector_getAtSpherical(dataObj->priv->spin, node);
  if (!dataObj->priv->maxModulus)
    dataObj->priv->maxModulus = visu_node_array_setElementProperty
      (VISU_NODE_ARRAY(dataObj), SPIN_MAX_MOD_ID, initMaxModulus);
  val = &g_array_index(dataObj->priv->maxModulus, GValue, node->posElement);
  g_value_set_float(val, MAX(sph[TOOL_MATRIX_SPHERICAL_MODULUS],
                             g_value_get_float(val)));
}
/**
 * visu_data_spin_setAtSpherical:
 * @dataObj: a #VisuDataSpin object.
 * @node: a #VisuNode object.
 * @vals: (array fixed-size=3): a vector in spherical coordinates.
 *
 * Store @vals as the spin representation for @node in @dataObj.
 *
 * Since: 3.8
 **/
void visu_data_spin_setAtSpherical(VisuDataSpin *dataObj, const VisuNode *node,
                                   const gfloat vals[3])
{
  GValue *val;

  g_return_if_fail(VISU_IS_DATA_SPIN(dataObj));

  if (!dataObj->priv->spin)
    {
      dataObj->priv->spin = visu_node_values_vector_new(VISU_NODE_ARRAY(dataObj),
                                                        SPIN_PROP_ID);
      visu_data_addNodeProperties(VISU_DATA(dataObj),
                                  VISU_NODE_VALUES(dataObj->priv->spin));
    }
  visu_node_values_vector_setAtSpherical(dataObj->priv->spin, node, vals);
  if (!dataObj->priv->maxModulus)
    dataObj->priv->maxModulus = visu_node_array_setElementProperty
      (VISU_NODE_ARRAY(dataObj), SPIN_MAX_MOD_ID, initMaxModulus);
  val = &g_array_index(dataObj->priv->maxModulus, GValue, node->posElement);
  g_value_set_float(val, MAX(vals[TOOL_MATRIX_SPHERICAL_MODULUS],
                             g_value_get_float(val)));
}

/**
 * visu_data_spin_getMaxModulus:
 * @dataObj: a #VisuDataSpin object.
 * @iElement: an integer.
 *
 * Inquires the max spin modulous from @dataObj for the given
 * #VisuElement represented by @iElement.
 *
 * Since: 3.8
 *
 * Returns: a positive float.
 **/
gfloat visu_data_spin_getMaxModulus(const VisuDataSpin *dataObj, guint iElement)
{
  GValue *val;
  
  g_return_val_if_fail(VISU_IS_DATA_SPIN(dataObj), 1.f);

  g_return_val_if_fail(dataObj->priv->maxModulus &&
                       iElement < dataObj->priv->maxModulus->len, 1.f);

  val = &g_array_index(dataObj->priv->maxModulus, GValue, iElement);
  return g_value_get_float(val);
}

/*****************************************************************************/
/*                                                                           */
/*****************************************************************************/
/* The following are the methods responsible of dealing with the reading of files. */
/*****************************************************************************/
/*                                                                           */
/*****************************************************************************/

/* This is the method associated to the reading of supported spin files. */
static gboolean read_spin_file(VisuDataLoader *loader _U_, VisuDataLoadable *data,
                               guint type, guint nSet _U_,
                               GCancellable *cancel _U_, GError **error)
{
  char line[TOOL_MAX_LINE_LENGTH] = "\0";
  float vals[3];
  int itrash, iLine;
  VisuNodeArrayIter iter;
  FILE *readFrom;
  gboolean readContinue;

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

  readFrom = fopen(visu_data_loadable_getFilename(data, type), "r");
  if (!readFrom)
    {
      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FILE,
			   _("impossible to open this spin file.\n"));
      return FALSE;
    }
  iLine = 1;

  /* The first line is a commentry. */
  if(!fgets(line, TOOL_MAX_LINE_LENGTH, readFrom) || feof(readFrom))
    {
      *error = g_error_new(VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
			   _("spin file should have one line at least.\n"));
      fclose(readFrom);
      return FALSE;
    }
  iLine += 1;
   
  readContinue = TRUE;
  visu_node_array_iter_new(VISU_NODE_ARRAY(data), &iter);
  for(visu_node_array_iterStartNumber(VISU_NODE_ARRAY(data), &iter); iter.node;
      visu_node_array_iterNextNodeNumber(VISU_NODE_ARRAY(data), &iter))
    {
      if (readContinue)
	{
	  if(!fgets(line, TOOL_MAX_LINE_LENGTH, readFrom) || feof(readFrom))
	    readContinue = FALSE;
	  else
	    {
	      if(sscanf(line, "%d %f %f %f", &itrash, vals + TOOL_MATRIX_SPHERICAL_MODULUS,
			vals + TOOL_MATRIX_SPHERICAL_THETA, vals + TOOL_MATRIX_SPHERICAL_PHI) != 4)
		{
		  g_warning("line number #%d is invalid."
			    " Setting node parameters to default ones...", iLine);
		  vals[TOOL_MATRIX_SPHERICAL_THETA]   = 0.f;
		  vals[TOOL_MATRIX_SPHERICAL_PHI]     = 0.f;
		  vals[TOOL_MATRIX_SPHERICAL_MODULUS] = 0.f;
		}
	    }
	  iLine += 1;
	}
      else
	{
	  vals[TOOL_MATRIX_SPHERICAL_THETA]   = 0.f;
	  vals[TOOL_MATRIX_SPHERICAL_PHI]     = 0.f;
	  vals[TOOL_MATRIX_SPHERICAL_MODULUS] = 0.f;
	}
      visu_data_spin_setAtSpherical(VISU_DATA_SPIN(data), iter.node, vals);
    }
  fclose(readFrom);

  return TRUE;
}

/* This is the method associated to the reading of binary spin files. */
static gboolean read_binary_file(VisuDataLoader *loader _U_, VisuDataLoadable *data,
                                 guint type, guint nSet _U_,
                                 GCancellable *cancel _U_, GError **error)
{
  ToolFiles *readFrom;
  gboolean valid;
  ToolFortranEndianId endian;
  guint nspins;
  GArray *mods, *thetas, *phis;
  int i;
  float vals[3];
  VisuNodeArrayIter iter;

  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;

  /* Try to find the endianness. */
  if (!tool_files_fortran_testEndianness(readFrom, 4, &endian))
    {
      g_object_unref(readFrom);
      return FALSE;
    }

  /* Try to the number of spins. */
  if (!tool_files_fortran_readInteger(readFrom, (gint*)&nspins, endian, error))
    {
      g_object_unref(readFrom);
      return FALSE;
    }
  if (!tool_files_fortran_checkFlag(readFrom, 4, endian, error))
    {
      g_object_unref(readFrom);
      return FALSE;
    }

  /* From now on, we consider to a have valid spin file. */
  visu_node_array_iter_new(VISU_NODE_ARRAY(data), &iter);
  if (nspins != iter.nAllStoredNodes)
    {
      g_set_error(error, VISU_DATA_LOADABLE_ERROR, DATA_LOADABLE_ERROR_FORMAT,
                  _("number of spin differs from number of nodes.\n"));
      g_object_unref(readFrom);
      return TRUE;
    }
  
  /* Read module. */
  valid = tool_files_fortran_readDoubleArray(readFrom, &mods,
                                             nspins, endian, TRUE, error);
  if (!valid)
    {
      g_object_unref(readFrom);
      return TRUE;
    }
  /* Read theta. */
  valid = tool_files_fortran_readDoubleArray(readFrom, &thetas,
                                             nspins, endian, TRUE, error);
  if (!valid)
    {
      g_array_unref(mods);
      g_object_unref(readFrom);
      return TRUE;
    }
  /* Read phi. */
  valid = tool_files_fortran_readDoubleArray(readFrom, &phis,
                                             nspins, endian, TRUE, error);
  if (!valid)
    {
      g_array_unref(mods);
      g_array_unref(thetas);
      g_object_unref(readFrom);
      return TRUE;
    }
  /* Close the file. */
  g_object_unref(readFrom);

  for(visu_node_array_iterStart(VISU_NODE_ARRAY(data), &iter), i = 0; iter.node;
      visu_node_array_iterNext(VISU_NODE_ARRAY(data), &iter), i++)
    {
      vals[TOOL_MATRIX_SPHERICAL_MODULUS] = g_array_index(mods, double, i);
      vals[TOOL_MATRIX_SPHERICAL_THETA]   = g_array_index(thetas, double, i);
      vals[TOOL_MATRIX_SPHERICAL_PHI]     = g_array_index(phis, double, i);
      visu_data_spin_setAtSpherical(VISU_DATA_SPIN(data), iter.node, vals);
    }

  g_array_unref(mods);
  g_array_unref(thetas);
  g_array_unref(phis);
  return TRUE;
}
