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

#include <stdlib.h>
#include <string.h>

#include "visu_configFile.h"
#include "coreTools/toolColor.h"

/**
 * SECTION:visu_elements
 * @short_description: defines methods to create and acccess to
 * #VisuElement.
 *
 * <para>V_Sim is used to rendered at given position several object of
 * the same kind. The #VisuElement object is used to control that
 * kind. Typically, it corresponds to chemical element. It can
 * represent the silicon, the iron...</para>
 *
 * <para>#VisuElement are defined by their name and have some
 * characteristic like if they are rendered or not, or masked or not.</para>
 */

/**
 * VisuElement:
 *
 * Structure to stores #VisuElement objects.
 */
/**
 * VisuElementClass:
 * @parent: the parent.
 *
 * An opaque structure representing the class of #VisuElement objects.
 */

#define FLAG_ELEMENT_PROPERTIES "element_properties"
#define DESC_ELEMENT_PROPERTIES "Define some properties ; rendered (0 or 1) masked" \
  "(0 or 1)."
static gboolean _elementProperties[3];

static void exportResources(GString *data, VisuData* dataObj);

/**
 * _VisuElement:
 * @parent: the parent.
 * @name: Name of the key used in the hashtable to find
 *   this element. The int variable is the number
 *   of this type.
 * @typeNumber: An integer unique for each VisuElement, it is
 *   used as a name for the opengl material associated
 *   with it.
 * @rgb: main color of the element  in [r, g, b, a] format.
 * @material: lighting effects for material in [amb, dif, shi, spe,
 * emi] format.
 * @rendered: A flag to store if all nodes of this element are rendered or not.
 *   Its default value is TRUE.
 * @sensitiveToPlanes: a flag to say if nodes of this element are sensitive
 *   to the masking effect of planes (default is TRUE).
 * @physical: TRUE if the element is a physical one.
 * @dispose_has_run: internal.
 *
 * Structure to store the description of an element.
 */

enum {
  PROP_0,
  PROP_RENDERED,
  PROP_MASKABLE,
  PROP_COLORIZABLE,
  N_PROPS
};
static GParamSpec *_properties[N_PROPS];

enum {
  ELEMENT_NEW_SIGNAL,
  LAST_SIGNAL
};

/* This hashtable contains a list of all different
   elements loaded in visu. */
static GHashTable *allElements_table = NULL;
static GList *allElements_list = NULL;

/* These functions write all the element list to export there associated resources. */
static void visu_element_finalize(GObject* obj);
static void visu_element_get_property(GObject* obj, guint property_id,
                                      GValue *value, GParamSpec *pspec);
static void visu_element_set_property(GObject* obj, guint property_id,
                                      const GValue *value, GParamSpec *pspec);

static void onEntryProperties(VisuConfigFile *obj, VisuConfigFileEntry *entry, gpointer data);

static guint visu_element_signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE(VisuElement, visu_element, VISU_TYPE_OBJECT)

static void visu_element_class_init(VisuElementClass *klass)
{
  VisuConfigFileEntry *resourceEntry, *oldEntry;

  DBG_fprintf(stderr, "Visu Element: creating the class of the object.\n");
  DBG_fprintf(stderr, "                - adding new signals ;\n");
  /**
   * VisuElement::ElementNew:
   * @element: the object emitting the signal.
   *
   * A new element is available.
   *
   * Since: 3.6
   */
  visu_element_signals[ELEMENT_NEW_SIGNAL] = 
    g_signal_new("ElementNew",
                 G_TYPE_FROM_CLASS (klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
                 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
                 G_TYPE_NONE, 0);


  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->finalize = visu_element_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_element_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_element_get_property;

  /**
   * VisuElement::rendered:
   *
   * If element is rendered or not.
   *
   * Since: 3.8
   */
  _properties[PROP_RENDERED] =
    g_param_spec_boolean("rendered", "Rendered",
                         "if element is rendered", TRUE, G_PARAM_READWRITE);
  /**
   * VisuElement::maskable:
   *
   * If element is maskable by planes or not.
   *
   * Since: 3.8
   */
  _properties[PROP_MASKABLE] =
    g_param_spec_boolean("maskable", "Maskable",
                         "if element is maskable", TRUE, G_PARAM_READWRITE);
  /**
   * VisuElement::colorizable:
   *
   * If element is colorizable or not.
   *
   * Since: 3.8
   */
  _properties[PROP_COLORIZABLE] =
    g_param_spec_boolean("colorizable", "Colorizable",
                         "if element is colorizable", TRUE, G_PARAM_READWRITE);

  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROPS, _properties);

  oldEntry = visu_config_file_addEntry(VISU_CONFIG_FILE_RESOURCE,
                                       "element_is_rendered",
                                       "Obsolete entry included in element_properties",
                                       1, NULL);
  visu_config_file_entry_setVersion(oldEntry, 3.1f);
  resourceEntry = visu_config_file_addBooleanArrayEntry(VISU_CONFIG_FILE_RESOURCE,
                                                        FLAG_ELEMENT_PROPERTIES,
                                                        DESC_ELEMENT_PROPERTIES,
                                                        2, _elementProperties, TRUE);
  visu_config_file_entry_setVersion(resourceEntry, 3.4f);
  visu_config_file_entry_setReplace(resourceEntry, oldEntry);
  g_signal_connect(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_ELEMENT_PROPERTIES,
                   G_CALLBACK(onEntryProperties), (gpointer)0);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_RESOURCE, exportResources);

  /* Set internal parameters. */
  allElements_table =
    g_hash_table_new_full(g_str_hash, g_str_equal,
			  NULL, (GDestroyNotify)g_object_unref);
}

static void visu_element_init(VisuElement *ele)
{
  DBG_fprintf(stderr, "Visu Element: initializing a new object (%p).\n",
	      (gpointer)ele);
  ele->physical = TRUE;
  ele->_rendered = TRUE;
  ele->_maskable = TRUE;
  ele->_colorizable = TRUE;
}

/* This method is called once only. */
static void visu_element_finalize(GObject* obj)
{
  VisuElement *ele;

  g_return_if_fail(obj);
  DBG_fprintf(stderr, "Visu Element: finalize object %p.\n", (gpointer)obj);

  ele = VISU_ELEMENT(obj);
  g_free(ele->name);
  g_hash_table_steal(allElements_table, ele);
  allElements_list = g_list_remove(allElements_list, ele);

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Visu Element: chain to parent.\n");
  G_OBJECT_CLASS(visu_element_parent_class)->finalize(obj);
  DBG_fprintf(stderr, "Visu Element: freeing ... OK.\n");
}
static void visu_element_get_property(GObject* obj, guint property_id,
                                      GValue *value, GParamSpec *pspec)
{
  VisuElement *self = VISU_ELEMENT(obj);

  DBG_fprintf(stderr, "Visu Element: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case PROP_RENDERED:
      g_value_set_boolean(value, self->_rendered);
      DBG_fprintf(stderr, "%d.\n", self->_rendered);
      break;
    case PROP_MASKABLE:
      g_value_set_boolean(value, self->_maskable);
      DBG_fprintf(stderr, "%d.\n", self->_maskable);
      break;
    case PROP_COLORIZABLE:
      g_value_set_boolean(value, self->_colorizable);
      DBG_fprintf(stderr, "%d.\n", self->_colorizable);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_element_set_property(GObject* obj, guint property_id,
                                      const GValue *value, GParamSpec *pspec)
{
  VisuElement *self = VISU_ELEMENT(obj);

  DBG_fprintf(stderr, "Visu Element: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case PROP_RENDERED:
      DBG_fprintf(stderr, "%d.\n", g_value_get_boolean(value));
      visu_element_setRendered(self, g_value_get_boolean(value));
      break;
    case PROP_MASKABLE:
      DBG_fprintf(stderr, "%d.\n", g_value_get_boolean(value));
      visu_element_setMaskable(self, g_value_get_boolean(value));
      break;
    case PROP_COLORIZABLE:
      DBG_fprintf(stderr, "%d.\n", g_value_get_boolean(value));
      visu_element_setColorizable(self, g_value_get_boolean(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_element_new:
 * @key: the name of the new element to create.
 *
 * Allocate a new visuElement with the specified name. Remember
 * that names must be unique since they identify the element.
 *
 * Returns: (transfer none): the newly created VisuElement or 0 if something goes
 * wrong in the process (if the name already exist for example).
 */
VisuElement *visu_element_new(const char *key)
{
  VisuElement *ele;

  ele = visu_element_lookup(key);
  if (ele)
    {
      g_warning("Element '%s' already exists.", key);
      return ele;
    }

  ele = VISU_ELEMENT(g_object_new(VISU_TYPE_ELEMENT, NULL));
  ele->name       = g_strdup((key[0] == '%')?key + 1:key);
  ele->physical   = (key[0] != '%') && strcmp(key, "g") && strcmp(key, "G");
  g_hash_table_insert(allElements_table, (gpointer)ele->name, (gpointer)ele);
  allElements_list = g_list_append(allElements_list, (gpointer)ele);

  DBG_fprintf(stderr, "Visu Elements: create a new VisuElement '%s' -> %p.\n",
	      key, (gpointer)ele);  
  g_signal_emit(G_OBJECT(ele), visu_element_signals[ELEMENT_NEW_SIGNAL], 0, NULL);
  DBG_fprintf(stderr, "Visu Elements: new element signal OK.\n");

  return ele;
}

/**
 * visu_element_getAllElements:
 *
 * This method returns a list of all the registered #VisuElement.
 * The returned list is read-only.
 *
 * Returns: (element-type VisuElement) (transfer none): the list of
 * all known #VisuElement.
 */
const GList *visu_element_getAllElements(void)
{
  return allElements_list;
}
/**
 * visu_element_pool_finalize: (skip)
 *
 * Destroy the internal list of existing #VisuElement.
 *
 * Since: 3.8
 **/
void visu_element_pool_finalize(void)
{
  GList *lst;

  lst = g_list_copy(allElements_list);
  g_list_free_full(lst, (GDestroyNotify)g_object_unref);
}

/**
 * visu_element_retrieveFromName:
 * @name: a string that identify the #VisuElement (in UTF8) ;
 * @nw: (out caller-allocates): a location to store a boolean.
 *
 * Try to find a #VisuElement already associated to that @name or
 * create a new one if none has been found. If @nw is not NULL it is
 * set to FALSE if @name was found.
 *
 * Returns: (transfer none): a #VisuElement associated to this @name.
 */
VisuElement *visu_element_retrieveFromName(const gchar *name, gboolean *nw)
{
  VisuElement *ele;

  if (!allElements_table)
    g_type_class_ref(VISU_TYPE_ELEMENT);

  DBG_fprintf(stderr, "Visu Element: retrieve '%s' (%d).\n", name,
              g_hash_table_size(allElements_table));

  if (nw)
    *nw = FALSE;

  ele = g_hash_table_lookup(allElements_table, (name[0] == '%')?name + 1:name);
  if (ele)
    return ele;

  if (nw)
    *nw = TRUE;

  return visu_element_new(name);
}

/**
 * visu_element_lookup:
 * @name: a string.
 *
 * Lookup for element @name in the base. Do not create it if not
 * found. To do this, use visu_element_retrieveFromName().
 *
 * Since: 3.6
 *
 * Returns: (transfer none): the found #VisuElement or NULL.
 */
VisuElement *visu_element_lookup(const gchar *name)
{
  if (!allElements_table)
    g_type_class_ref(VISU_TYPE_ELEMENT);

  DBG_fprintf(stderr, "Visu Element: lookup '%s' (%d).\n",
              name, g_hash_table_size(allElements_table));
  return g_hash_table_lookup(allElements_table, (name[0] == '%')?name + 1:name);
}

/**
 * visu_element_getName:
 * @ele: a #VisuElement object.
 *
 * This routines returns the name of the given @ele.
 *
 * Since: 3.7
 *
 * Returns: a string owned by V_Sim.
 */
const gchar* visu_element_getName(const VisuElement *ele)
{
  g_return_val_if_fail(VISU_IS_ELEMENT(ele), (const gchar*)0);

  return ele->name;
}
/**
 * visu_element_getPhysical:
 * @ele: a #VisuElement object.
 *
 * This routine gets if @ele is physical or not. A not physical
 * element can be used for instance to represent specific points...
 *
 * Since: 3.7
 *
 * Returns: TRUE if @ele is indeed physical.
 */
gboolean visu_element_getPhysical(VisuElement *ele)
{
  g_return_val_if_fail(VISU_IS_ELEMENT(ele), FALSE);

  return ele->physical;
}
/**
 * visu_element_getRendered:
 * @self: a #VisuElement object.
 *
 * Retrieve wether all #VisuNode of @self are currently hidden or not.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @self is hidden or not.
 **/
gboolean visu_element_getRendered(const VisuElement *self)
{
  g_return_val_if_fail(VISU_IS_ELEMENT(self), FALSE);

  return self->_rendered;
}
/**
 * visu_element_setRendered:
 * @self: a #VisuElement object.
 * @val: a boolean.
 *
 * Changes if all #VisuNode of type @self are hidden or not.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_element_setRendered(VisuElement *self, gboolean val)
{
  g_return_val_if_fail(VISU_IS_ELEMENT(self), FALSE);

  if (self->_rendered == val)
    return FALSE;

  self->_rendered = val;
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_RENDERED]);
  return TRUE;
}
/**
 * visu_element_getMaskable:
 * @self: a #VisuElement object.
 *
 * Retrieve whether #VisuNode of type @self can be hidden by planes or any
 * #VisuNodeMasker object.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @self is maskable.
 **/
gboolean visu_element_getMaskable(const VisuElement *self)
{
  g_return_val_if_fail(VISU_IS_ELEMENT(self), FALSE);

  return self->_maskable;
}
/**
 * visu_element_setMaskable:
 * @self: a #VisuElement object.
 * @val: a boolean value.
 *
 * Changes if all #VisuNode of type @self can be affected by a
 * #VisuNodeMasker object.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_element_setMaskable(VisuElement *self, gboolean val)
{
  g_return_val_if_fail(VISU_IS_ELEMENT(self), FALSE);

  if (self->_maskable == val)
    return FALSE;

  self->_maskable = val;
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_MASKABLE]);
  return TRUE;
}

/**
 * visu_element_getColorizable:
 * @self: a #VisuElement object.
 *
 * Retrieve whether #VisuNode of type @self can be colorized by any
 * #VisuDataColorizer object.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @self is colorizable.
 **/
gboolean visu_element_getColorizable(const VisuElement *self)
{
  g_return_val_if_fail(VISU_IS_ELEMENT(self), FALSE);

  return self->_colorizable;
}
/**
 * visu_element_setColorizable:
 * @self: a #VisuElement object.
 * @val: a boolean value.
 *
 * Changes if all #VisuNode of type @self can be affected by a
 * #VisuDataColorizer object.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_element_setColorizable(VisuElement *self, gboolean val)
{
  g_return_val_if_fail(VISU_IS_ELEMENT(self), FALSE);

  if (self->_colorizable == val)
    return FALSE;

  self->_colorizable = val;
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_COLORIZABLE]);
  
  return TRUE;
}

static void onEntryProperties(VisuConfigFile *obj _U_, VisuConfigFileEntry *entry,
                              gpointer data _U_)
{
  VisuElement *ele;

  ele = visu_element_retrieveFromName(visu_config_file_entry_getLabel(entry), (int*)0);
  if (!ele)
    return;
  visu_element_setRendered(ele, _elementProperties[0]);
  visu_element_setMaskable(ele, _elementProperties[1]);
  /* visu_element_setColorizable(ele, _elementProperties[2]); */
}
static void exportResources(GString *data, VisuData *dataObj)
{
  GList *pos;
  VisuElement *ele;

  visu_config_file_exportComment(data, DESC_ELEMENT_PROPERTIES);
  for (pos = allElements_list; pos; pos = g_list_next(pos))
    {
      ele = VISU_ELEMENT(pos->data);
      if (!dataObj || visu_node_array_containsElement(VISU_NODE_ARRAY(dataObj), ele))
        visu_config_file_exportEntry(data, FLAG_ELEMENT_PROPERTIES, ele->name,
                                     "%d %d %d", ele->_rendered, ele->_maskable,
                                     ele->_colorizable);
    }
  visu_config_file_exportComment(data, "");
}
