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

#include <GL/gl.h>
#include <GL/glu.h> 

#include <glib.h>

#include <visu_tools.h>

/**
 * SECTION:light
 * @short_description: Controls the use of lights in the rendering
 * window.
 *
 * <para>One can defines several lights in OpenGL. The
 * #VisuGlLights is an object that stores several of them and
 * that can be applied to the current OpenGL context using
 * visu_gl_lights_apply(). The lights that are created with
 * visu_gl_light_newDefault() are ambiant light with a white colour. The
 * multiplier coefficient is use to soften lights when several are
 * used together. It is used as a factor for all light parameters
 * (ambient, diffuse...) ecept the specular one.</para>
 */

/**
 * VisuGlLight:
 * @enabled: if the light is used or not ;
 * @ambient: the ambient color of the light ;
 * @diffuse: the diffuse color of the light ;
 * @specular: the specular color of the light ;
 * @position: the position in space of the light ;
 * @multiplier: a value that multiply all color values (should be in [0;1]).
 *
 * This structure is convenient to store lights as defined by OpenGL.
 */

/**
 * VisuGlLights:
 *
 * A short way to access #_VisuGlLights objects.
 */
struct _VisuGlLights
{
  guint refCount;

  GList* list;
  /* Current size of the list. */
  gint nbStoredVisuGlLights;
  /* Number of lights called by glEnable() when last applied. */
  gint nbEnabledVisuGlLights;
};

/* Local methods. */
static void lighting_set(gpointer data, gpointer user_data);
static VisuGlLight* light_copy(VisuGlLight *light);

/**
 * visu_gl_lights_get_type:
 *
 * Create and retrieve a #GType for a #VisuGlLights object.
 *
 * Since: 3.7
 *
 * Returns: a new type for #VisuGlLights structures.
 */
GType visu_gl_lights_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("VisuGlLights", 
                                   (GBoxedCopyFunc)visu_gl_lights_ref,
                                   (GBoxedFreeFunc)visu_gl_lights_unref);
  return g_define_type_id;
}
/**
 * visu_gl_lights_new:
 *
 * Create a new #VisuGlLights object. It contains no light when created.
 * Use visu_gl_lights_add() to add new lights and
 * visu_gl_lights_remove() to remove others.
 *
 * Returns: a newly created #VisuGlLights. Use visu_gl_lights_free()
 *          to free such an object.
 */
VisuGlLights* visu_gl_lights_new()
{
  VisuGlLights *env;

  env = g_malloc(sizeof(VisuGlLights));
  env->refCount = 1;
  env->list = (GList*)0;
  env->nbStoredVisuGlLights = 0;
  env->nbEnabledVisuGlLights = 0;

  return env;
}
/**
 * visu_gl_lights_ref:
 * @env: a #VisuGlLights object.
 *
 * Increase the ref counter.
 *
 * Since: 3.7
 *
 * Returns: itself.
 **/
VisuGlLights* visu_gl_lights_ref(VisuGlLights *env)
{
  env->refCount += 1;
  return env;
}
/**
 * visu_gl_lights_unref:
 * @env: a #VisuGlLights object.
 *
 * Decrease the ref counter, free all memory if counter reachs zero.
 *
 * Since: 3.7
 **/
void visu_gl_lights_unref(VisuGlLights *env)
{
  env->refCount -= 1;
  if (!env->refCount)
    visu_gl_lights_free(env);
}
/**
 * visu_gl_lights_free:
 * @env: a #VisuGlLights object.
 *
 * Free memory occupied by the given environnement.
 */
void visu_gl_lights_free(VisuGlLights *env)
{
  g_return_if_fail(env);

  visu_gl_lights_removeAll(env);
  /* Need to apply to disable all previously enbaled lights before
     freeing the object. */
  visu_gl_lights_apply(env);
  g_free(env);
}
/**
 * visu_gl_lights_available:
 * @env: a #VisuGlLights object.
 *
 * Inquire if one can add more lights to the scene.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the maximum number of lights allowed by OpenGL
 * implementation is not reached yet.
 **/
gboolean visu_gl_lights_available(VisuGlLights *env)
{
  g_return_val_if_fail(env, FALSE);
  return (env->nbStoredVisuGlLights < GL_MAX_LIGHTS);
}
/**
 * visu_gl_lights_add:
 * @env: a #VisuGlLights object ;
 * @light: a #VisuGlLight object.
 *
 * This method adds the given @light to the list of known lights declared
 * in the given environnement. The light is not copied and should not be freed
 * when stored in the environnement.
 *
 * Returns: TRUE if visu_gl_lights_apply() should be called.
 */
gboolean visu_gl_lights_add(VisuGlLights *env, VisuGlLight *light)
{
  g_return_val_if_fail(env && light, FALSE);
  g_return_val_if_fail(env->nbStoredVisuGlLights < GL_MAX_LIGHTS, FALSE);

  DBG_fprintf(stderr, "VisuGlLight : add a new light (%p).\n", (gpointer)light);
  env->list = g_list_append(env->list, light);
  env->nbStoredVisuGlLights += 1;

  return TRUE;
}
/**
 * visu_gl_lights_remove:
 * @env: a #VisuGlLights object ;
 * @light: a #VisuGlLight object.
 *
 * This method removes the given @light from the list of known lights  declared
 * in the given environnement. The @light argument is first removed and then freed
 * by a call to g_free().
 *
 * Returns: TRUE if visu_gl_lights_apply() should be called.
 */
gboolean visu_gl_lights_remove(VisuGlLights *env, VisuGlLight *light)
{
  g_return_val_if_fail(env && light, FALSE);
  
  DBG_fprintf(stderr, "VisuGlLight : remove a light (%p).\n", (gpointer)light);
  env->list = g_list_remove(env->list, light);
  g_free(light);
  env->nbStoredVisuGlLights -= 1;

  return TRUE;
}
/**
 * visu_gl_lights_getList:
 * @env: a #VisuGlLights object.
 *
 * Retrieve the list of known #VisuGlLight used by the given environnement.
 *
 * Returns: (transfer none) (element-type VisuGlLight*): a list of
 * #VisuGlLight objects. Should not be freed.
 */
GList* visu_gl_lights_getList(VisuGlLights *env)
{
  g_return_val_if_fail(env, (GList*)0);

  return env->list;
}
/**
 * visu_gl_lights_removeAll:
 * @env: a #VisuGlLights object.
 *
 * Empty the list of stored lights. All stored lights objects are freed.
 *
 * Returns: TRUE if the visu_gl_lights_apply() should be called.
 */
gboolean visu_gl_lights_removeAll(VisuGlLights *env)
{
  GList *list;
  int n;

  g_return_val_if_fail(env, FALSE);

  if (!env->list)
    return FALSE;

  DBG_fprintf(stderr, "VisuGlLight : emptying list of stored lights of"
	      " environnement %p.\n", (gpointer)env);
  list = env->list;
  n = 0;
  while (list)
    {
      DBG_fprintf(stderr, " | removing light %p\n", list->data);
      g_free(list->data);
      n += 1;
      list = g_list_next(list);
    }
  g_list_free(env->list);
  env->list = (GList*)0;
  env->nbStoredVisuGlLights = 0;

  return TRUE;
}
/**
 * visu_gl_lights_apply:
 * @env: a #VisuGlLights object.
 *
 * Apply all stored informations about lights to the current OpenGL context.
 *
 * Returns: TRUE if OpenGL context has been changed.
 */
gboolean visu_gl_lights_apply(VisuGlLights *env)
{
  gint nb;
  float lmodel_ambient[4] = {0.2f, 0.2f, 0.2f, 1.0f};

  g_return_val_if_fail(env, FALSE);

  if (env->nbEnabledVisuGlLights == 0 && !env->list)
    return FALSE;

  /* glPushMatrix(); */
  /* glLoadIdentity(); */

  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);
  glEnable(GL_LIGHTING);

  for (nb = 0; nb < env->nbEnabledVisuGlLights; nb++)
    glDisable(GL_LIGHT0 + nb);
    
  g_list_foreach(env->list, lighting_set, (gpointer)env);

  env->nbEnabledVisuGlLights = env->nbStoredVisuGlLights;
   
  /* glPopMatrix(); */

  return TRUE;
}

/**
 * visu_gl_light_newDefault:
 *
 * Create a new light with default value (white color and position in
 * the front, right, top position of the screen).
 *
 * Returns: the newly created #VisuGlLight. Use g_free() to deallocate this light.
 */
VisuGlLight* visu_gl_light_newDefault()
{
  float params[16] =  {1.0f, 1.0f, 1.0f, 1.0f,
		       1.0f, 1.0f, 1.0f, 1.0f,
		       1.0f, 1.0f, 1.0f, 1.0f,
		       3.0f, 2.0f, 1.7f, 0.0f};
  VisuGlLight *light;
  int i;

  light = g_malloc(sizeof(VisuGlLight));
  light->enabled = TRUE;
  light->multiplier = 1.;
  for(i = 0; i < 4; i++)
    {
      light->ambient[i] = params[i];
      light->diffuse[i] = params[4 + i];
      light->specular[i] = params[8 + i];
      light->position[i] = params[12 + i];
    }
  return light;
}
/**
 * visu_gl_light_get_type:
 *
 * Create and retrieve a #GType for a #VisuGlLight object.
 *
 * Since: 3.7
 *
 * Returns: a new type for #VisuGlLight structures.
 */
GType visu_gl_light_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("VisuGlLight", 
                                   (GBoxedCopyFunc)light_copy,
                                   (GBoxedFreeFunc)g_free);
  return g_define_type_id;
}
static VisuGlLight* light_copy(VisuGlLight *light)
{
  VisuGlLight *out;

  out = g_malloc(sizeof(VisuGlLight));
  *out = *light;
  return out;
}

static void lighting_set(gpointer data, gpointer user_data)
{
  VisuGlLight *light0 = data;
  int n;
  float values[4];
  int i;
  VisuGlLights *env;

  g_return_if_fail(user_data);
  env = (VisuGlLights*)user_data;

  n = g_list_index(env->list, light0);
  if(light0->enabled == FALSE)
    {
      /* From glLightfv man page : "It is always the case that GL_LIGHTi = GL_LIGHT0 + i." */
      glDisable(GL_LIGHT0 + n);
      return;
    }

  glEnable(GL_LIGHT0 + n);
  for (i = 0; i < 4; i++) values[i] = light0->ambient[i] * light0->multiplier;
  glLightfv(GL_LIGHT0 + n, GL_AMBIENT,  values);
  for (i = 0; i < 4; i++) values[i] = light0->diffuse[i] * light0->multiplier;
  glLightfv(GL_LIGHT0 + n, GL_DIFFUSE, values);
  for (i = 0; i < 4; i++) values[i] = light0->specular[i] * 1.;
  glLightfv(GL_LIGHT0 + n, GL_SPECULAR, values);
  glLightfv(GL_LIGHT0 + n, GL_POSITION, light0->position);
  DBG_fprintf(stderr, "VisuGlLight : set light [%f][%f][%f][%f]\n", light0->position[0],
	      light0->position[1], light0->position[2], light0->multiplier);
}
