/*   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 "axes.h"
#include "../renderingMethods/renderingAtomic.h"
#include "../renderingMethods/renderingSpin.h"

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

#include <opengl.h>
#include <openGLFunctions/text.h>
#include <visu_object.h>
#include <visu_tools.h>
#include <visu_configFile.h>
#include <OSOpenGL/visu_OSInterface.h>
#include <renderingBackend/visu_windowInterface.h>
#include <openGLFunctions/objectList.h>

#include <math.h>

/* Parameters & resources*/
/* This is a boolean to control is the axes is render or not. */
#define FLAG_RESOURCE_AXES_USED   "axes_are_on"
#define DESC_RESOURCE_AXES_USED   "Control if the axes are drawn ; boolean (0 or 1)"
#define RESOURCE_AXES_USED_DEFAULT 0
gboolean readAxesAreOn(gchar **lines, int nbLines,
		       int position, GString *errorMessage);
/* A resource to control the color used to render the lines of the axes. */
#define FLAG_RESOURCE_AXES_COLOR   "axes_color"
#define DESC_RESOURCE_AXES_COLOR   "Define the color of the axes ; three floating point values (0. <= v <= 1.)"
float axesRGB[3];
gboolean readAxesColor(gchar **lines, int nbLines,
		       int position, GString *errorMessage);
/* A resource to control the width to render the lines of the axes. */
#define FLAG_RESOURCE_AXES_LINE   "axes_line_width"
#define DESC_RESOURCE_AXES_LINE   "Define the width of the lines of the axes ; one floating point values (1. <= v <= 10.)"
#define RESOURCE_AXES_LINE_DEFAULT 1.
float axesLineWidth;
gboolean readAxesLineWidth(gchar **lines, int nbLines,
			   int position, GString *errorMessage);

/* Export function that is called by visu_module to write the
   values of resources to a file. */
gboolean exportResourcesAxes(GString *data, int *nbLinesWritten);

static float axesRGBDefault[3] = {1.0, 0.5, 0.1};

void rebuildAxes(VisuData *dataObj);

/* This flag can be set internally to specify that axes should be recreated. */
gboolean axesHaveBeenBuilt;

/* Local callbacks */
static void onAxesParametersChange(VisuData *dataObj, OpenGLView *view, gpointer data);
static void onAxesConfigChange(GObject *visu, VisuData *dataObj, gpointer data);
static void rebuildAxesOnResourcesLoaded(GObject *visu, gpointer data);
static void onDataReadySignal(GObject *visu, VisuData *dataObj, gpointer data);

OpenGLExtension* initExtensionAxes()
{
  char *name = "Axes";
  char *description = _("Draw the axes at the bottom right of the rendering area.");
  int listAxes;
  VisuConfigFileEntry *resourceEntry;

  DBG_fprintf(stderr,"Initialising the axes OpenGL extension...\n");
  listAxes = openGLObjectList_new(1);
  extensionAxes = OpenGLExtension_new(name, description, listAxes, rebuildAxes);

  resourceEntry = visuConfigFileAdd_entry(VISU_CONFIGFILE_RESOURCE,
					  FLAG_RESOURCE_AXES_USED,
					  DESC_RESOURCE_AXES_USED,
					  1, readAxesAreOn);
  resourceEntry = visuConfigFileAdd_entry(VISU_CONFIGFILE_RESOURCE,
					  FLAG_RESOURCE_AXES_COLOR,
					  DESC_RESOURCE_AXES_COLOR,
					  1, readAxesColor);
  resourceEntry = visuConfigFileAdd_entry(VISU_CONFIGFILE_RESOURCE,
					  FLAG_RESOURCE_AXES_LINE,
					  DESC_RESOURCE_AXES_LINE,
					  1, readAxesLineWidth);
  visuConfigFileAdd_exportFunction(VISU_CONFIGFILE_RESOURCE,
				   exportResourcesAxes);

  /* Initialisation des valeurs par défaut. */
  axesSet_RGBValues(axesRGBDefault, MASK_RGB_ALL);
  extensionAxes->used = RESOURCE_AXES_USED_DEFAULT;
  OpenGLExtensionSet_priority(extensionAxes, OPENGL_EXTENSION_PRIORITY_LAST);
  OpenGLExtensionSet_saveOpenGLState(extensionAxes, TRUE);
  axesLineWidth = RESOURCE_AXES_LINE_DEFAULT;

  g_signal_connect(G_OBJECT(visu), "dataReadyForRendering",
		   G_CALLBACK(onDataReadySignal), (gpointer)0);

  g_signal_connect(G_OBJECT(visu), "SpinAxes",
 		   G_CALLBACK(onAxesConfigChange), (gpointer)0);
  g_signal_connect(G_OBJECT(visu), "resourcesLoaded",
 		   G_CALLBACK(rebuildAxesOnResourcesLoaded), (gpointer)0);


  axesHaveBeenBuilt = FALSE;
  
  return extensionAxes;
}

/* Method used to change the value of the parameter axes_color. */
int axesSet_RGBValues(float rgb[3], int mask)
{
  int diff = 0;
  
  if (mask & MASK_RGB_R && axesRGB[0] != rgb[0])
    {
      axesRGB[0] = rgb[0];
      diff = 1;
    }
  if (mask & MASK_RGB_G && axesRGB[1] != rgb[1])
    {
      axesRGB[1] = rgb[1];
      diff = 1;
    }
  if (mask & MASK_RGB_B && axesRGB[2] != rgb[2])
    {
      axesRGB[2] = rgb[2];
      diff = 1;
    }
  if (!diff)
    return 0;

  /* Il faut recréer la boîte puisque des param ont changés. */
  axesHaveBeenBuilt = FALSE;
/*   axesDraw((GObject*)0, (gpointer)0); */
  return extensionAxes->used;
}

/* Method used to change the value of the parameter axes_line_width. */
int axesSet_lineWidth(float width)
{
  if (width < 1. || width > 10. || width == axesLineWidth)
    return 0;

  axesLineWidth = width;
  /* Il faut recréer la boîte puisque des param ont changés. */
  axesHaveBeenBuilt = FALSE;
/*   axesDraw((GObject*)0, (gpointer)0); */
  return extensionAxes->used;
}

/* Method used to change the value of the parameter axes_is_on. */
int axesSet_areOn(int value)
{
  if (value == extensionAxes->used)
    return 0;

  extensionAxes->used = value;
  return (value && !axesHaveBeenBuilt);
}

/* Get methods. */
float axesGet_redValue()
{
  return axesRGB[0];
}

float axesGet_greenValue()
{
  return axesRGB[1];
}

float axesGet_blueValue()
{
  return axesRGB[2];
}

int axesGet_areOn()
{
  return extensionAxes->used;
}

float axesGet_lineWidth()
{
  return axesLineWidth;
}


/****************/
/* Private part */
/****************/

void rebuildAxes(VisuData *dataObj)
{
  openGLText_rebuildFontList();
  /* Set flag to false to force recreation of axes. */
  axesHaveBeenBuilt = FALSE;
  axesDraw(dataObj);
}
static void rebuildAxesOnResourcesLoaded(GObject *visu, gpointer data)
{
  GList *tmpLst;

  tmpLst = visuDataGet_allObjects();
  while (tmpLst)
    {
      axesDraw(VISU_DATA(tmpLst->data));
      tmpLst = g_list_next(tmpLst);
    }
}
static void onDataReadySignal(GObject *visu, VisuData *dataObj, gpointer data)
{
  if (!dataObj)
    return;

  g_signal_connect(G_OBJECT(dataObj), "OpenGLNearFar",
		   G_CALLBACK(onAxesParametersChange), (gpointer)0);
  g_signal_connect(G_OBJECT(dataObj), "OpenGLWidthHeight",
		   G_CALLBACK(onAxesParametersChange), (gpointer)0);

  axesHaveBeenBuilt = FALSE;
  axesDraw(dataObj);
}

/**
 * drawAxes
 * @length: the length of the axes
 * @width: the width of the line used to draw the axes
 * @long_axes:
 *
 * Draw three axes following the current x,y,z directions. Their @length and
 * @width can be specified.
 */
void drawAxes(float length, GLsizei w, GLsizei h, float width,
	      char *legend, gboolean long_axes) 
{
  glLineWidth(width);
  glColor3f(axesRGB[0], axesRGB[1], axesRGB[2]);
  glBegin(GL_LINES);
  glVertex3f(long_axes ? -length : 0, 0.0f, 0.0f); glVertex3f(length, 0.0f, 0.0f);
  glVertex3f(0.0f, long_axes ? -length : 0, 0.0f); glVertex3f(0.0f, length, 0.0f);
  glVertex3f(0.0f, 0.0f, long_axes ? -length : 0); glVertex3f(0.0f, 0.0f, length);
  glEnd();

  glRasterPos3f(length, 0.0f, 0.0f); openGLText_drawChars("x"); 
  glRasterPos3f(0.0f, length, 0.0f); openGLText_drawChars("y"); 
  glRasterPos3f(0.0f, 0.0f, length); openGLText_drawChars("z");

  if(legend != NULL)
    {
      glMatrixMode (GL_PROJECTION);
      glPushMatrix();
      glLoadIdentity ();  
      gluOrtho2D (0, MIN(w,h), 0, MIN(h,w));
      glMatrixMode (GL_MODELVIEW);
      glPushMatrix();
      glLoadIdentity ();  
      glRasterPos3f(20., 5., 0.9); openGLText_drawChars(legend);
      glPopMatrix();
      glMatrixMode(GL_PROJECTION);
      glPopMatrix();
      glMatrixMode(GL_MODELVIEW);
    }
}

/**
 * draw_coloured_cone:
 * @r: the radius of the cone
 * @h: the semi-height of the cone
 * @n: the precision used to draw the sphere
 *
 * Draws a coloured double cone at the given position.
 */ 
void draw_coloured_cone(double r, double h, int n, float phi_prime_zero)
{
  float hsv[3], rgb[3];
  int i,j;
  double theta1,theta2,theta3;
  float e_x, e_y, e_z, p_x, p_y, p_z;

  g_return_if_fail(r >= 0 && n >= 0);
  
  if (n < 4 || r <= 0) 
    {
      glBegin(GL_POINTS);
      glVertex3f(0,0,0);
      glEnd();
      return;
    }

  glFrontFace(GL_CW);

  glPushMatrix();
  glRotatef(phi_prime_zero, 0, 0, 1);

  glRotatef(-90, 1, 0, 0);

#define TWOPI 6.283185307179586
#define PID2 1.570796326794897

  hsv[1] = 0;
  hsv[2] = 1;

  for (j=0;j<n/2;j++) 
    {
      theta1 = j * TWOPI / n - PID2;
      theta2 = (j + 1) * TWOPI / n - PID2;
      
      glBegin(GL_QUAD_STRIP);
      for (i=0;i<=n;i++) 
	{
	  theta3 = i * TWOPI / n;
	  
	  hsv[0] = /*1-*/(float)i/(float)n;

	  hsv[1] = 2*(float)(1+j)/(float)(n/2);
	  if(hsv[1] > 1) hsv[1] = 1;
      
	  hsv[2] = 2-2*(float)(1+j)/(float)(n/2); 
	  if(hsv[2] > 1) hsv[2] = 1; 

	  e_x = /*cos(theta2) **/hsv[1]*hsv[2]* cos(theta3);
	  e_y = sin(theta2);
	  e_z = /*cos(theta2) **/hsv[1]*hsv[2]* sin(theta3);
	  p_x = r * e_x;
	  p_y = /*r * e_y*/h*(hsv[1] - hsv[2]);
	  p_z = r * e_z;
	  
	  HSVtoRGB(rgb, hsv);
	  glColor3f(rgb[0], rgb[1], rgb[2]); 
	  glNormal3f(e_x,e_y,e_z);
	  glVertex3f(p_x,p_y,p_z);
	  
	  hsv[0] = /*1-*/(float)i/(float)n;

	  hsv[1] = 2*(float)j/(float)(n/2);
	  if(hsv[1] > 1) hsv[1] = 1;
      
	  hsv[2] = 2-2*(float)j/(float)(n/2); 
	  if(hsv[2] > 1) hsv[2] = 1; 

	  e_x = /*cos(theta1) **/hsv[1]*hsv[2]* cos(theta3);
       	  e_y = sin(theta1);
	  e_z = /*cos(theta1) **/hsv[1]*hsv[2]* sin(theta3);
	  p_x = r * e_x;
	  p_y = /*r * e_y*/h*(hsv[1] - hsv[2]);
	  p_z = r * e_z;
	
	  HSVtoRGB(rgb, hsv);
	  glColor3f(rgb[0], rgb[1], rgb[2]); 
	  glNormal3f(e_x,e_y,e_z);
	  glVertex3f(p_x,p_y,p_z);

	}
      glEnd();
    }
  glPopMatrix();
  glFrontFace(GL_CCW);
  
}

/* void drawConeCircle(/\*XYZ c,*\/ double r, int n)  */
/* { */
/*   /\*  XYZ e,p;*\/ */
/*   GLboolean antialiasing_was_on; */
/*   GLUquadric* trash = gluNewQuadric(); */
/*   int i; */
/*   double theta, height = 1; */

/* /\*   glEnable (GL_POLYGON_SMOOTH); *\/ */
/* /\*   glDisable (GL_DEPTH_TEST); *\/ */

/*   antialiasing_was_on = enableGlFeature(GL_LINE_SMOOTH); */
/*   glPushMatrix(); */
/*   glRotatef(90, 1, 0, 0); */
/*   glTranslatef(0, 0, -height/2); */
/*   glLineWidth(axesLineWidth); */
/*   glColor3f(0, 0, 0);  */
/*   gluQuadricOrientation(trash, GLU_INSIDE); */
/*   gluCylinder(trash, 0.95*r, 0.95*r, height, n, 1); */
/* /\*   glBegin(GL_LINE_LOOP); *\/ */
/* /\*   for (i=0;i<=n;i++)  *\/ */
/* /\*     { *\/ */
/* /\*       theta = i * TWOPI / n; *\/ */
/* /\*       e.x = cos(theta); *\/ */
/* /\*       e.z = sin(theta); *\/ */
/* /\*       p.x = 0.8*(c.x +  r * e.x); *\/ */
/* /\*       p.z = 0.8*(c.z +  r * e.z); *\/ */
/* /\*       glVertex3f(p.x, -0.6, p.z); *\/ */
/* /\*       glVertex3f(p.x, 0.6, p.z); *\/ */
/* /\*     } *\/ */
/* /\*   glEnd(); *\/ */
/*   glPopMatrix(); */
/*   restoreGlFeature(GL_LINE_SMOOTH, antialiasing_was_on); */
/* /\*   glEnable (GL_DEPTH_TEST); *\/ */
/* /\*   glDisable(GL_POLYGON_SMOOTH); *\/ */

/* } */
static void onAxesConfigChange(GObject *visu, VisuData *dataObj, gpointer data)
{
  g_return_if_fail(dataObj);

  onAxesParametersChange(dataObj, visuDataGet_openGLView(dataObj), data);
}

static void onAxesParametersChange(VisuData *dataObj, OpenGLView *view, gpointer data)
{
  axesHaveBeenBuilt = FALSE;
  axesDraw(dataObj);
}

void axesDraw(VisuData *dataObj)
{
  float length;
  GLsizei w, h;
  GLint xx, yy;
  double mini;
  double maxi;
  GLUquadric *trash = gluNewQuadric();
  OpenGLView *view;

  /* Nothing to draw; */
  if(!axesGet_areOn() || axesHaveBeenBuilt) return;

  /* Nothing to draw if no data is associated to
     the current rendering window. */
  if (!dataObj)
    return;

  DBG_fprintf(stderr, "Extension axes : creating axes.\n");

  view = visuDataGet_openGLView(dataObj);
  length = 0.5f*view->box->extens/1.91f;
  w = 0.2*view->window->width;
  h = 0.2*view->window->height;
  xx = view->window->width - w;
  yy = 0;
  mini = -0.5 * view->box->extens/view->camera->gr;
  maxi = -mini;

  axesHaveBeenBuilt = TRUE;

  openGLText_initFontList();
  
  glDeleteLists(extensionAxes->objectListId, 1);

  glNewList(extensionAxes->objectListId, GL_COMPILE);

  glEnable(GL_CULL_FACE);

  /* Désactivation de la lumière et du brouillard et activation du culling. */
  glDisable(GL_LIGHTING);
  glDisable(GL_FOG);

  /* Resetting the depth buffer. */
  glClear(GL_DEPTH_BUFFER_BIT);

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glFrustum(mini, maxi, mini, maxi, view->window->near, view->window->far);

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glViewport(xx, yy, w, h);

  if(pointerOnRenderingSpinMethod == getRenderingMethodInUse())
    {
      float *color_wheel_angle = rspin_global_resource_get("color_wheel_angle");
      float phi_prime_zero = (color_wheel_angle != NULL) ? *color_wheel_angle : 0;
      float *cone_phi_angle = rspin_global_resource_get("cone_phi_angle");
      float phi_a = (cone_phi_angle != NULL) ? *cone_phi_angle : 0;
      float *cone_theta_angle = rspin_global_resource_get("cone_theta_angle");
      float theta_a = (cone_theta_angle != NULL) ? *cone_theta_angle : 0;

      /* Draw the first color cone */
      glPushMatrix();
      glRotatef(phi_a, 0, 0, 1);
      glRotatef(theta_a, 0, 1, 0);
      draw_coloured_cone(length, 1.2*length, 16, phi_prime_zero);
      glPopMatrix();

      drawAxes(1.5*length, w, h, axesLineWidth, "front", TRUE);

      glViewport(xx, yy+h, w, h);

      /* Enabling front culling and drawing the second color cone */
      glPushMatrix();
      glRotatef(phi_a, 0, 0, 1);
      glRotatef(theta_a, 0, 1, 0);
      glCullFace(GL_FRONT);
      draw_coloured_cone(length, 1.2*length, 16, phi_prime_zero);    
      glCullFace(GL_BACK);
      glPopMatrix();

      drawAxes(1.5*length, w, h, axesLineWidth, "back", TRUE);
    }
  else if(pointerOnRenderingAtomicMethod == getRenderingMethodInUse())
    {
/*       length = 0.5f*view->box->extens/1.91f; */
      drawAxes(length, w, h, axesLineWidth, NULL, FALSE);
    }

  /* Back to the main viewport. */
  glViewport(0, 0, view->window->width, view->window->height);

  glPopMatrix();

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  glMatrixMode(GL_MODELVIEW);

  glEndList();

}

/* Parameters & resources*/

/* This is a boolean to control is the axes is render or not. */
gboolean readAxesAreOn(gchar **lines, int nbLines,
		       int position, GString *errorMessage)
{
  int res, val;
/*   VisuData *data; */

/*   if (visuRenderingWindowGet_current()) */
/*     data = visuRenderingWindowGet_visuData(visuRenderingWindowGet_current()); */
/*   else */
/*     data = (VisuData*)0; */
  res = sscanf(lines[0],"%d", &val);
  if (res != 1)
    {
      if (errorMessage)
	g_string_append_printf(errorMessage, _("WARNING! Parse error at line %d,"
					       " 1 boolean value must appear"
					       " after the %s markup.\n"),
			       position, FLAG_RESOURCE_AXES_USED);
      axesSet_areOn(RESOURCE_AXES_USED_DEFAULT);
      return FALSE;
    }

  axesSet_areOn(val);
  return TRUE;
}
/* A resource to control the color used to render the lines of the axes. */
gboolean readAxesColor(gchar **lines, int nbLines,
		       int position, GString *errorMessage)
{
  int res;
  float rgb[3];
  
  res = sscanf(lines[0],"%f %f %f", &rgb[0], &rgb[1], &rgb[2]);
  if (res != 3 ||
      visuConfigFileSet_floatValue(&rgb[0], rgb[0], 0., 1.) ||
      visuConfigFileSet_floatValue(&rgb[1], rgb[1], 0., 1.) ||
      visuConfigFileSet_floatValue(&rgb[2], rgb[2], 0., 1.))
    {
      if (errorMessage)
	g_string_append_printf(errorMessage, _("WARNING! Parse error at line %d, 3 floating "
					       "point values (0 <= v <= 1) must appear after"
					       " the %s markup.\n"),
			       position, FLAG_RESOURCE_AXES_COLOR);
      axesSet_RGBValues(axesRGBDefault, MASK_RGB_ALL);
      return FALSE;
    }
  axesSet_RGBValues(rgb, MASK_RGB_ALL);

  return TRUE;
}
/* A resource to control the width to render the lines of the axes. */
gboolean readAxesLineWidth(gchar **lines, int nbLines,
			   int position, GString *errorMessage)
{
  int res;
  float width;
  
  res = sscanf(lines[0],"%f", &width);
  if (res != 1 ||
      visuConfigFileSet_floatValue(&width, width, 1., 10.))
    {
      if (errorMessage)
	g_string_append_printf(errorMessage, _("WARNING! Parse error at line %d, 1 floating "
					       "point value (1 <= v <= 10) must appear after"
					       " the %s markup.\n"),
			       position, FLAG_RESOURCE_AXES_COLOR);
      axesSet_lineWidth(RESOURCE_AXES_LINE_DEFAULT);
      return FALSE;
    }
  axesSet_lineWidth(width);

  return TRUE;
}

/* Export function that is called by visu_module to write the
   values of resources to a file. */
gboolean exportResourcesAxes(GString *data, int *nbLinesWritten)
{
  g_string_append_printf(data, "# %s\n", DESC_RESOURCE_AXES_USED);
  g_string_append_printf(data, "%s:\n", FLAG_RESOURCE_AXES_USED);
  g_string_append_printf(data, "    %d\n", extensionAxes->used);
  g_string_append_printf(data, "# %s\n", DESC_RESOURCE_AXES_COLOR);
  g_string_append_printf(data, "%s:\n", FLAG_RESOURCE_AXES_COLOR);
  g_string_append_printf(data, "    %4.3f %4.3f %4.3f\n",
	  axesRGB[0], axesRGB[1], axesRGB[2]);
  g_string_append_printf(data, "# %s\n", DESC_RESOURCE_AXES_LINE);
  g_string_append_printf(data, "%s:\n", FLAG_RESOURCE_AXES_LINE);
  g_string_append_printf(data, "    %4.0f\n", axesLineWidth);
  g_string_append_printf(data, "\n");
  *nbLinesWritten += 10;

  return TRUE;
}
