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

/**
 * SECTION: visu_dataloader
 * @short_description: a base class for all load methods.
 *
 * <para>This is a base class to define load methods for atomic or
 * spin data.</para>
 */


struct _VisuDataLoaderPrivate
{
  VisuDataLoaderFunc load;
  GDestroyNotify notify;
  gpointer data;
  guint priority;

  gchar *status;
};

enum
  {
    PROP_0,
    PROP_STATUS,
    N_PROP
  };
static GParamSpec *_properties[N_PROP];

static void visu_data_loader_finalize    (GObject* obj);
static void visu_data_loader_get_property(GObject* obj, guint property_id,
                                          GValue *value, GParamSpec *pspec);

G_DEFINE_TYPE_WITH_CODE(VisuDataLoader, visu_data_loader, TOOL_TYPE_FILE_FORMAT,
                        G_ADD_PRIVATE(VisuDataLoader))

static void visu_data_loader_class_init(VisuDataLoaderClass *klass)
{
  G_OBJECT_CLASS(klass)->finalize     = visu_data_loader_finalize;
  G_OBJECT_CLASS(klass)->get_property = visu_data_loader_get_property;

  /**
   * VisuDataLoader::status:
   *
   * A string describing the current status of the load process.
   *
   * Since: 3.8
   */
  _properties[PROP_STATUS] =
    g_param_spec_string("status", "Status", "loading status", "",
                       G_PARAM_READABLE);
}
static void visu_data_loader_init(VisuDataLoader *obj)
{
  obj->priv = visu_data_loader_get_instance_private(obj);
  obj->priv->status = (gchar*)0;
  obj->priv->load = (VisuDataLoaderFunc)0;
  obj->priv->notify = (GDestroyNotify)0;
  obj->priv->data = (gpointer)0;
}
static void visu_data_loader_get_property(GObject* obj, guint property_id,
                                          GValue *value, GParamSpec *pspec)
{
  VisuDataLoader *self = VISU_DATA_LOADER(obj);

  switch (property_id)
    {
    case PROP_STATUS:
      g_value_set_string(value, self->priv->status);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_data_loader_finalize(GObject* obj)
{
  VisuDataLoader *data;

  data = VISU_DATA_LOADER(obj);

  g_free(data->priv->status);
  if (data->priv->notify)
    data->priv->notify(data->priv->data);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_data_loader_parent_class)->finalize(obj);
}

/**
 * visu_data_loader_new: (skip)
 * @descr: a string describing the object, translated and in UTF8.
 * @patterns: (array zero-terminated=1): the patterns used to identify files of this type.
 * @restricted: a boolean.
 * @func: a loading method.
 * @priority: a integer.
 *
 * Creates a new #VisuDataLoader with the given description.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a newly created #VisuDataLoader object.
 **/
VisuDataLoader* visu_data_loader_new(const gchar* descr,
                                     const gchar** patterns, gboolean restricted,
                                     VisuDataLoaderFunc func, guint priority)
{
  return visu_data_loader_new_full(descr, patterns, restricted, func, priority,
                                   (GDestroyNotify)0, (gpointer)0);
}
/**
 * visu_data_loader_new_full: (rename-to visu_data_loader_new)
 * @descr: a string describing the object, translated and in UTF8.
 * @patterns: (array zero-terminated=1): the patterns used to identify files of this type.
 * @restricted: a boolean.
 * @func: (scope notified): a loading method.
 * @priority: a integer.
 * @notify: (allow-none): a function.
 * @data: (closure): some data.
 *
 * Creates a new #VisuDataLoader with the given description.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a newly created #VisuDataLoader object.
 **/
VisuDataLoader* visu_data_loader_new_full(const gchar* descr,
                                          const gchar** patterns, gboolean restricted,
                                          VisuDataLoaderFunc func, guint priority,
                                          GDestroyNotify notify, gpointer data)
{
  VisuDataLoader *loader;

  loader = g_object_new(VISU_TYPE_DATA_LOADER,
                        "name", descr, "ignore-type", restricted, NULL);
  tool_file_format_addPatterns(TOOL_FILE_FORMAT(loader), patterns);
  loader->priv->load = func;
  loader->priv->notify = notify;
  loader->priv->data = data;
  loader->priv->priority = priority;

  return loader;
}

/**
 * visu_data_loader_comparePriority:
 * @a: a #VisuDataLoader object.
 * @b: a #VisuDataLoader object.
 *
 * Compare the priority of @a and @b.
 *
 * Since: 3.8
 *
 * Returns: -1 if @a is at a lower priority than @b, +1 otherwise and
 * 0 in case of equality.
 **/
gint visu_data_loader_comparePriority(const VisuDataLoader *a, const VisuDataLoader *b)
{
  g_return_val_if_fail(VISU_IS_DATA_LOADER(a) && VISU_IS_DATA_LOADER(b), 0);

  if (a->priv->priority < b->priv->priority)
    return -1;
  else if (a->priv->priority > b->priv->priority)
    return +1;
  else
    return 0;
}

/**
 * visu_data_loader_load:
 * @loader: a #VisuDataLoader object.
 * @data: a #VisuDataLoadable object to be populated.
 * @type: a file type.
 * @iSet: an integer.
 * @cancel: (allow-none): a cancellation object.
 * @error: an error location.
 *
 * Try to load @filename as a file format corresponding to
 * @loader. @error is not set if @filename is not of this format,
 * simply %FALSE is returned.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @filename is of a format handled by @loader.
 **/
gboolean visu_data_loader_load(VisuDataLoader *loader, VisuDataLoadable *data,
                               guint type, guint iSet,
                               GCancellable *cancel, GError **error)
{
  gboolean res;

  g_return_val_if_fail(VISU_IS_DATA_LOADER(loader), FALSE);

  if (loader->priv->load)
    {
      visu_data_loader_setStatus(loader, _("Loading..."));
      DBG_fprintf(stderr, "Visu Loader: data obj has %d ref count.\n",
                  G_OBJECT(data)->ref_count);
      res = loader->priv->load(loader, data, type, iSet, cancel, error);
      DBG_fprintf(stderr, "Visu Loader: data obj has %d ref count.\n",
                  G_OBJECT(data)->ref_count);
      return res;
    }
  else
    return FALSE;
}

/**
 * visu_data_loader_setStatus:
 * @loader: a #VisuDataLoader object.
 * @status: a string.
 *
 * Change the current status of @loader. This can be used to notify
 * changes when visu_data_loader_load() is taking long time.
 *
 * Since: 3.8
 **/
void visu_data_loader_setStatus(VisuDataLoader *loader, const gchar *status)
{
  g_return_if_fail(VISU_IS_DATA_LOADER(loader));

  g_free(loader->priv->status);
  loader->priv->status = g_strdup(status);
  g_object_notify_by_pspec(G_OBJECT(loader), _properties[PROP_STATUS]);
}


/**
 * VisuDataLoaderIter:
 *
 * Structure used to define #VisuDataLoaderIter objects.
 *
 * Since: 3.8
 */
struct _VisuDataLoaderIter
{
  guint refcount;
  GHashTable *elements;
};
struct _OneElement
{
  const VisuElement *ele;
  guint pos;
  guint nbNodes;
};

GType visu_data_loader_iter_get_type(void)
{
  static GType g_define_type_id = 0;

  if (g_define_type_id == 0)
    g_define_type_id = g_boxed_type_register_static
      ("VisuDataLoaderIter", 
       (GBoxedCopyFunc)visu_data_loader_iter_ref,
       (GBoxedFreeFunc)visu_data_loader_iter_unref);
  return g_define_type_id;
}

/**
 * visu_data_loader_iter_new:
 *
 * Creates a newly allocated iterator. This iterator can be used to
 * accumulate nodes when parsing a file. When parsing is finished, use
 * visu_data_loader_iter_allocate() to allocate enough memory in a
 * #VisuNodeArray to store the parsed data.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a newly allocated #VisuDataLoaderIter
 * object. Use visu_data_loader_iter_unref() when no longer needed.
 **/
VisuDataLoaderIter* visu_data_loader_iter_new()
{
  VisuDataLoaderIter *iter;

  iter = g_malloc(sizeof(VisuDataLoaderIter));
  iter->refcount = 1;
  iter->elements = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);

  return iter;
}
/**
 * visu_data_loader_iter_addNode:
 * @iter: a #VisuDataLoaderIter object.
 * @element: a #VisuElement object.
 *
 * Register an additional node of type @element.
 *
 * Since: 3.8
 **/
void visu_data_loader_iter_addNode(VisuDataLoaderIter *iter, const VisuElement *element)
{
  struct _OneElement *infos;

  g_return_if_fail(iter);

  infos = (struct _OneElement*)g_hash_table_lookup(iter->elements, element);
  if (!infos)
    {
      infos = g_malloc(sizeof(struct _OneElement));
      infos->ele = element;
      infos->pos = g_hash_table_size(iter->elements);
      infos->nbNodes = 1;
      g_hash_table_insert(iter->elements, (gpointer)element, infos);
    }
  else
    infos->nbNodes += 1;
}
static void _valToType(gpointer key _U_, gpointer value, gpointer data)
{
  struct _OneElement *infos;
  
  infos = (struct _OneElement *)value;
  DBG_fprintf(stderr, " | %d -> '%s' (%p).\n", infos->pos,
              infos->ele->name, (gpointer)infos->ele);
  g_array_index((GArray*)data, const VisuElement*, infos->pos) = infos->ele;
}
static GArray* _createElementArray(const VisuDataLoaderIter *iter)
{
  GArray *types;

  g_return_val_if_fail(iter, (GArray*)0);

  types = g_array_sized_new(FALSE, FALSE, sizeof(VisuElement*),
                            g_hash_table_size(iter->elements));
  g_array_set_size(types, g_hash_table_size(iter->elements));
  g_hash_table_foreach(iter->elements, _valToType, types);

  return types;
}
static void _valToNb(gpointer key _U_, gpointer value, gpointer data)
{
  struct _OneElement *infos;
  
  infos = (struct _OneElement *)value;
  DBG_fprintf(stderr, " | %d -> %d.\n", infos->pos, infos->nbNodes);
  g_array_index((GArray*)data, guint, infos->pos) = infos->nbNodes;
}
static GArray* _createSizeArray(const VisuDataLoaderIter *iter)
{
  GArray *nattyp;

  g_return_val_if_fail(iter, (GArray*)0);

  nattyp = g_array_sized_new(FALSE, FALSE, sizeof(guint),
                             g_hash_table_size(iter->elements));
  g_array_set_size(nattyp, g_hash_table_size(iter->elements));
  g_hash_table_foreach(iter->elements, _valToNb, nattyp);

  return nattyp;
}
/**
 * visu_data_loader_iter_allocate:
 * @iter: a #VisuDataLoaderIter object.
 * @array: a #VisuNodeArray object.
 *
 * Call visu_node_array_allocate() on @array with the proper
 * #VisuElement list and number of nodes as registered in @iter.
 *
 * Since: 3.8
 *
 * Returns: the total number of nodes registered in @iter.
 **/
guint visu_data_loader_iter_allocate(VisuDataLoaderIter *iter, VisuNodeArray *array)
{
  GArray *types, *nattyp;
  guint i, natom;

  g_return_val_if_fail(iter && VISU_IS_NODE_ARRAY(array), 0);

  DBG_fprintf(stderr, "Visu Data Loader: allocate for %d elements.\n",
              g_hash_table_size(iter->elements));
  types  = _createElementArray(iter);
  nattyp = _createSizeArray(iter);
  natom = 0;
  for (i = 0; i < types->len; i++) natom += g_array_index(nattyp, guint, i);
  if (!natom)
    {
      g_array_free(nattyp, TRUE);
      g_array_free(types, TRUE);
      return 0;
    }

  /* Allocate the space for the nodes. */
  visu_node_array_allocate(array, types, nattyp);
  g_array_free(nattyp, TRUE);
  g_array_free(types, TRUE);

  return natom;
}
/**
 * visu_data_loader_iter_unref:
 * @iter: a #VisuDataLoaderIter object.
 *
 * Decrement the reference counter of @iter. When counter reaches 0,
 * the memory occupied by @iter is freed.
 *
 * Since: 3.8
 **/
void visu_data_loader_iter_unref(VisuDataLoaderIter *iter)
{
  g_return_if_fail(iter);

  iter->refcount -= 1;

  if (!iter->refcount)
    {
      g_hash_table_destroy(iter->elements);
      g_free(iter);
    }
}
/**
 * visu_data_loader_iter_ref:
 * @iter: a #VisuDataLoaderIter object.
 *
 * Increment the reference counter on @iter.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a pointer on @iter.
 **/
VisuDataLoaderIter* visu_data_loader_iter_ref(VisuDataLoaderIter *iter)
{
  g_return_val_if_fail(iter, (VisuDataLoaderIter*)0);

  iter->refcount += 1;
  return iter;
}
