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

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

#include <math.h>

#include <visu_tools.h>
#include <visu_data.h>
#include <visu_object.h>
#include <visu_configFile.h>
#include <openGLFunctions/objectList.h>

int listCylinder;

#define FLAG_RESOURCES_PAIR_RADIUS "pairCylinder_pairRadius"
#define DESC_RESOURCES_PAIR_RADIUS "This value is the radius for specific pairs drawn as cylinders ; element1 elemen2 0 < real < 10"
#define FLAG_RESOURCES_CYLINDER_RADIUS "pairCylinder_radius"
#define DESC_RESOURCES_CYLINDER_RADIUS "This value is the default radius of the pairs drawn as cylinders ; 0 < real < 10"
#define RESOURCES_CYLINDER_RADIUS_DEFAULT 0.1
float cylinderRadius;

#define FLAG_RESOURCES_CYLINDER_COLOR_TYPE "cylinder_colorType"
#define DESC_RESOURCES_CYLINDER_COLOR_TYPE "It chooses the colors of the cylinders according differents criterion ;"
#define RESOURCES_CYLINDER_COLOR_TYPE_DEFAULT cylinder_user_color
int cylinderColorType;

gboolean readCylinderDefaultRadius(gchar **lines, int nbLines,
				   int position, GString *errorMessage);
gboolean readCylinderRadius(gchar **lines, int nbLines,
			    int position, GString *errorMessage);
gboolean readCylinderColorType(gchar **lines, int nbLines,
			       int position, GString *errorMessage);
gboolean exportResourcesCylinder(GString *data, int *nbLinesWritten);


int setCylinderGeneralRadius(float val)
{
  DBG_fprintf(stderr, "Pairs Cylinder : set the general cylinder radius to %f.\n", val);
  if (val < RESOURCES_CYLINDER_RADIUS_MIN)
    val = RESOURCES_CYLINDER_RADIUS_MIN;
  if (val > RESOURCES_CYLINDER_RADIUS_MAX)
    val = RESOURCES_CYLINDER_RADIUS_MAX;

  if (val == cylinderRadius)
    return 0;

  setPairsAreOutOfDate();
  cylinderRadius = val;
  return 1;
}
float getCylinderGeneralRadius()
{
  return cylinderRadius;
}

int setCylinderRadius(PairsData *data, float val)
{
  float *radius;

  if (!data)
    return 0;

  DBG_fprintf(stderr, "Pairs Cylinder : set the cylinder radius of pair '%s-%s' to %f.\n",
	      data->name1, data->name2, val);
  if (val < RESOURCES_CYLINDER_RADIUS_MIN)
    val = RESOURCES_CYLINDER_RADIUS_MIN;
  if (val > RESOURCES_CYLINDER_RADIUS_MAX)
    val = RESOURCES_CYLINDER_RADIUS_MAX;

  radius = getPairsProperty(data, "radius");
  if (!radius)
    {
      radius = malloc(sizeof(float));
      if (!radius)
	{
	    allocationProblems();
	    exit(1);
	}
      setPairsProperty(data, "radius", (gpointer)radius);
      *radius = val;
      setPairsAreOutOfDate();
      return 1;
    }
  else
    if (*radius != val)
      {
	*radius = val;
	setPairsAreOutOfDate();
	return 1;
      }
  return 0;
}
float getCylinderRadius(PairsData *data)
{
  float *radius;

  if (!data)
    return -1.;

  radius = getPairsProperty(data, "radius");
  if (radius)
    return *radius;
  else
    return cylinderRadius;
}
int setCylinderColorType(int val)
{
  DBG_fprintf(stderr, "Pairs Cylinder : set the cylinder color type to %d.\n", val);
  if (val < 0 || val >= cylinder_nb_color)
    {
      fprintf(stderr, "WARNING! A call to setCylinderColorType has been"
	      " done with a wrong parameter (%d), val must be within 0 and %d.\n",
	      val, cylinder_nb_color);
      return 0;
    }

  cylinderColorType = val;
  setPairsAreOutOfDate();
  return 1;
}
int getCylinderColorType()
{
  return cylinderColorType;
}


void setColorAndWidthForCylinder(VisuElement *ele1, VisuElement *ele2, PairsData *data)
{
  float mm[4] = {0.0f, 0.0f, 0.0f, 1.0f};
  float amb = 0.5, dif = 0.5, shi = 0., spe = 0. , emi = 0.;
  Color *color;

  color = (Color*)getPairsProperty(data, "color");
  if (!color)
    return;

  switch (cylinderColorType)
    {
    case cylinder_user_color:
      mm[0] = amb * color->rgba[PAIRS_COLOR_R];
      mm[1] = amb * color->rgba[PAIRS_COLOR_G];
      mm[2] = amb * color->rgba[PAIRS_COLOR_B];
      glMaterialfv(GL_FRONT, GL_AMBIENT, mm);
      mm[0] = dif * color->rgba[PAIRS_COLOR_R];
      mm[1] = dif * color->rgba[PAIRS_COLOR_G];
      mm[2] = dif * color->rgba[PAIRS_COLOR_B];
      glMaterialfv(GL_FRONT, GL_DIFFUSE, mm);
      glMaterialf(GL_FRONT, GL_SHININESS, shi * 128.);
      mm[0] = spe;
      mm[1] = spe;
      mm[2] = spe;
      glMaterialfv(GL_FRONT, GL_SPECULAR, mm);
      mm[0] = emi * color->rgba[PAIRS_COLOR_R];
      mm[1] = emi * color->rgba[PAIRS_COLOR_G];
      mm[2] = emi * color->rgba[PAIRS_COLOR_B];
      glMaterialfv(GL_FRONT, GL_EMISSION, mm);
      break;
    case cylinder_element_color:
    default:
      break;
    }
  setPairsAreOutOfDate();
}

void drawCylinderPairs(VisuElement *ele1, VisuElement *ele2, PairsData *data,
		       OpenGLView *view, double x1, double y1, double z1,
		       double x2, double y2, double z2, float d2)
{
  int mat1, mat2, nlat;
  double vNorm[3]; /* vecteur normal aux vecteurs (0,0,1) et (x2-x1, y2-y1, z2-z1) */
  double vDest[3]; /* vecteur (x2-x1, y2-y1, z2-z1) */
  double cosAlpha; /* cosinus entre (0,0,1) et Vdest */
  double alpha;
  #define RADTODEG 57.29577951
  float radius, *tmpRad;
  GLUquadricObj *obj;


  tmpRad = getPairsProperty(data, "radius");
  if (tmpRad)
    radius = *tmpRad;
  else
    radius = cylinderRadius;
  nlat = OpenGLViewGet_numberOfFacettes(view, radius);
  vDest[0] = x2 - x1;
  vDest[1] = y2 - y1;
  vDest[2] = z2 - z1;
  if (vDest[0] != 0 || vDest[1] != 0)
    {
      vNorm[0] = - vDest[1];
      vNorm[1] = vDest[0];
      vNorm[2] = 0.;
      cosAlpha = sqrt((vDest[2] * vDest[2]) / d2);
      if (vDest[2] < 0.)
	cosAlpha = - cosAlpha;
      alpha = acos(cosAlpha) * RADTODEG;
    }
  else
    {
      vNorm[0] = 1.;
      vNorm[1] = 0.;
      vNorm[2] = 0.;
      if (vDest[2] < 0.)
	alpha = 180.;
      else
	alpha = 0.;
    }
  obj = gluNewQuadric();
  glPushMatrix();
  switch (cylinderColorType)
    {
    case cylinder_user_color:
      glTranslated(x1, y1, z1);
      glRotated(alpha, vNorm[0], vNorm[1], vNorm[2]);
      gluCylinder(obj, (GLdouble)radius, (GLdouble)radius,
		  (GLdouble)sqrt(d2), (GLint)nlat, (GLint)1);
      break;
    case cylinder_element_color:
      mat1 = visuElementGet_identifierMaterial(ele1);
      mat2 = visuElementGet_identifierMaterial(ele2);
      if (mat1 <= 0 || mat2 <= 0)
	g_warning("Can't draw cylinders because either ele1"
		  "or ele2 has no identifier for material.\n");
      glTranslated(x1, y1, z1);
      glRotated(alpha, vNorm[0], vNorm[1], vNorm[2]);
      glCallList(mat1);
      gluCylinder(obj, (GLdouble)radius, (GLdouble)radius,
		  (GLdouble)sqrt(d2) / 2., (GLint)nlat, (GLint)1);
      glPopMatrix();
      glPushMatrix();
      glTranslated(x2, y2, z2);
      glRotated(alpha - 180., vNorm[0], vNorm[1], vNorm[2]);
      glCallList(mat2);
      gluCylinder(obj, (GLdouble)radius, (GLdouble)radius,
		  (GLdouble)sqrt(d2) / 2., (GLint)nlat, (GLint)1);
      break;
    default:
      break;
    }
  glPopMatrix();
}

PairsExtension* initPairsCylinder()
{
  char *name = _("Cylinder pairs");
  char *desc = _("Pairs are rendered by cylinders."
		 " The color and the width can by chosen.");
  PairsExtension *extension;
  VisuConfigFileEntry *resourceEntry;

  extension = pairsExtension_new("Cylinder pairs", name, desc, 1,
				 (initEndOpenGlPairsFunc)0, (initEndOpenGlPairsFunc)0,
				 setColorAndWidthForCylinder, (startEndPairsFunc)0,
				 drawCylinderPairs);

  resourceEntry = visuConfigFileAdd_entry(VISU_CONFIGFILE_RESOURCE,
					  FLAG_RESOURCES_CYLINDER_COLOR_TYPE,
					  DESC_RESOURCES_CYLINDER_COLOR_TYPE,
					  1, readCylinderColorType);
  resourceEntry = visuConfigFileAdd_entry(VISU_CONFIGFILE_RESOURCE,
					  FLAG_RESOURCES_CYLINDER_RADIUS,
					  DESC_RESOURCES_CYLINDER_RADIUS,
					  1, readCylinderDefaultRadius);
  resourceEntry = visuConfigFileAdd_entry(VISU_CONFIGFILE_RESOURCE,
					  FLAG_RESOURCES_PAIR_RADIUS,
					  DESC_RESOURCES_PAIR_RADIUS,
					  1, readCylinderRadius);
  visuConfigFileAdd_exportFunction(VISU_CONFIGFILE_RESOURCE,
				   exportResourcesCylinder);


  listCylinder = openGLObjectList_new(2);

  cylinderRadius = RESOURCES_CYLINDER_RADIUS_DEFAULT;
  cylinderColorType = cylinder_user_color;

  pointerToPairExtension_cylinder = extension;
  return extension;
}


/*****************************************/
/* Dealing with parameters and resources */
/*****************************************/

gboolean readCylinderDefaultRadius(gchar **lines, int nbLines,
				   int position, GString *errorMessage)
{
  int res;
  float radius;

  res = sscanf(lines[0], "%f", &radius);
  if (res != 1 || radius < RESOURCES_CYLINDER_RADIUS_MIN ||
      radius > RESOURCES_CYLINDER_RADIUS_MAX)
    {
      if (errorMessage)
	g_string_append_printf(errorMessage, _("WARNING! Parse error at line %d,"
					       " 1 real value must appear after"
					       " the %s markup.\n"),
			       position, FLAG_RESOURCES_CYLINDER_RADIUS);
      setCylinderGeneralRadius(RESOURCES_CYLINDER_RADIUS_DEFAULT);
      return FALSE;
    }
  setCylinderGeneralRadius(radius);
  return TRUE;
}
gboolean readCylinderRadius(gchar **lines, int nbLines,
			    int position, GString *errorMessage)
{
  int res, errorFlag;
  float radius;
  char *cursor;
  VisuElement* ele1, *ele2;
  PairsData *data;

  cursor = lines[0];
  ele1 = visuElementGet_fromValueLine(&cursor, -1, &errorFlag);
  if (!ele1 )
    {
      if (errorMessage)
	visuElementPrint_errorFromValueLine(errorMessage, position, errorFlag);
      return FALSE;
    }
  ele2 = visuElementGet_fromValueLine(&cursor, -1, &errorFlag);
  if (!ele2 )
    {
      if (errorMessage)
	visuElementPrint_errorFromValueLine(errorMessage, position, errorFlag);
      return FALSE;
    }
  data = getPairsData(ele1, ele2);
  if (!data)
    {
      if (errorMessage)
	g_string_append_printf(errorMessage, _("WARNING! unexpected internal error.\n"));
      g_warning("INTERNAL ERROR! Can't find the VisuData associated to"
		" elements '%s' and '%s'.\n", ele1->name, ele2->name);
      return FALSE;
    }
  res = sscanf(cursor, "%f", &radius);
  if (res != 1 || radius < RESOURCES_CYLINDER_RADIUS_MIN ||
      radius > RESOURCES_CYLINDER_RADIUS_MAX)
    {
      if (errorMessage)
	g_string_append_printf(errorMessage, _("WARNING! Parse error at line %d,"
					       " 1 real value must appear after"
					       " the %s markup.\n"),
			       position, FLAG_RESOURCES_PAIR_RADIUS);
      setCylinderRadius(data, RESOURCES_CYLINDER_RADIUS_DEFAULT);
      return FALSE;
    }
  setCylinderRadius(data, radius);
  return TRUE;
}
gboolean readCylinderColorType(gchar **lines, int nbLines,
			       int position, GString *errorMessage)
{
  int res, val;

  res = sscanf(lines[0],"%d", &val);
  if (res != 1 || val < 0 || val >= cylinder_nb_color)
    {
      if (errorMessage)
	g_string_append_printf(errorMessage, _("WARNING! Parse error at line %d,"
					       " 1 integer value must appear after"
					       " the %s markup.\n"),
			       position, FLAG_RESOURCES_CYLINDER_COLOR_TYPE);
      setCylinderColorType(RESOURCES_CYLINDER_COLOR_TYPE_DEFAULT);
      return FALSE;
    }
  setCylinderColorType(val);

  return TRUE;
}
void exportPairsRadius(PairsData *data, gpointer userData)
{
  struct foreachFuncExport_struct *str;
  float *radius;

  radius = (float*)getPairsProperty(data, "radius");
  if (radius)
    {
      str = ( struct foreachFuncExport_struct*)userData;
      g_string_append_printf(str->data, "%s:\n    %s %s %f\n", FLAG_RESOURCES_PAIR_RADIUS,
			     data->name1, data->name2, *radius);
      *str->nbLinesWritten += 2;
    }
}
gboolean exportResourcesCylinder(GString *data, int *nbLinesWritten)
{
  struct foreachFuncExport_struct str;

  g_string_append_printf(data, "# %s 0 <= integer < %d\n", DESC_RESOURCES_CYLINDER_COLOR_TYPE,
			 cylinder_nb_color);
  g_string_append_printf(data, "%s:\n    %d\n",
			 FLAG_RESOURCES_CYLINDER_COLOR_TYPE,
			 cylinderColorType);
  g_string_append_printf(data, "# %s\n", DESC_RESOURCES_CYLINDER_RADIUS);
  g_string_append_printf(data, "%s:\n    %f\n",
			 FLAG_RESOURCES_CYLINDER_RADIUS, cylinderRadius);
  *nbLinesWritten = 6;
  str.data = data;
  str.nbLinesWritten = nbLinesWritten;
  g_string_append_printf(data, "# %s\n", DESC_RESOURCES_PAIR_RADIUS);
  foreachPairsData(exportPairsRadius, &str);
  g_string_append_printf(data, "\n");
  *nbLinesWritten += 2;

  return TRUE;
}
