/*   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_dataatomic.h"

#include "visu_basic.h"

#include "loaders/atomic_ascii.h"
#include "loaders/atomic_d3.h"
#include "loaders/atomic_xyz.h"
#include "loaders/atomic_yaml.h"

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

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

struct _VisuDataAtomicPrivate
{
  gchar *file;
  VisuDataLoader *format;
};

static GList *_atomicFormats = NULL;
static void _initAtomicFormats(void)
{
  visu_data_atomic_class_addLoader(visu_data_loader_ascii_getStatic());
  visu_data_atomic_class_addLoader(visu_data_loader_d3_getStatic());
  visu_data_atomic_class_addLoader(visu_data_loader_xyz_getStatic());
  if (visu_data_loader_yaml_getStatic())
    visu_data_atomic_class_addLoader(visu_data_loader_yaml_getStatic());
}

static void visu_data_atomic_finalize    (GObject* obj);
static void visu_data_atomic_get_property(GObject* obj, guint property_id,
                                          GValue *value, GParamSpec *pspec);
static void visu_data_atomic_set_property(GObject* obj, guint property_id,
                                          const GValue *value, GParamSpec *pspec);
static gboolean visu_data_atomic_load(VisuDataLoadable *self, guint iSet,
                                      GCancellable *cancel, GError **error);
static const gchar* visu_data_atomic_getFilename(const VisuDataLoadable *self,
                                                 guint fileType);

G_DEFINE_TYPE_WITH_CODE(VisuDataAtomic, visu_data_atomic, VISU_TYPE_DATA_LOADABLE,
                        G_ADD_PRIVATE(VisuDataAtomic))

static void visu_data_atomic_class_init(VisuDataAtomicClass *klass)
{
  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->finalize     = visu_data_atomic_finalize;
  G_OBJECT_CLASS(klass)->get_property = visu_data_atomic_get_property;
  G_OBJECT_CLASS(klass)->set_property = visu_data_atomic_set_property;
  VISU_DATA_LOADABLE_CLASS(klass)->load = visu_data_atomic_load;
  VISU_DATA_LOADABLE_CLASS(klass)->getFilename = visu_data_atomic_getFilename;

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

  /**
   * VisuDataAtomic::atomic-filename:
   *
   * The path to the source filename.
   *
   * Since: 3.8
   */
  _properties[PROP_FILE] =
    g_param_spec_string("atomic-filename", "Atomic filename", "source filename",
                        (gchar*)0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
  /**
   * VisuDataAtomic::atomic-format:
   *
   * The format of the source filename, if known.
   *
   * Since: 3.8
   */
  _properties[PROP_FORMAT] =
    g_param_spec_object("atomic-format", "Atomic 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);

  _initAtomicFormats();
}
static void visu_data_atomic_init(VisuDataAtomic *obj)
{
  obj->priv = visu_data_atomic_get_instance_private(obj);

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

  data = VISU_DATA_ATOMIC(obj);

  g_free(data->priv->file);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_data_atomic_parent_class)->finalize(obj);
}
static void visu_data_atomic_get_property(GObject* obj, guint property_id,
                                          GValue *value, GParamSpec *pspec)
{
  VisuDataAtomic *self = VISU_DATA_ATOMIC(obj);

  switch (property_id)
    {
    case PROP_LABEL:
      g_value_take_string(value, g_path_get_basename(self->priv->file));
      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_atomic_set_property(GObject* obj, guint property_id,
                                          const GValue *value, GParamSpec *pspec)
{
  VisuDataAtomic *self = VISU_DATA_ATOMIC(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_atomic_new:
 * @file: a filename.
 * @format: (allow-none): a #VisuDataLoader format, if known.
 *
 * Creates a #VisuDataAtomic object and set @file to it.
 *
 * Since: 3.8
 *
 * Returns: a newly allocated #VisuDataAtomic object.
 **/
VisuDataAtomic* visu_data_atomic_new(const gchar *file, VisuDataLoader *format)
{
  return g_object_new(VISU_TYPE_DATA_ATOMIC, "n-files", 1,
                      "atomic-filename", file, "atomic-format", format, NULL);
}

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

  g_return_if_fail(VISU_IS_DATA_LOADER(loader));
  DBG_fprintf(stderr, "Data Atomic: adding a new loader '%s'.\n",
              tool_file_format_getName(TOOL_FILE_FORMAT(loader)));
  tool_file_format_setPropertiesFromCLI(TOOL_FILE_FORMAT(loader));
  _atomicFormats = g_list_prepend(_atomicFormats, loader);
  _atomicFormats = g_list_sort(_atomicFormats,
                               (GCompareFunc)visu_data_loader_comparePriority);
}

/**
 * visu_data_atomic_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_atomic_class_getLoaders(void)
{
  return _atomicFormats;
}
/**
 * visu_data_atomic_class_getFileDescription:
 *
 * Returns a translated string describing what is files loaded by
 * #VisuDataAtomic objects.
 *
 * Since: 3.8
 *
 * Returns: a string owned by V_Sim.
 **/
const gchar* visu_data_atomic_class_getFileDescription(void)
{
  return _("Position files");
}
/**
 * visu_data_atomic_class_finalize:
 *
 * Empty the list of known loaders.
 *
 * Since: 3.8
 **/
void visu_data_atomic_class_finalize(void)
{
  g_list_free_full(_atomicFormats, (GDestroyNotify)g_object_unref);
  _atomicFormats = (GList*)0;
}

/**
 * visu_data_atomic_getFile:
 * @data: a #VisuDataAtomic object.
 * @format: (out caller-allocates): a location to store a
 * #VisuDataLoader object.
 *
 * Returns the file defined in @data and its associated @format, if any.
 *
 * Since: 3.8
 *
 * Returns: a string owned by V_Sim.
 **/
const gchar* visu_data_atomic_getFile(VisuDataAtomic *data, VisuDataLoader **format)
{
  g_return_val_if_fail(VISU_IS_DATA_ATOMIC(data), (const gchar*)0);

  if (format)
    *format = data->priv->format;
  return data->priv->file;
}
static const gchar* visu_data_atomic_getFilename(const VisuDataLoadable *self,
                                                 guint fileType)
{
  g_return_val_if_fail(fileType == 0, (const gchar*)0);

  return visu_data_atomic_getFile(VISU_DATA_ATOMIC(self), (VisuDataLoader**)0);
}

static gboolean _finalize(VisuDataAtomic *data, VisuDataLoader *loader)
{
  VisuNodeArrayIter iter;
  ToolUnits unit, preferedUnit;
  VisuBox *box;

  /* We do a last check on population... in case. */
  visu_node_array_iter_new(VISU_NODE_ARRAY(data), &iter);
  g_return_val_if_fail(iter.nAllStoredNodes, FALSE);
  DBG_fprintf(stderr, "Data Atomic: loading OK (%d nodes).\n", iter.nAllStoredNodes);

  /* Setup additional infos. */
  box = visu_boxed_getBox(VISU_BOXED(data));
  unit = visu_box_getUnit(box);
  preferedUnit = visu_basic_getPreferedUnit();
  if (preferedUnit != TOOL_UNITS_UNDEFINED &&
      unit != TOOL_UNITS_UNDEFINED &&
      unit != preferedUnit)
    visu_box_setUnit(box, preferedUnit);

  visu_node_array_completeAdding(VISU_NODE_ARRAY(data));

  data->priv->format = loader;

  return TRUE;
}

static gboolean _abort(VisuDataAtomic *data)
{
  visu_node_array_completeAdding(VISU_NODE_ARRAY(data));

  return FALSE;
}

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

  g_return_val_if_fail(VISU_IS_DATA_ATOMIC(self), FALSE);

  data = VISU_DATA_ATOMIC(self);

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

  visu_node_array_startAdding(VISU_NODE_ARRAY(self));
  for (lst = _atomicFormats; 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 Atomic: testing '%s' with format: %s.\n",
		      data->priv->file, tool_file_format_getName(TOOL_FILE_FORMAT(loader)));
	  if (visu_data_loader_load(loader, self, 0, iSet, cancel, error))
            return (*error) ? _abort(data) : _finalize(data, loader);
          if (*error && (*error)->domain == G_FILE_ERROR)
            return _abort(data);
          if (*error && data->priv->format)
            return _abort(data);
        }
    }
  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 _abort(data);
}

/**
 * visu_data_atomic_loadAt:
 * @data: a #VisuDataAtomic object.
 * @filename: a filename.
 * @iSet: an id.
 * @cancel: a #GCancellable object.
 * @error: an error location.
 *
 * Specific routine to load an atomic file that is not stored in the
 * location declared at construction. This is only used for archive plug-in.
 *
 * Since: 3.8
 *
 * Returns: TRUE on success.
 **/
gboolean visu_data_atomic_loadAt(VisuDataAtomic *data, const gchar *filename, guint iSet, GCancellable *cancel, GError **error)
{
  gchar *old;
  gboolean res;

  g_return_val_if_fail(VISU_IS_DATA_ATOMIC(data), FALSE);
  old = data->priv->file;
  data->priv->file = (gchar*)filename;
  res = visu_data_atomic_load(VISU_DATA_LOADABLE(data), iSet, cancel, error);
  data->priv->file = old;
  return res;
}

/**
 * visu_data_atomic_getForces:
 * @dataObj: a #VisuDataAtomic object.
 * @create: a boolean.
 *
 * Retrieves the #VisuNodeValuesVector used to store forces for
 * @dataObj, create it depending on @create if not exists.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a #VisuNodeValuesVector object, owned by V_Sim.
 **/
VisuNodeValuesVector* visu_data_atomic_getForces(VisuDataAtomic *dataObj,
                                                 gboolean create)
{
  VisuNodeValuesVector *vect;

  if (!dataObj)
    return (VisuNodeValuesVector*)0;

  vect = (VisuNodeValuesVector*)visu_data_getNodeProperties(VISU_DATA(dataObj),
                                                            _("Forces"));
  if (!vect && create)
    {
      vect = visu_node_values_vector_new(VISU_NODE_ARRAY(dataObj),
                                         _("Forces"));
      visu_node_values_setEditable(VISU_NODE_VALUES(vect), FALSE);
      visu_data_addNodeProperties(VISU_DATA(dataObj), VISU_NODE_VALUES(vect));
    }
  return vect;
}
