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

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

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

#include "openGLFunctions/objectList.h"
#include "opengl.h"

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


/**********************/
/* Private prototypes */
/**********************/

/* This is a pointer to the last added VisuElement. */
/* VisuElement *lastAddedVisuElement; */
int identifierMaterials;

/* This hashtable contains a list of all different
   elements loaded in visu. ntype is _not_ the number of items
   in this hashtable. */
GHashTable *differentVisuElements;
GList *visuElement_list;

/* This int stores how many user functions have needs
   to recreate the nodes of an element when its material
   has been changed. */
int flagCreateNodesAfterMaterialChange;


#define FLAG_RESOURCES_MAT "material"
#define DESC_RESOURCES_MAT "Codes the main color in RedGreenBlue format and the light effects on material, eight floats between 0. and 1."
#define FLAG_RESOURCES_ELE_RENDERED "element_is_rendered"
#define DESC_RESOURCES_ELE_RENDERED "Define if the specified element is rendered or not."

/* These functions detail how to read the RGB resource and
   the material resource introduced by this part. */
gboolean readMaterial(gchar **lines, int nbLines,
		      int position, GString *errorMessage);
gboolean readRendered(gchar **lines, int nbLines,
		      int position, GString *errorMessage);

/* These functions write all the element list to export there associated resources. */
gboolean exportResourcesRenderingBase(GString *data, int *nbLinesWritten);

void freeHashValue(gpointer data);



/* Add a VisuElement to the hashtable. */
int visuElementAdd(VisuElement* element)
{
  int nbElements;

  if (element &&
      !g_hash_table_lookup(differentVisuElements, (gpointer)element->name))
    {
      DBG_fprintf(stderr, "Visu Elements : adding %s (%d).\n", element->name,
		  GPOINTER_TO_INT(element));
      nbElements = g_hash_table_size(differentVisuElements);
      if (nbElements == NMAX_TP)
	{
	  fprintf(stderr, "WARNING : the system requires more than %d elements."
		  " This value is set at compile time with the variable NMAX_TP."
		  " If you need more, ask the maintainer to raise this value.\n", NMAX_TP);
	  return 1;
	}
      element->typeNumber = g_hash_table_size(differentVisuElements);
      g_hash_table_insert(differentVisuElements, (gpointer)element->name, (gpointer)element);
      visuElement_list = g_list_append(visuElement_list, (gpointer)element);
/*       lastAddedVisuElement = element; */
    }
  return 0;
}

/* This method is a unified way to retrieve the
   element at the beginning of line of value. */
VisuElement* visuElementGet_fromValueLine(char **line, int sizeMax, int *errorFlag)
{
  int i, res;
  char element[36];
  VisuElement *ele;
  int avance, cont;

  if (!line || !*line)
    {
      *errorFlag = VISU_ELEMENT_EMPTY_LINE;
      return (VisuElement*)0;
    }

  avance = 0;
  cont = 1;
  for(i = 0; (sizeMax < 0 || i < sizeMax) && (*line)[i] && avance < 36 && cont; i++)
    {
      /* one character found */
      if ( (*line)[i] != ' ' && (*line)[i] != '\t' &&
	   (*line)[i] != '\n' && (*line)[i] != '\0' )
	{
	  element[avance] = (*line)[i];
	  avance++;
	}
      /* one stopping character is found */
      else if ((*line)[i] == ' ' || (*line)[i] == '\t')
	{
	  if (avance)
	    cont = 0;
	}
      /* one error character is found */
      else
	{
	  *errorFlag = VISU_ELEMENT_BAD_NAME;
	  return (VisuElement*)0;
	}
    }
  if (!avance)
    {
      *errorFlag = VISU_ELEMENT_EMPTY_LINE;
      return (VisuElement*)0;
    }
    
  *line = *line + i;

  element[avance] = '\0';
  /* 	      fprintf(stderr, " -> '%s'\n", element); */
  /* Ok on a trouvé un élément. Est-il déjà connu? */
  ele = (VisuElement*)
    g_hash_table_lookup(differentVisuElements,
			(gpointer)(element));
  if (!ele)
    {
      ele = visuElementNewWithName(element);
      if (ele)
	{
	  res = visuElementAdd(ele);
	  if (res)
	    {
	      *errorFlag = VISU_ELEMENT_MAX_REACHED;
	      return (VisuElement*)0;
	    }
	}
      else
	*errorFlag = VISU_ELEMENT_CREATE_ERROR;
    }

  return ele;
}

void visuElementPrint_errorFromValueLine(GString *dest, int line, int flag)
{
  switch (flag)
    {
    case VISU_ELEMENT_EMPTY_LINE:
      g_string_append_printf(dest, _("WARNING! Parse error at line %d,"
						 " line is empty.\n"), line);
      break;
    case VISU_ELEMENT_BAD_NAME:
      g_string_append_printf(dest, _("WARNING! Parse error at line %d,"
						 " there is nothing after the element"
						 " name.\n"), line);
      break;
    case VISU_ELEMENT_MAX_REACHED:
      g_string_append_printf(dest, _("WARNING! Parse error at line %d,"
						 " can't add this knew element,"
						 " maximum (%d) has been reached.\n"),
			     line, NMAX_TP);
      break;
    case VISU_ELEMENT_CREATE_ERROR:
      g_string_append_printf(dest, _("WARNING! Parse error at line %d,"
						 " can't create this knew element.\n"),
			     line);
      break;
    }
}

GList* visuElementGet_allElements()
{
  return visuElement_list;
}
VisuElement* visuElementGet_fromName(char* name)
{
  return g_hash_table_lookup(differentVisuElements, (gpointer)name);
}



/* It saves the values of rgb in the specified VisuElement. */
int visuElementSet_allColorValues(VisuElement* ele, float rgb[4], float material[5])
{
  int chgt;

  chgt = (ele->rgb[0] != rgb[0]) || (ele->rgb[1] != rgb[1]) ||
    (ele->rgb[2] != rgb[2]) || (ele->rgb[3] != rgb[3]);
  ele->rgb[0] = rgb[0];
  ele->rgb[1] = rgb[1];
  ele->rgb[2] = rgb[2];
  ele->rgb[3] = rgb[3];
  chgt = chgt || (ele->material[0] != material[0]) || (ele->material[1] != material[1]) ||
    (ele->material[2] != material[2]) || (ele->material[3] != material[3]) ||
    (ele->material[4] != material[4]);
  ele->material[0] = material[0];
  ele->material[1] = material[1];
  ele->material[2] = material[2];
  ele->material[3] = material[3];
  ele->material[4] = material[4];

  if (chgt)
    visuElement_createMaterial(ele);

  if (chgt && ele->rendered)
    return 2 * flagCreateNodesAfterMaterialChange - 1;
  else
    return 0;
}

int visuElementSet_allRGBValues(VisuElement* ele, float rgb[4])
{
  if (!ele)
    return 0;

  if (ele->rgb[0] == rgb[0] &&
      ele->rgb[1] == rgb[1] &&
      ele->rgb[2] == rgb[2] &&
      ele->rgb[3] == rgb[3])
    return 0;

  ele->rgb[0] = rgb[0];
  ele->rgb[1] = rgb[1];
  ele->rgb[2] = rgb[2];
  ele->rgb[3] = rgb[3];

  visuElement_createMaterial(ele);

  if (ele->rendered)
    return 2 * flagCreateNodesAfterMaterialChange - 1;
  else
    return 0;
}

/* It saves the specific value of rgb (0 for red, 1 for green and 2
   for bluein the specified VisuElement. */
int visuElementSet_RGBValue(VisuElement* ele, int rgb, float value)
{
  if (rgb < 0 || rgb > 3)
    return 0;

  if (value == ele->rgb[rgb])
    return 0;

  ele->rgb[rgb] = value;
  visuElement_createMaterial(ele);
  if (ele->rendered)
    return 2 * flagCreateNodesAfterMaterialChange - 1;
  else
    return 0;
}

/* It saves the values of material in the specified VisuElement. */
int visuElementSet_allMaterialValues(VisuElement* ele, float material[5])
{
  if (!ele)
    return 0;

  ele->material[0] = material[0];
  ele->material[1] = material[1];
  ele->material[2] = material[2];
  ele->material[3] = material[3];
  ele->material[4] = material[4];

  visuElement_createMaterial(ele);
  if (ele->rendered)
    return 2 * flagCreateNodesAfterMaterialChange - 1;
  else
    return 0;
}

/* It saves the specific value of material (use the enum as the parameter
   material) in the specified VisuElement. */
int visuElementSet_materialValue(VisuElement* ele, int material, float value)
{
  if (material < 0 || material > 4)
    return 0;

  if (value == ele->material[material])
    return 0;

  ele->material[material] = value;
  visuElement_createMaterial(ele);
  if (ele->rendered)
    return 2 * flagCreateNodesAfterMaterialChange - 1;
  else
    return 0;
}

VisuElement *visuElementNewWithName(char *key)
{
  VisuElement *ele;
  int i;

  ele = malloc(sizeof(VisuElement));
  if (!ele)
    {
      allocationProblems();
      return (VisuElement*)0;
    }

  ele->name = malloc(sizeof(char) * (strlen(key) + 1));
  if (!ele->name)
    {
      allocationProblems();
      return (VisuElement*)0;
    }
  strcpy(ele->name, key);
  ele->typeNumber = -1;
  ele->materialIsUpToDate = FALSE;
  ele->rendered = TRUE;

  ele->properties = g_hash_table_new_full(g_str_hash, g_str_equal,
					  NULL, freeGPointer);
  if (!ele)
    {
      allocationProblems();
      return (VisuElement*)0;
    }

  for (i = 0; i < 4; i++)
    ele->rgb[i] = 1.;

  for (i = 0; i < 5; i++)
    ele->material[i] = 0.25;
  DBG_fprintf(stderr, "Visu Elements : creating a new VisuElement '%s' -> %d.\n",
	      key, GPOINTER_TO_INT(ele));
  return ele;
}

void visuElement_createMaterial(VisuElement *ele)
{
  float mm[4] = {0.0f, 0.0f, 0.0f, 0.0f};

  g_return_if_fail(ele);

  DBG_fprintf(stderr, "Visu Elements : creating material for %s (ele id %d - OpenGL id %d)\n", ele->name, ele->typeNumber, identifierMaterials + ele->typeNumber);
/*   fprintf(stderr, "%f %f %f - %f %f %f %f %f\n", ele->rgb[0], ele->rgb[1], ele->rgb[2], ele->material[0], ele->material[1], ele->material[2], ele->material[3], ele->material[4]); */
  mm[3] = ele->rgb[3];

  glDeleteLists(identifierMaterials + ele->typeNumber, 1);
  glNewList(identifierMaterials + ele->typeNumber, GL_COMPILE);
  setOpenGlMaterial(ele->material, ele->rgb);
  glEndList();
  ele->materialIsUpToDate = TRUE;
}

/* return the OpenGL identifier of the specified VisuElement. */
int visuElementGet_identifierMaterial(const VisuElement *ele)
{
  if (!ele)
    return 0;
  if (!ele->materialIsUpToDate)
    fprintf(stderr, "WARNING! Calling a material list not up to date for element '%s'.\n",
	    ele->name);
/*     visuElement_createMaterial((gpointer)ele->name, (gpointer)ele, (gpointer)0); */
  return identifierMaterials + ele->typeNumber;
}

void visuElementSet_updateNodesOnMaterialChange()
{
  flagCreateNodesAfterMaterialChange += 1;
  DBG_fprintf(stderr, "Visu Element : set flagCreateNodesAfterMaterialChange to %d.\n",
	      flagCreateNodesAfterMaterialChange);
}
void visuElementUnset_updateNodesOnMaterialChange()
{
  flagCreateNodesAfterMaterialChange -= 1;
  if (flagCreateNodesAfterMaterialChange < 0)
    flagCreateNodesAfterMaterialChange = 0;
  DBG_fprintf(stderr, "Visu Element : set flagCreateNodesAfterMaterialChange to %d.\n",
	      flagCreateNodesAfterMaterialChange);
}

int initVisuElements()
{
  char *name = "Visu elements";
  /* Description of the visuModule visu_data. */
  char *description = _("Draw all the elements.");
  VisuConfigFileEntry *resourceEntry;

  differentVisuElements =
    g_hash_table_new_full(g_str_hash, g_str_equal,
			  (GDestroyNotify)freeString,
			  (GDestroyNotify)freeHashValue);
  if (!differentVisuElements)
    {
      allocationProblems();
      return 0;
    }
  visuElement_list = (GList*)0;
/*   lastAddedVisuElement = (VisuElement*)0; */

  /* Create a VisuModule to registered the new resources as
     rgb and material. */
  resourceEntry = visuConfigFileAdd_entry(VISU_CONFIGFILE_RESOURCE,
					  FLAG_RESOURCES_MAT,
					  DESC_RESOURCES_MAT,
					  1, readMaterial);
  resourceEntry = visuConfigFileAdd_entry(VISU_CONFIGFILE_RESOURCE,
					  FLAG_RESOURCES_ELE_RENDERED,
					  DESC_RESOURCES_ELE_RENDERED,
					  1, readRendered);
  visuConfigFileAdd_exportFunction(VISU_CONFIGFILE_RESOURCE,
				   exportResourcesRenderingBase);


  /* Get an OpenGL identifier to store all the materials. */
  identifierMaterials = openGLObjectList_new(NMAX_TP);

  flagCreateNodesAfterMaterialChange = 0;

  return 1;
}

/******************/
/* Resources part */
/******************/
gboolean readMaterial(gchar **lines, int nbLines,
		      int position, GString *errorMessage)
{
  char *cursor;
  VisuElement* ele;
  int res, errorFlag;
  float rgb[4];
  float material[5];

  cursor = lines[0];
  ele = visuElementGet_fromValueLine(&cursor, -1, &errorFlag);
  if (!ele)
    {
      if (errorMessage)
	visuElementPrint_errorFromValueLine(errorMessage, position, errorFlag);
      return FALSE;
    }
  res = sscanf(cursor, "%f %f %f %f %f %f %f %f %f",
	       &rgb[0], &rgb[1], &rgb[2], &rgb[3],
	       &material[0],
	       &material[1],
	       &material[2],
	       &material[3],
	       &material[4]);
  if (res != 9)
    {
      if (errorMessage)
	g_string_append_printf(errorMessage, _("WARNING! Parse error at line %d,"
						   " 8 floating point values (0 <= v"
						   " <= 1) must appear after the"
						   " material markup.\n"), position);
      return FALSE;
    }
  res = 0;
  res = res || visuConfigFileSet_floatValue(&rgb[0], rgb[0], 0., 1.);
  res = res || visuConfigFileSet_floatValue(&rgb[1], rgb[1], 0., 1.);
  res = res || visuConfigFileSet_floatValue(&rgb[2], rgb[2], 0., 1.);
  res = res || visuConfigFileSet_floatValue(&rgb[3], rgb[3], 0., 1.);
  res = res || visuConfigFileSet_floatValue(&material[0], material[0], 0., 1.);
  res = res || visuConfigFileSet_floatValue(&material[1], material[1], 0., 1.);
  res = res || visuConfigFileSet_floatValue(&material[2], material[2], 0., 1.);
  res = res || visuConfigFileSet_floatValue(&material[3], material[3], 0., 1.);
  res = res || visuConfigFileSet_floatValue(&material[4], material[4], 0., 1.);
  if (res)
    {
      if (errorMessage)
	g_string_append_printf(errorMessage, _("WARNING! Parse error at line %d,"
					       " 8 floating point values (0 <= v"
					       " <= 1) must appear after the material"
					       " markup.\n"), position);
      return FALSE;
    }
  visuElementSet_allColorValues(ele, rgb, material);
  colorAdd_floatRGBA(rgb, (int*)0);
  
  return TRUE;
}
gboolean readRendered(gchar **lines, int nbLines,
		      int position, GString *errorMessage)
{
  char *cursor;
  VisuElement* ele;
  int res, errorFlag;
  int rendered;

  cursor = lines[0];
  ele = visuElementGet_fromValueLine(&cursor, -1, &errorFlag);
  if (!ele)
    {
      if (errorMessage)
	visuElementPrint_errorFromValueLine(errorMessage, position, errorFlag);
      return FALSE;
    }
  res = sscanf(cursor, "%d", &rendered);
  if (res != 1)
    {
      if (errorMessage)
	g_string_append_printf(errorMessage, _("WARNING! Parse error at line %d,"
						   " 1 integer value (0 or"
						   " 1) must appear after the"
						   " element_is_rendered markup.\n"),
			       position);
      return FALSE;
    }
  if (rendered)
    visuElementSet_rendered(ele, TRUE);
  else
    visuElementSet_rendered(ele, FALSE);
  
  return TRUE;
}

/* These functions read all the element list to export there associated resources. */
gboolean exportResourcesRenderingBase(GString *data, int *nbLinesWritten)
{
  GList *pos;

  g_string_append_printf(data, "# %s\n", DESC_RESOURCES_MAT);
  *nbLinesWritten = 1;
  pos = visuElementGet_allElements();
  while(pos)
    {
      g_string_append_printf(data, "%s:\n", FLAG_RESOURCES_MAT);
      g_string_append_printf(data, "    %s %4.3f %4.3f %4.3f %4.3f"
			     "   %4.2f %4.2f %4.2f %4.2f %4.2f\n",
			     ((VisuElement*)pos->data)->name,
			     ((VisuElement*)pos->data)->rgb[0],
			     ((VisuElement*)pos->data)->rgb[1],
			     ((VisuElement*)pos->data)->rgb[2],
			     ((VisuElement*)pos->data)->rgb[3],
			     ((VisuElement*)pos->data)->material[0],
			     ((VisuElement*)pos->data)->material[1],
			     ((VisuElement*)pos->data)->material[2],
			     ((VisuElement*)pos->data)->material[3],
			     ((VisuElement*)pos->data)->material[4]);
      *nbLinesWritten += 2;
      pos = g_list_next(pos);
    }
  g_string_append_printf(data, "# %s\n", DESC_RESOURCES_ELE_RENDERED);
  *nbLinesWritten = 1;
  pos = visuElementGet_allElements();
  while(pos)
    {
      if (!((VisuElement*)pos->data)->rendered)
	{
	  g_string_append_printf(data, "%s:\n", FLAG_RESOURCES_ELE_RENDERED);
	  g_string_append_printf(data, "    %s 0",
				 ((VisuElement*)pos->data)->name);
	  *nbLinesWritten += 2;
	}
      pos = g_list_next(pos);
    }
  g_string_append_printf(data, "\n");
  *nbLinesWritten += 1;

  return TRUE;
}

void freeHashValue(gpointer data)
{
  free(((VisuElement*)data)->name);
  free((VisuElement*)data);
}


void visuElementSet_property(VisuElement* element, char* key, gpointer value)
{
  if (!element || !key)
    {
      fprintf(stderr, "WARNING! 'visuElementSet_property' has been called with"
	      " a null element parameter or a null key parameter.\n");
      return;
    }
  DBG_fprintf(stderr, "Visu Element : set a new (key, value) to the element"
	      " '%s' : ('%s', %p).\n", element->name, key, value);
  g_hash_table_insert(element->properties, (gpointer)key, (gpointer)value);
}
gpointer visuElementGet_property(const VisuElement* element, char* key)
{
  if (!element || !key)
    {
      fprintf(stderr, "WARNING! 'visuElementGet_property' has been called with"
	      " a null element parameter or a null key parameter (%p, '%s').\n",
	      (gpointer)element, key);
      return (gpointer)0;
    }
  return g_hash_table_lookup(element->properties, (gpointer)key);
}
void visuElementRemove_property(VisuElement* element, char* key)
{
  if (!element || !key)
    {
      fprintf(stderr, "WARNING! 'visuElementRemove_property' has been called with"
	      " a null element parameter or a null key parameter.\n");
      return;
    }
  g_hash_table_remove(element->properties, (gpointer)key);
}

int visuElementSet_rendered(VisuElement *element, gboolean rendered)
{
  if (!element)
    {
      fprintf(stderr, "WARNING! 'visuElementSet_rendered' has been called"
	      " with a null element parameter.\n");
      return 0;
    }
  if (element->rendered == rendered)
    return 0;

  element->rendered = rendered;
  return 1;
}
gboolean visuElementGet_rendered(VisuElement *element)
{
  if (!element)
    {
      fprintf(stderr, "WARNING! 'visuElementGet_rendered' has been called"
	      " with a null element parameter.\n");
      return 0;
    }
  return element->rendered;
}


/* Debug : print all informations about all the stored VisuElements. */
void debug_AllVisuElements()
{
  fprintf(stderr,"DEBUG info : %d elements available.\n", g_hash_table_size(differentVisuElements));
  g_hash_table_foreach(differentVisuElements, (GHFunc)debug_VisuElement, (gpointer)0);
}

/* Debug : print all informations about the specified VisuElement. */
void debug_VisuElement(gpointer key, gpointer value, gpointer data)
{
  VisuElement *ele;

  ele = (VisuElement*)value;
  fprintf(stderr, "Element '%s'\n", ele->name);
  fprintf(stderr, "  | rgba [%f %f %f %f]\n", ele->rgb[0], ele->rgb[1], ele->rgb[2], ele->rgb[3]);
}
