/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Aurélien LHERBIER,
	laboratoire L_Sim, (2001-2005)

	Adresse mèl :
	LHERBIER, aurelien P lherbier A 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 : Aurélien LHERBIER,
	laboratoire L_Sim, (2001-2005)

	E-mail addresses :
	LHERBIER, aurelien D lherbier AT cea D 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 <string.h>

#include <GL/gl.h>

#include <visu_tools.h>
#include "isoline.h"

/**
 * SECTION:isoline
 * @short_description: handle the drawing and the computation of
 * isolines.
 *
 * <para>TODO</para>
 */

/**
 * VisuLine:
 *
 * Structure representing a curved line in 3D, opaque structure.
 */
struct _VisuLine
{
  guint refCount;

  guint num_sublines, num_vertices;
  float **vertex_3dpos;

  double value;
};

static gboolean Create_line(int *iTab, int ntriangles,
                            double *xline, double *yline, double *zline,
                            VisuLine **isoline);

static int edgeTable[8] =
{
0x0  , 0x0, 0x0, 0x1, 0x0, 0x2, 0x3, 0x4
};

static int linTable[5][5] =
{
  {-1, -1, -1, -1, -1},
  { 0,  1, -1, -1, -1},
  { 0,  2, -1, -1, -1},
  { 1,  2, -1, -1, -1},
  { 0,  1,  2,  0, -1}
};

/******************************************************************************/
/******************************************************************************/

/* DEVELOPMENT AREA
   Methods are copied to be callable without all static stuff everywhere. */

/**
 * visu_line_free:
 * @line: a set of lines.
 *
 * Free the line object.
 *
 * Since: 3.4
 */
void visu_line_free(VisuLine *line)
{
  g_return_if_fail(line);

  DBG_fprintf(stderr, "Line: free line %p.\n", (gpointer)line);
  if(line->vertex_3dpos)
    {
      g_free(line->vertex_3dpos[0]);
      g_free(line->vertex_3dpos);
      line->vertex_3dpos = (float**)0;
    }
  DBG_fprintf(stderr, " | Ok.\n");
}

/**
 * visu_line_get_type:
 * @void: 
 *
 * Create and retrieve a #GType for a #VisuLine object.
 *
 * Since: 3.7
 *
 * Returns: a new type for #VisuLine structures.
 **/
GType visu_line_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("VisuLine", 
                                   (GBoxedCopyFunc)visu_line_ref,
                                   (GBoxedFreeFunc)visu_line_unref);
  return g_define_type_id;
}
/**
 * visu_line_ref:
 * @line: a #VisuLine object.
 *
 * Increase the ref counter.
 *
 * Since: 3.7
 *
 * Returns: itself.
 **/
VisuLine* visu_line_ref(VisuLine *line)
{
  line->refCount += 1;
  return line;
}
/**
 * visu_line_unref:
 * @line: a #VisuLine object.
 *
 * Decrease the ref counter, free all memory if counter reachs zero.
 *
 * Since: 3.7
 **/
void visu_line_unref(VisuLine *line)
{
  line->refCount -= 1;
  if (line->refCount)
    return;
  visu_line_free(line);
  g_free(line);
}

/**
 * visu_line_newFromTriangles:
 * @data: the lines to be computed ;
 * @nTriangles: the surface to compute isoline from ;
 * @isoValue: the value of the computed isoline.
 *
 * Create on the fly an isoline from a given set of triangles. If the
 * lines are created, @isoline will be allocated and should be freed
 * with visu_line_free() after use.
 *
 * Since: 3.6
 *
 * Returns: the newly allocated #VisuLine or NULL.
 */
VisuLine* visu_line_newFromTriangles(float **data, guint nTriangles, double isoValue)
{
  /* Local variables */
  VisuLine *isoline_;
  guint j, n;
  int p, q;
  int nPoints, nedges;
  int *iTab;
  /* give edge index in the full edge list with edge j of a given triangle i as input*/
  /* j in [0,1,2] and i in [0,ntriangles-1]*/
  float fac, isov;
  double val0, val1;
  double *xline, *yline, *zline;
  float *coord0, *coord1; /* local vertex coordinates */

  gboolean status;
#if DEBUG == 1
  GTimer *timer;
  gulong fractionTimer;
#endif

  /* Routine code*/
  isov = isoValue;

  /* Ensure that the pointer is not zero */

  g_return_val_if_fail(data, (VisuLine*)0);

  DBG_fprintf(stderr, "Line: compute isoline at value %g from %d triangles.\n",
              isoValue, nTriangles);

#if DEBUG == 1
  timer = g_timer_new();
  g_timer_start(timer);
#endif

  /* Allocate memory*/
  nedges = 3 * nTriangles;
  iTab   = g_malloc(nedges * sizeof(int));
  xline  = g_malloc(nedges * sizeof(double));
  yline  = g_malloc(nedges * sizeof(double));
  zline  = g_malloc(nedges * sizeof(double));

  /* Glance trough the list of edges and search intersection
     between edges and VisuScalarField */
  nPoints = 0;
  for(n = 0; n < nTriangles; n++)
    {
      /* Get the 3D coordinates and scalar field value of each vertex
	 (0,1) of the current edge*/
      for(j=0; j<3; j++)
	{
	  /* Get the xyz coordinates of the 2 vertices of the current
	     edge of the current triangle*/
	  p=j;
	  q=(j == 2)?0:j+1;
	  val0 = data[2 * n + 1][p];
	  val1 = data[2 * n + 1][q];

	  /* Test if edge contains isovalue */
	  if((isov - val0 <  0.0 && isov - val1 >= 0.0) ||
	     (isov - val0 >= 0.0 && isov - val1 <  0.0))
	    {
	      fac = (isov - val0) / (val1 - val0);
	      coord0 = data[2 * n + 0] + 3 * p;
	      coord1 = data[2 * n + 0] + 3 * q;
	      iTab[3 * n + j] = nPoints;
	      xline[nPoints] = coord0[0] + fac * (coord1[0]-coord0[0]);
	      yline[nPoints] = coord0[1] + fac * (coord1[1]-coord0[1]);
	      zline[nPoints] = coord0[2] + fac * (coord1[2]-coord0[2]);
	      nPoints++;
	    }
	  else
	    iTab[3 * n + j] = -1;
	}
    }
  xline = g_realloc(xline, nPoints * sizeof(double));
  yline = g_realloc(yline, nPoints * sizeof(double));
  zline = g_realloc(zline, nPoints * sizeof(double));
  DBG_fprintf(stderr, " | found %d points for line.\n", nPoints);
#if DEBUG == 1
  g_timer_stop(timer);
  fprintf(stderr, "Isoline : compute intersections in %g micro-s.\n",
          g_timer_elapsed(timer, &fractionTimer)/1e-6);
  g_timer_destroy(timer);
#endif

#if DEBUG == 1
  timer = g_timer_new();
  g_timer_start(timer);
#endif
  if(nPoints == 0)
    {
      /* g_warning("no isoline found for value %g.", isoValue); */
      status = FALSE;
      isoline_ = (VisuLine*)0;
    }
  else
    {
      /* Create lines from the computed points*/
      isoline_ = g_malloc(sizeof(VisuLine));
      isoline_->refCount = 1;
      isoline_->value = isoValue;
      status = Create_line(iTab, nTriangles,
                           xline, yline, zline, &isoline_);
    }
#if DEBUG == 1
  g_timer_stop(timer);
  fprintf(stderr, "Isoline : build lines in %g micro-s.\n",
          g_timer_elapsed(timer, &fractionTimer)/1e-6);
  g_timer_destroy(timer);
#endif

  g_free(iTab);
  g_free(xline);
  g_free(yline);
  g_free(zline);

  DBG_fprintf(stderr, " | Line exit with status %d.\n", (int)status);

  return (status)?isoline_:(VisuLine*)0;
}

/*
 * Create_line:
 * @nPoints: an integer giving the number points of the line;
 * @iTab: table containing the number of line points if line exists at this place otherwise -1;
 * @nedges: an integer giving the number of edges;
 * @ntriangles: an integer giving the number of triangles;
 * @vertices_index_from_triangles : return general vertex index from a given triangle;
 * @edges_index_from_triangles : return general edge index from a given triangle;
 * @isoValue: the value of the isoline;
 * @xline: x coordinate of the line points;
 * @yline: y coordinate of the line points;
 * @zline: z coordinate of the line points;
 * @vertices_SFV: scalar field value on each vertex;
 * @isoline: the Line structure containing data for plot;
 * Create on the fly a line from a list of points.
 * Returns: TRUE if the line is created.
 */
static gboolean Create_line(int *iTab, int ntriangles,
                            double *xline, double *yline, double *zline,
                            VisuLine **isoline)
{

/* Local variables */

  register int i;
  int cubeindex;
  int e;
  guint n, nlines;
  int m0, m1;
  int *vTab;

/* Routine code */

/* Ensure that the pointer is not zero */
  g_return_val_if_fail(isoline, FALSE);

/* Allocate buffers. */
  vTab = g_malloc(2*3*ntriangles * sizeof(int));
  nlines = 0;

/* Loop over the triangles */
  for(i=0; i<ntriangles; i++)
  {
    cubeindex = 0;
    if (iTab[3 * i + 0] >= 0) cubeindex |=   1;
    if (iTab[3 * i + 1] >= 0) cubeindex |=   2;
    if (iTab[3 * i + 2] >= 0) cubeindex |=   4;

    e = edgeTable[cubeindex];

    if (e == 0) continue;

    for (n = 0; linTable[e][n + 1]!= -1; n += 1)
    {
       m0 = iTab[3 * i + linTable[e][n  ]];
       if(m0 == -1)
       {
         g_warning("m1 %d.", i);
	 g_free(vTab);
	 g_free(*isoline);
         return FALSE;
       }
       m1 = iTab[3 * i + linTable[e][n+1]];
       if(m1 == -1)
       {
         g_warning("m2 %d.", i);
	 g_free(vTab);
	 g_free(*isoline);
         return FALSE;
       }

       vTab[2*nlines + 0] = m0;
       vTab[2*nlines + 1] = m1;

       nlines++;
     }
  }

  DBG_fprintf(stderr, " | found %d lines.\n", nlines);

  if(nlines == 0)
    {
      g_warning("no isolines found.");
      g_free(vTab);
      g_free(*isoline);
      return FALSE;
    }

/* Ok, try to create a Line object from here. */

  DBG_fprintf(stderr, " | copy vertices.\n");

  (*isoline)->num_sublines = nlines;
  (*isoline)->num_vertices = 2*nlines+1;

/* Allocate memory. */

  (*isoline)->vertex_3dpos = g_malloc(sizeof(float*) * (*isoline)->num_vertices);
  (*isoline)->vertex_3dpos[0] = g_malloc(sizeof(float) * (*isoline)->num_vertices * 3);
  for (n = 0; n < (*isoline)->num_vertices; n++)
    (*isoline)->vertex_3dpos[n] = (*isoline)->vertex_3dpos[0] + 3 * n;

  for(n = 0; n < nlines; n++)
    {
      (*isoline)->vertex_3dpos[2*n  ][0] = xline[vTab[2*n  ]];
      (*isoline)->vertex_3dpos[2*n  ][1] = yline[vTab[2*n  ]];
      (*isoline)->vertex_3dpos[2*n  ][2] = zline[vTab[2*n  ]];
      (*isoline)->vertex_3dpos[2*n+1][0] = xline[vTab[2*n+1]];
      (*isoline)->vertex_3dpos[2*n+1][1] = yline[vTab[2*n+1]];
      (*isoline)->vertex_3dpos[2*n+1][2] = zline[vTab[2*n+1]];
    }

  DBG_fprintf(stderr, " | finishing lines.\n");

  g_free(vTab);
  return TRUE;
}

/**
 * visu_line_draw:
 * @line: a set of points forming a line.
 * @rgb: a colour.
 *
 * Call the OpenGL routine that will draw this line.
 *
 * Since: 3.4
 */
void visu_line_draw(VisuLine *line, float rgb[3])
{
  guint i;

  g_return_if_fail(line);

  glLineWidth(2.f);
/*   glPointSize(2.f); */
  glColor3fv(rgb);
  glDepthMask(0);

  glBegin(GL_LINES);
  for ( i = 0; i < line->num_sublines; i++)
    {
      glVertex3fv(line->vertex_3dpos[2 * i]);
      glVertex3fv(line->vertex_3dpos[2 * i + 1]);
    }
  glEnd();

/*   glEnable(GL_POINT_SMOOTH); */
/*   glBegin(GL_POINTS); */
/*   for ( i = 0; i < line->num_sublines; i++) */
/*     { */
/*       glVertex3fv(line->vertex_3dpos[2 * i]); */
/*       glVertex3fv(line->vertex_3dpos[2 * i + 1]); */
/*     } */
/*   glEnd(); */
/*   glDisable(GL_POINT_SMOOTH); */

  glDepthMask(1);
}

/**
 * visu_line_project:
 * @line: a #VisuLine object.
 * @plane: a #VisuPlane object.
 * @nSeg: a location to store the size of projection.
 *
 * Calculate the projection of each @line vertex on @plane.
 *
 * Since: 3.6
 * 
 * Returns: a newly allocated array of line segments. The size of this
 * array is 4 * @nSeg, holding the two plane coordiantes of the two
 * vertices of a line.
 */
float* visu_line_project(VisuLine *line, VisuPlane *plane, guint *nSeg)
{
  float basis[2][3], center[3], *out;
  guint i;

  g_return_val_if_fail(line && nSeg, (float*)0);

  visu_plane_getBasis(plane, basis, center);
  
  out = g_malloc(sizeof(float) * 2 * line->num_sublines * 2);
  *nSeg = line->num_sublines;
  for ( i = 0; i < line->num_sublines; i++)
    {
      out[i * 4 + 0] =
	basis[0][0] * (line->vertex_3dpos[2 * i + 0][0] - center[0]) +
	basis[0][1] * (line->vertex_3dpos[2 * i + 0][1] - center[1]) +
	basis[0][2] * (line->vertex_3dpos[2 * i + 0][2] - center[2]);
      out[i * 4 + 1] =
	basis[1][0] * (line->vertex_3dpos[2 * i + 0][0] - center[0]) +
	basis[1][1] * (line->vertex_3dpos[2 * i + 0][1] - center[1]) +
	basis[1][2] * (line->vertex_3dpos[2 * i + 0][2] - center[2]);

      out[i * 4 + 2] =
	basis[0][0] * (line->vertex_3dpos[2 * i + 1][0] - center[0]) +
	basis[0][1] * (line->vertex_3dpos[2 * i + 1][1] - center[1]) +
	basis[0][2] * (line->vertex_3dpos[2 * i + 1][2] - center[2]);
      out[i * 4 + 3] =
	basis[1][0] * (line->vertex_3dpos[2 * i + 1][0] - center[0]) +
	basis[1][1] * (line->vertex_3dpos[2 * i + 1][1] - center[1]) +
	basis[1][2] * (line->vertex_3dpos[2 * i + 1][2] - center[2]);
    }

  return out;
}

/**
 * visu_line_getValue:
 * @line: a #VisuLine object.
 *
 * Lines are usually created as iso-values line in a mesh.
 *
 * Since: 3.6
 *
 * Returns: the value associated to the line.
 */
double visu_line_getValue(VisuLine *line)
{
  g_return_val_if_fail(line, -1.);

  return line->value;
}
