/*   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 <gtk/gtk.h>
#include <math.h>
#include <string.h>

#include "gtk_curveWidget.h"
#include <support.h>
#include <visu_tools.h>
#include <visu_elements.h>
#include <coreTools/toolMatrix.h>
#include <renderingMethods/elementAtomic.h>

/**
 * SECTION: gtk_curveWidget
 * @short_description: A specialised curve widget to draw distance
 * distribution for pairs.
 *
 * <para>This is a psecialised widget to display g(r)
 * information.</para>
 *
 * Since: 3.6
 */

/**
 * VisuUiCurveFrame:
 *
 * An opaque structure defining a #VisuUiCurveFrame widget.
 *
 * Since: 3.6
 */
struct _VisuUiCurveFrame
{
  GtkDrawingArea parent;

  gboolean dispose_has_run, dirty;

  VisuPairSet *model;
  gulong pair_sig;
  
  ToolMatrixScalingFlag scale;
  float zoom;
  float dists[2];
  float heights[2];
  float hlRange[2];

  VisuUiCurveFrameStyle style;
  VisuElement *filter;
  GHashTable *data;
  guint nSteps;
  float init, step;
};

/**
 * VisuUiCurveFrameClass:
 *
 * An opaque structure defining the class of a #VisuUiCurveFrame widget.
 *
 * Since: 3.6
 */
struct _VisuUiCurveFrameClass
{
  GtkDrawingAreaClass parent_class;
};

#define SPACER "\302\240-\302\240"
#define ALL    _("All")

enum
  {
    PROP_0,
    MIN_PROP,
    MAX_PROP,
    ZOOM_PROP,
    MIN_HL_PROP,
    MAX_HL_PROP,
    INT_HL_PROP,
    MEAN_HL_PROP,
    FILTER_PROP,
    LABEL_PROP,
    N_PROP
  };
static GParamSpec *_properties[N_PROP];

/**
 * visu_ui_curve_frame_get_type:
 *
 * Internal routine to get #VISU_TYPE_UI_CURVE_FRAME value.
 *
 * Since: 3.6
 */
G_DEFINE_TYPE (VisuUiCurveFrame, visu_ui_curve_frame, GTK_TYPE_DRAWING_AREA)

#if GTK_MAJOR_VERSION < 3 && GTK_MINOR_VERSION < 26
static gboolean visu_ui_curve_frame_expose(GtkWidget *curve, GdkEventExpose *event);
#endif
static gboolean visu_ui_curve_frame_drawCairo(GtkWidget *curve, cairo_t *cr);
static void visu_ui_curve_frame_get_property(GObject* obj, guint property_id,
                                             GValue *value, GParamSpec *pspec);
static void visu_ui_curve_frame_set_property(GObject* obj, guint property_id,
                                             const GValue *value, GParamSpec *pspec);
static void visu_ui_curve_frame_dispose(GObject *obj);
static void visu_ui_curve_frame_finalize(GObject *obj);
static void hashDraw(gpointer key, gpointer value, gpointer usre_data);
static void drawData(VisuUiCurveFrame *curve, cairo_t *cr, const gchar *name,
		     const guint *data, double w, double h, double dw,
		     VisuUiCurveFrameStyle style);
static void _addData(VisuUiCurveFrame *curve, const gchar *eleName, const gchar *lkName,
                     const guint *data, guint nSteps, float init, float step);
static void _setNNodes(VisuUiCurveFrame *curve, const gchar *ele, guint n);

static void visu_ui_curve_frame_class_init (VisuUiCurveFrameClass *klass)
{
#if GTK_MAJOR_VERSION < 3 && GTK_MINOR_VERSION < 26
  GTK_WIDGET_CLASS(klass)->expose_event = visu_ui_curve_frame_expose;
#else
  GTK_WIDGET_CLASS(klass)->draw = visu_ui_curve_frame_drawCairo;
#endif
  G_OBJECT_CLASS(klass)->dispose  = visu_ui_curve_frame_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_ui_curve_frame_finalize;
  G_OBJECT_CLASS(klass)->get_property  = visu_ui_curve_frame_get_property;
  G_OBJECT_CLASS(klass)->set_property = visu_ui_curve_frame_set_property;

  /**
   * VisuUiCurveFrame::minimum:
   *
   * Store the minimum distance to get the g(r) from.
   *
   * Since: 3.8
   */
  _properties[MIN_PROP] = g_param_spec_float("minimum", "Minimum", "minimum distance",
                                             0.f, G_MAXFLOAT, 0.f, G_PARAM_READWRITE);
  /**
   * VisuUiCurveFrame::maximum:
   *
   * Store the maximum distance to get the g(r) to.
   *
   * Since: 3.8
   */
  _properties[MAX_PROP] = g_param_spec_float("maximum", "Maximum", "maximum distance",
                                             0.f, G_MAXFLOAT, 10.f, G_PARAM_READWRITE);
  /**
   * VisuUiCurveFrame::zoom:
   *
   * Store the zoom factor to discretise the g(r).
   *
   * Since: 3.8
   */
  _properties[ZOOM_PROP] = g_param_spec_float("zoom", "Zoom", "zoom factor",
                                              1.f, G_MAXFLOAT, 5.f, G_PARAM_READWRITE);
  /**
   * VisuUiCurveFrame::minimum-highlight:
   *
   * Store the minimum distance for the highlight region.
   *
   * Since: 3.8
   */
  _properties[MIN_HL_PROP] = g_param_spec_float("minimum-highlight", "Minimum highlight",
                                                "minimum highlight distance",
                                                0.f, G_MAXFLOAT, 1.f, G_PARAM_READWRITE);
  /**
   * VisuUiCurveFrame::maximum-highlight:
   *
   * Store the maximum distance for the highlight region.
   *
   * Since: 3.8
   */
  _properties[MAX_HL_PROP] = g_param_spec_float("maximum-highlight", "Maximum highlight",
                                                "maximum highlight distance",
                                                0.f, G_MAXFLOAT, 1.f, G_PARAM_READWRITE);
  /**
   * VisuUiCurveFrame::integral-in-range:
   *
   * Store the integral value in the highlight range.
   *
   * Since: 3.8
   */
  _properties[INT_HL_PROP] = g_param_spec_float("integral-in-range", "Integral in range",
                                                "integral value in highlight range",
                                                0.f, G_MAXFLOAT, 0.f, G_PARAM_READABLE);
  /**
   * VisuUiCurveFrame::mean-in-range:
   *
   * Store the mean value in the highlight range.
   *
   * Since: 3.8
   */
  _properties[MEAN_HL_PROP] = g_param_spec_float("mean-in-range", "Mean in range",
                                                 "mean value in highlight range",
                                                 0.f, G_MAXFLOAT, 0.f, G_PARAM_READABLE);
  /**
   * VisuUiCurveFrame::filter:
   *
   * Store the filter.
   *
   * Since: 3.8
   */
  _properties[FILTER_PROP] = g_param_spec_object("filter", "Filter",
                                                 "filter by element",
                                                 VISU_TYPE_ELEMENT, G_PARAM_READWRITE);
  /**
   * VisuUiCurveFrame::label:
   *
   * Store the label used by the filter.
   *
   * Since: 3.8
   */
  _properties[LABEL_PROP] = g_param_spec_string("label", "Label",
                                                "label describing the filter",
                                                "", G_PARAM_READABLE);

  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROP, _properties);
}

static void visu_ui_curve_frame_init(VisuUiCurveFrame *curve)
{
  curve->dispose_has_run = FALSE;

  curve->model = (VisuPairSet*)0;
  curve->dirty = TRUE;

  curve->style = CURVE_LINEAR;
  curve->scale = TOOL_MATRIX_SCALING_LOG;

  curve->heights[0] = 0.;
  curve->heights[1] = 5.;

  curve->dists[0] = 0.f;
  curve->dists[1] = G_MAXFLOAT;

  curve->hlRange[0] = -1.f;

  curve->zoom = 5.f;

  curve->filter = (VisuElement*)0;
  curve->data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
}
static void visu_ui_curve_frame_get_property(GObject* obj, guint property_id,
                                             GValue *value, GParamSpec *pspec)
{
  VisuUiCurveFrame *self = VISU_UI_CURVE_FRAME(obj);
  guint *data, i, n;
  gfloat d, val;

  switch (property_id)
    {
    case MIN_PROP:
      g_value_set_float(value, self->dists[0]);
      break;
    case MAX_PROP:
      g_value_set_float(value, self->dists[1]);
      break;
    case ZOOM_PROP:
      g_value_set_float(value, self->zoom);
      break;
    case MIN_HL_PROP:
      g_value_set_float(value, self->hlRange[0]);
      break;
    case MAX_HL_PROP:
      g_value_set_float(value, self->hlRange[1]);
      break;
    case INT_HL_PROP:
      if (self->filter)
        data = (guint*)g_hash_table_lookup(self->data, visu_element_getName(self->filter));
      else
        data = (guint*)g_hash_table_lookup(self->data, ALL);
      if (data)
        {
          val = 0.f;
          for (i = 0, d = self->init; i < self->nSteps - 1; i++, d += self->step)
            if (d >= self->hlRange[0] && d < self->hlRange[1])
              val += (float)data[i];

          g_value_set_float(value, val / (float)data[self->nSteps - 1]);
        }
      else
        g_value_set_float(value, 0.f);
      break;
    case MEAN_HL_PROP:
      if (self->filter)
        data = (guint*)g_hash_table_lookup(self->data, visu_element_getName(self->filter));
      else
        data = (guint*)g_hash_table_lookup(self->data, ALL);
      if (data)
        {
          val = 0.f;
          n = 0;
          for (i = 0, d = self->init; i < self->nSteps - 1; i++, d += self->step)
            if (d >= self->hlRange[0] && d < self->hlRange[1])
              {
                val += (float)data[i] * (d + self->step * 0.5f);
                n += data[i];
              }

          g_value_set_float(value, val / (float)n);
        }
      else
        g_value_set_float(value, 0.f);
      break;
    case FILTER_PROP:
      g_value_set_object(value, self->filter);
      break;
    case LABEL_PROP:
      g_value_set_string(value, self->filter ? visu_element_getName(self->filter) : ALL);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_ui_curve_frame_set_property(GObject* obj, guint property_id,
                                             const GValue *value, GParamSpec *pspec)
{
  VisuUiCurveFrame *self = VISU_UI_CURVE_FRAME(obj);
  float rg[2];

  switch (property_id)
    {
    case MIN_PROP:
      rg[0] = g_value_get_float(value);
      rg[1] = self->dists[1];
      visu_ui_curve_frame_setSpan(self, rg);
      break;
    case MAX_PROP:
      rg[0] = self->dists[0];
      rg[1] = g_value_get_float(value);
      visu_ui_curve_frame_setSpan(self, rg);
      break;
    case ZOOM_PROP:
      visu_ui_curve_frame_setZoomFactor(self, g_value_get_float(value));
      break;
    case MIN_HL_PROP:
      rg[0] = g_value_get_float(value);
      rg[1] = self->hlRange[1];
      visu_ui_curve_frame_setHighlightRange(self, rg);
      break;
    case MAX_HL_PROP:
      rg[0] = self->hlRange[0];
      rg[1] = g_value_get_float(value);
      visu_ui_curve_frame_setHighlightRange(self, rg);
      break;
    case FILTER_PROP:
      visu_ui_curve_frame_setFilter(self, VISU_ELEMENT(g_value_get_object(value)));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_ui_curve_frame_dispose(GObject *obj)
{
  DBG_fprintf(stderr, "Gtk Curve: dispose object %p.\n", (gpointer)obj);

  if (VISU_UI_CURVE_FRAME(obj)->dispose_has_run)
    return;

  VISU_UI_CURVE_FRAME(obj)->dispose_has_run = TRUE;

  visu_ui_curve_frame_setModel(VISU_UI_CURVE_FRAME(obj), (VisuPairSet*)0);
  visu_ui_curve_frame_setFilter(VISU_UI_CURVE_FRAME(obj), (VisuElement*)0);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_ui_curve_frame_parent_class)->dispose(obj);
}
static void visu_ui_curve_frame_finalize(GObject *obj)
{
  g_return_if_fail(obj);

  DBG_fprintf(stderr, "Gtk Curve: finalize object %p.\n", (gpointer)obj);

  g_hash_table_destroy(VISU_UI_CURVE_FRAME(obj)->data);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_ui_curve_frame_parent_class)->finalize(obj);

  DBG_fprintf(stderr, " | freeing ... OK.\n");
}

static double tickDist(float dists[2], int length, ToolMatrixScalingFlag scale)
{
#define TICK_SPACE 50.f
  float s, l, n;

  n = (float)length / TICK_SPACE;
  s = (dists[1] - dists[0]) / n;
  l = log10(s);
  l = (int)((l < 0.f)?l - 1.f:l);
  s = s / exp(l * log(10));

  if (scale == TOOL_MATRIX_SCALING_LINEAR)
    {
      if (s <= 1.25) n = 1.f;
      else if (s <= 3.75) n = 2.5f;
      else if (s <= 7.5) n = 5.f;
      else s = 10.f;
    }
  else
    {
      if (s <= 5.f) n = 1.f;
      else n = 10.f;
    }
  s = n * exp(l * log(10));

  return (double)s;
}

static gboolean isValidData(VisuElement* filter, const gchar *data)
{
  gchar *tmp;
  gboolean res;
  
  if (filter)
    {
      tmp = g_strdup_printf("%s"SPACER, visu_element_getName(filter));
      res = (!strcmp(data, visu_element_getName(filter)) || strstr(data, tmp));
      g_free(tmp);
    }
  else
    res = (!strcmp(data, ALL) || !strstr(data, SPACER));
  return res;
}

static void setCairoColor(cairo_t *cr, const VisuElement *filter, const gchar *data)
{
  VisuElement *ele;
  const ToolColor *color;
  gchar *tmp;
  const gchar *name;

  if (!filter)
    name = data;
  else
    {
      tmp = g_strdup_printf("%s"SPACER, visu_element_getName(filter));
      name = data + strlen(tmp);
      g_free(tmp);
    }

  ele = visu_element_lookup(name);
  if (ele)
    {
      color = visu_element_renderer_getColor(visu_element_renderer_getFromPool(ele));
      cairo_set_source_rgb (cr, color->rgba[0], color->rgba[1], color->rgba[2]);
    }
  else
    cairo_set_source_rgb (cr, 0.5, 0.5, 0.5);
}

static void _compute(VisuUiCurveFrame *curve)
{
  VisuPair *pair;
  VisuData *dataObj;
  VisuPairDistribution *dd;
  guint i, *storage;

  curve->dirty = FALSE;

  g_hash_table_remove_all(curve->data);
  if (!curve->model)
    return;

  g_object_get(curve->model, "data", &dataObj, NULL);
  if (!dataObj)
    return;

  curve->nSteps = 0;
  curve->init = -1.f;
  curve->step = (curve->dists[1] - curve->dists[0]) / curve->zoom / 50.f;
  i = 0;
  while ((pair = visu_pair_set_getNthPair(curve->model, i++)))
    {
      dd = visu_pair_getDistanceDistribution(pair, dataObj, curve->step,
                                             curve->dists[0], curve->dists[1]);
      if (i == 1)
        {
          curve->init = dd->initValue;
          curve->nSteps = dd->nValues;
          storage = g_malloc0(sizeof(guint) * curve->nSteps);
          g_hash_table_insert(curve->data, g_strdup(ALL), storage);
        }
      _addData(curve, dd->ele1->name, dd->ele2->name,
               dd->histo, dd->nValues, dd->initValue, dd->stepValue);
      if (dd->ele1 == dd->ele2)
        _setNNodes(curve, dd->ele1->name, dd->nNodesEle1);
    };
  g_object_unref(dataObj);
}

struct _drawData
{
  VisuUiCurveFrame *curve;
  cairo_t *cr;
  double w;
  double h;
  double dw;
};
static gboolean visu_ui_curve_frame_drawCairo(GtkWidget *wd, cairo_t *cr)
{
#define BUF 3.
  double x, s, f, g, w, h, dw, dh, xp, wl, hl, yp;
  float *dists, *heights;
  gchar val[64];
  cairo_text_extents_t ext;
  struct _drawData  dt;
  guint *data;
  VisuUiCurveFrame *curve;
  GList *eles, *tmpLst;
  GtkAllocation allocation;

  DBG_fprintf(stderr, "Gtk Curve: general redraw.\n");

  curve = VISU_UI_CURVE_FRAME(wd);

  if (curve->dirty)
    _compute(curve);

  dists = curve->dists;
  heights = curve->heights;
  gtk_widget_get_allocation(wd, &allocation);
        
  cairo_select_font_face(cr, "Sans",
			 CAIRO_FONT_SLANT_NORMAL,
			 CAIRO_FONT_WEIGHT_NORMAL);
  cairo_set_font_size(cr, 10.0);
  cairo_text_extents(cr, "10000.", &ext);
  dw = ext.width + BUF * 2.;
  dh = ext.height + BUF * 2.;
  w = (double)allocation.width - dw;
  h = (double)allocation.height - dh;
  data = (guint*)g_hash_table_lookup(curve->data, (curve->filter)?visu_element_getName(curve->filter):ALL);

  /* the frame itself. */
  cairo_set_line_width(cr, 1.);
  cairo_rectangle(cr, dw - 0.5, 0.5, w, h);
  cairo_set_source_rgb (cr, 1, 1, 1);
  cairo_fill_preserve (cr);
  cairo_set_source_rgb (cr, 0, 0, 0);
  cairo_stroke (cr);
  cairo_set_line_width(cr, 2.);
  DBG_fprintf(stderr, "Gtk Curve: frame done.\n");

  /* The highlight zone. */
  if (curve->hlRange[0] >= 0.f && data)
    {
      wl = w * CLAMP((curve->hlRange[1] - curve->hlRange[0]) /
		     (dists[1] - dists[0]), 0.f, 1.f);
      cairo_set_source_rgba(cr, 0.05, 0., 1., 0.25);
      cairo_rectangle(cr, dw + w * CLAMP((curve->hlRange[0] - dists[0])/
					 (dists[1] - dists[0]), 0.f, 1.f),
		      0.5, wl, h);
      cairo_fill(cr);
    }

  /* The plots. */
  dt.curve = curve;
  dt.cr    = cr;
  dt.w     = w;
  dt.dw    = dw;
  dt.h     = h;
  if (data && curve->style != CURVE_BAR)
    drawData(curve, cr, (curve->filter)?visu_element_getName(curve->filter):ALL, data, w, h, dw, CURVE_BAR);
  g_hash_table_foreach(curve->data, hashDraw, &dt);
  if (data && curve->style == CURVE_BAR)
    drawData(curve, cr, (curve->filter)?visu_element_getName(curve->filter):ALL, data, w, h, dw, CURVE_LINEAR);
  DBG_fprintf(stderr, "Gtk Curve: plots done.\n");

  /* the ticks & the labels */
  cairo_set_source_rgb (cr, 0, 0, 0);
  cairo_set_line_width(cr, 2.);
  f = w / (dists[1] - dists[0]);
  g = 1.f / f;
  s = tickDist(dists, w, TOOL_MATRIX_SCALING_LINEAR) * f;
  for (x = - dists[0] * f; x <= w; x += s)
    if (x >= 0.)
      {
	cairo_move_to(cr, dw + x, 0);
	cairo_line_to(cr, dw + x, 3);
	cairo_move_to(cr, dw + x, h);
	cairo_line_to(cr, dw + x, h - 3);
	cairo_stroke(cr);
	sprintf(val, "%g", x * g + dists[0]);
	cairo_text_extents(cr, val, &ext);
	cairo_move_to(cr, dw + MIN(MAX(0., x - ext.width/2 - ext.x_bearing),
				   w - ext.width - ext.x_bearing),
		      allocation.height - ext.height - ext.y_bearing - BUF);
	cairo_show_text(cr, val);
      }
  if (curve->scale == TOOL_MATRIX_SCALING_LOG)
    f = h / log(heights[1] + 0.5f);
  else
    {
      f = h / heights[1];
      s = tickDist(heights, h, curve->scale);
    }
  for (x = (curve->scale == TOOL_MATRIX_SCALING_LOG)?1.:0.; x <= heights[1];
       x = (curve->scale == TOOL_MATRIX_SCALING_LOG)?x * 10:x + s)
    {
      if (curve->scale == TOOL_MATRIX_SCALING_LOG)
	{
	  cairo_set_line_width(cr, 1.);
	  for (xp = x * 2.; xp < x * 10.; xp += x)
	    {
	      yp = h - log(xp + 0.5f) * f;
	      cairo_move_to(cr, dw + 0, yp);
	      cairo_line_to(cr, dw + 1.5, yp);
	      cairo_move_to(cr, allocation.width, yp);
	      cairo_line_to(cr, allocation.width - 2.5, yp);
	      cairo_stroke(cr);
	    }
	  xp = h - log(x + 0.5f) * f;
	}
      else
	xp = h - x * f;
      cairo_set_line_width(cr, 2.);
      cairo_move_to(cr, dw + 0, xp);
      cairo_line_to(cr, dw + 3, xp);
      cairo_move_to(cr, allocation.width, xp);
      cairo_line_to(cr, allocation.width - 4, xp);
      cairo_stroke(cr);
      sprintf(val, "%g", x);
      cairo_text_extents(cr, val, &ext);
      cairo_move_to(cr, dw - BUF - ext.width - ext.x_bearing,
		    MIN(MAX(- ext.y_bearing,
			    xp - ext.height/2 - ext.y_bearing), h));
      cairo_show_text(cr, val);
    }
  DBG_fprintf(stderr, "Gtk Curve: ticks done.\n");

  /* The legend. */
  eles = g_hash_table_get_keys(curve->data);
  if (eles)
    {
      DBG_fprintf(stderr, "Gtk Curve: rebuild the legend (%d).\n",
		  g_list_length(eles));
      wl = 0;
      s  = 0;
      hl = BUF;
      for (tmpLst = eles; tmpLst; tmpLst = g_list_next(tmpLst))
	if (isValidData(curve->filter, (gchar*)tmpLst->data))
	  {
	    cairo_text_extents(cr, (gchar*)tmpLst->data, &ext);
	    DBG_fprintf(stderr, " | '%s' -> %fx%fx%f\n", (gchar*)tmpLst->data,
			ext.width, ext.height, ext.y_bearing);
	    wl = MAX(wl, ext.width);
	    s  = MAX(s, -ext.y_bearing - 2.);
	    hl += BUF - ext.y_bearing;
	  }
      wl += 2 * BUF + BUF + s;
      hl += BUF;
      cairo_set_line_width(cr, 1.);
      cairo_rectangle(cr, dw + 3 + BUF + 0.5, 0.5 + 3 + BUF, wl, hl);
      cairo_set_source_rgb(cr, 0.67, 0.67, 0.67);
      cairo_fill_preserve (cr);
      cairo_set_source_rgb (cr, 0, 0, 0);
      cairo_stroke (cr);
      hl = BUF;
      for (tmpLst = eles; tmpLst; tmpLst = g_list_next(tmpLst))
	if (isValidData(curve->filter, (gchar*)tmpLst->data))
	  {
	    cairo_text_extents(cr, (gchar*)tmpLst->data, &ext);
	    hl -= ext.y_bearing;
	    cairo_move_to(cr, dw + 3 + 3 * BUF + 0.5 + ext.x_bearing + s,
			  0.5 + 3 + BUF + hl);
	    cairo_set_source_rgb(cr, 0, 0, 0);
	    cairo_show_text(cr, (gchar*)tmpLst->data);
	    cairo_arc(cr, dw + 3 + 2 * BUF + 0.5 + s / 2.,
		      0.5 + 3 + BUF + hl - s / 2., s / 2., 0, 2 * G_PI);
	    setCairoColor(cr, curve->filter, (gchar*)tmpLst->data);
	    cairo_fill(cr);
	    hl += BUF/*  + ext.y_bearing + ext.height */;
	  }
      g_list_free(eles);
    }
  DBG_fprintf(stderr, "Gtk Curve: redraw done.\n");

  return FALSE;
}

static void hashDraw(gpointer key, gpointer value, gpointer user_data)
{
  struct _drawData *dt;
  gchar *tmp;
  gboolean ret;

  if (!strcmp(key, ALL))
    return;
  
  dt = (struct _drawData*)user_data;
  if (!dt->curve->filter)
    {
      if (strstr((gchar*)key, SPACER))
	return;
    }
  else
    {
      tmp = g_strdup_printf("%s"SPACER, visu_element_getName(dt->curve->filter));
      ret = (!strstr((gchar*)key, tmp));
      g_free(tmp);
      if (ret) return;
    }
  drawData(dt->curve, dt->cr, (gchar*)key, (guint*)value,
	   dt->w, dt->h, dt->dw, dt->curve->style);
}
static void drawData(VisuUiCurveFrame *curve, cairo_t *cr, const gchar *name,
		     const guint *data, double w, double h, double dw,
		     VisuUiCurveFrameStyle style)
{
  float step, fx, x, fy, y, val, init;
  int nSteps, i;
  double bw;
  VisuPairDistribution dd;
  guint startStopId[2], pos;
  cairo_text_extents_t ext;
  gchar str[25];
  
  init = curve->init;
  step = curve->step;
  nSteps = curve->nSteps;

  setCairoColor(cr, curve->filter, name);

  bw = MAX(w * step / (curve->dists[1] - curve->dists[0]) - 1., 0.5);
  if (style == CURVE_BAR)
    cairo_set_line_width(cr, bw);
  else
    cairo_set_line_width(cr, 1.5);

  cairo_move_to(cr, dw, h - 0.5);
  fx = w / (curve->dists[1] - curve->dists[0]);
  if (curve->scale == TOOL_MATRIX_SCALING_LINEAR)
    fy = (h - 1.) / (curve->heights[1] - curve->heights[0]);
  else
    fy = (h - 1.) / log(curve->heights[1] + 0.5);
  for (i = 0; i < nSteps - 1; i++)
    if ((val = (step * i + init - curve->dists[0])) >= 0.)
      {
	if (style != CURVE_BAR || data[i] > 0)
	  {
	    x = dw + val * fx + bw / 2.;
	    if (style == CURVE_BAR)
	      cairo_move_to(cr, x, h - 0.5);
	    if (curve->scale == TOOL_MATRIX_SCALING_LINEAR)
	      cairo_line_to(cr, x, h - 0.5 - data[i] * fy);
	    else
	      cairo_line_to(cr, x, h - 0.5 - log(MAX(data[i] + 0.5, 1)) * fy);
	    if (style == CURVE_BAR)
	      cairo_stroke(cr);
	  }
      }
  if (style != CURVE_BAR)
    cairo_stroke(cr);

  if (style != CURVE_BAR)
    {
      dd.histo = (guint*)data;
      dd.nValues = (guint)nSteps;
      dd.initValue = init;
      dd.stepValue = step;
      if (curve->scale == TOOL_MATRIX_SCALING_LINEAR)
        dd.nNodesEle1 = dd.nNodesEle2 = h / fy / 1.5;
      else
        dd.nNodesEle1 = dd.nNodesEle2 = exp(h / fy / 1.5);
      startStopId[0] = 0;
      startStopId[1] = (guint)(nSteps - 1);
      while (visu_pair_distribution_getNextPick(&dd, startStopId, (guint*)0, (guint*)0, &pos))
        {
          sprintf(str, "%6.3f", init + step * pos);
          val = step * pos + init - curve->dists[0];
          cairo_text_extents(cr, str, &ext);
          x = val * fx + bw / 2. + 1.;
          x = (x + ext.width >= w)?w - ext.width:x;
          y = h - 0.5;
          if (curve->scale == TOOL_MATRIX_SCALING_LINEAR)
            y -= data[pos] * fy;
          else
            y -= log(MAX(data[pos] + 0.5, 1)) * fy;
          y += ext.height / 2.;
          y = MAX(y, ext.height + 1.5);
          cairo_move_to(cr, dw + x, y);
          cairo_set_source_rgb (cr, 0., 0., 0.);
          cairo_show_text(cr, str);
          startStopId[0] = startStopId[1];
          startStopId[1] = (guint)(nSteps - 1);
        }
    }
}

#if GTK_MAJOR_VERSION < 3 && GTK_MINOR_VERSION < 26
static gboolean visu_ui_curve_frame_expose(GtkWidget *curve, GdkEventExpose *event)
{
  cairo_t *cr;
  gboolean res;

  /* get a cairo_t */
  cr = gdk_cairo_create(GDK_DRAWABLE(gtk_widget_get_window(curve)));

  cairo_rectangle (cr,
		   event->area.x, event->area.y,
		   event->area.width, event->area.height);
  cairo_clip (cr);
        
  res = visu_ui_curve_frame_drawCairo(curve, cr);

  cairo_destroy (cr);

  return res;
}
#endif
/**
 * visu_ui_curve_frame_new:
 * @distMin: a float.
 * @distMax: a float (bigger than @distMin).
 *
 * It creates a graph that can display distances distribution for
 * #VisuElement pairing. The display span is given by @distMin and @distMax.
 *
 * Since: 3.6
 *
 * Returns: a newly craeted #VisuUiCurveFrame widget.
 */
GtkWidget *visu_ui_curve_frame_new(float distMin, float distMax)
{
  g_return_val_if_fail(distMin >= 0.f && distMax > distMin, (GtkWidget*)0);

  return g_object_new(VISU_TYPE_UI_CURVE_FRAME,
                      "minimum", distMin, "maximum", distMax, NULL);
}
/**
 * visu_ui_curve_frame_setSpan:
 * @curve: a #VisuUiCurveFrame widget.
 * @span: two floats.
 *
 * Changes the distance range that is displayed on the curve.
 *
 * Since: 3.6
 *
 * Returns: TRUE if the distance displayed is actually changed.
 */
gboolean visu_ui_curve_frame_setSpan(VisuUiCurveFrame *curve, float span[2])
{
  gboolean updateMin, updateMax;

  g_return_val_if_fail(VISU_IS_UI_CURVE_FRAME(curve), FALSE);
  g_return_val_if_fail(span[0] >= 0.f && span[1] > span[0], FALSE);

  updateMin = (curve->dists[0] != span[0]);
  updateMax = (curve->dists[1] != span[1]);

  curve->dists[0] = span[0];
  curve->dists[1] = span[1];

  if (updateMin)
    g_object_notify_by_pspec(G_OBJECT(curve), _properties[MIN_PROP]);
  if (updateMax)
    g_object_notify_by_pspec(G_OBJECT(curve), _properties[MAX_PROP]);

  if (curve->hlRange[0] < curve->dists[0])
    {
      curve->hlRange[0] = curve->dists[0];
      g_object_notify_by_pspec(G_OBJECT(curve), _properties[MIN_HL_PROP]);
    }
  if (curve->hlRange[1] > curve->dists[1])
    {
      curve->hlRange[1] = curve->dists[1];
      g_object_notify_by_pspec(G_OBJECT(curve), _properties[MAX_HL_PROP]);
    }
  
  if (updateMin || updateMax)
    {
      curve->dirty = TRUE;
      gtk_widget_queue_draw(GTK_WIDGET(curve));
    }

  return updateMin || updateMax;
}
/**
 * visu_ui_curve_frame_getSpan:
 * @curve: a #VisuUiCurveFrame widget.
 * @span: a location for two floats.
 *
 * Retrieves the distances inside which the distribution is displayed.
 *
 * Since: 3.6
 */
void visu_ui_curve_frame_getSpan(VisuUiCurveFrame *curve, float span[2])
{
  g_return_if_fail(VISU_IS_UI_CURVE_FRAME(curve));

  span[0] = curve->dists[0];
  span[1] = curve->dists[1];
}
/**
 * visu_ui_curve_frame_setZoomFactor:
 * @curve: a #VisuUiCurveFrame widget.
 * @zoom: the zoom factor.
 *
 * Adjusts the zoom factor.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 */
gboolean visu_ui_curve_frame_setZoomFactor(VisuUiCurveFrame *curve, float zoom)
{
  g_return_val_if_fail(VISU_IS_UI_CURVE_FRAME(curve) && zoom >= 0.f, FALSE);

  if (zoom == curve->zoom)
    return FALSE;

  curve->zoom = zoom;
  g_object_notify_by_pspec(G_OBJECT(curve), _properties[ZOOM_PROP]);
  curve->dirty = TRUE;
  gtk_widget_queue_draw(GTK_WIDGET(curve));

  return TRUE;
}
static void onPairsNotify(VisuPairSet *set _U_, GParamSpec *pspec _U_, gpointer data)
{
  VISU_UI_CURVE_FRAME(data)->dirty = TRUE;
  gtk_widget_queue_draw(GTK_WIDGET(data));
}
/**
 * visu_ui_curve_frame_setModel:
 * @curve: a #VisuUiCurveFrame object.
 * @model: a #VisuPairSet object.
 *
 * Bind the #VisuPairLink of @model to @curve.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the value actually changed.
 **/
gboolean visu_ui_curve_frame_setModel(VisuUiCurveFrame *curve, VisuPairSet *model)
{
  g_return_val_if_fail(VISU_IS_UI_CURVE_FRAME(curve), FALSE);

  if (curve->model == model)
    return FALSE;

  if (curve->model)
    {
      g_signal_handler_disconnect(curve->model, curve->pair_sig);
      g_object_unref(curve->model);
    }
  if (model)
    {
      g_object_ref(model);
      curve->pair_sig = g_signal_connect(model, "notify::pairs",
                                         G_CALLBACK(onPairsNotify), curve);
    }
  curve->model = model;

  return TRUE;
}
/**
 * visu_ui_curve_frame_addData:
 * @curve: a #VisuUiCurveFrame widget.
 * @eleName: a string.
 * @lkName: a string.
 * @data: an array of frequencies.
 * @nSteps: the size of @data.
 * @init: the initial x value for array @data.
 * @step: the step value to increase x for array @data.
 *
 * This routine changes the distribution for element @eleName, with
 * respect to element @lkName. @data is an array that gives the number @data[i]
 * of pairs @eleName - @lkName which distance is in (@init + @step * i).
 * 
 * Since: 3.6
 */
static void _addData(VisuUiCurveFrame *curve, const gchar *eleName, const gchar *lkName,
                     const guint *data, guint nSteps, float init, float step)
{
  guint max, i;
  guint *storage, *all, *ele1, *ele2;
  gchar *link1, *link2;

  g_return_if_fail(VISU_IS_UI_CURVE_FRAME(curve));
  g_return_if_fail(eleName && eleName[0] && lkName && lkName[0] && data);
  g_return_if_fail(curve->nSteps == nSteps &&
		   curve->init == init &&
		   curve->step == step);

  link1 = g_strdup_printf("%s"SPACER"%s", eleName, lkName);
  if (strcmp(eleName, lkName))
    link2 = g_strdup_printf("%s"SPACER"%s", lkName, eleName);
  else
    link2 = (gchar*)0;
  DBG_fprintf(stderr, "Gtk Curve: add data for link '%s'.\n", link1);

  ele1 = (guint*)g_hash_table_lookup(curve->data, eleName);
  ele2 = (guint*)g_hash_table_lookup(curve->data, lkName);
  all = (guint*)g_hash_table_lookup(curve->data, ALL);

  /* Create or replace the link data themselves. */
  storage = (guint*)g_hash_table_lookup(curve->data, link1);
  if (storage)
    {
      /* Remove it from the element data and all data. */
      g_return_if_fail(all && ele1 && ele2);
      DBG_fprintf(stderr, "Gtk Curve: remove previously stored"
                  " values for link '%s'.\n", link1);
      for (i = 0; i < nSteps; i++)
	{
	  ele1[i] -= storage[i];
	  if (ele1 != ele2)
	    ele2[i] -= storage[i];
	  all[i] -= storage[i];
	}
    }
  DBG_fprintf(stderr, "Gtk Curve: copy histo values.\n");
  storage = g_memdup(data, sizeof(int) * nSteps);
  g_hash_table_insert(curve->data, link1, (gpointer)storage);
  if (link2)
    {
      storage = g_memdup(data, sizeof(int) * nSteps);
      g_hash_table_insert(curve->data, link2, (gpointer)storage);
    }

  /* Add the data to the element data and all data. */
  if (!ele1)
    {
      ele1 = g_memdup(data, sizeof(int) * nSteps);
      g_hash_table_insert(curve->data, g_strdup(eleName), (gpointer)ele1);
    }
  else
    for (i = 0; i < nSteps - 1; i++)
      ele1[i] += data[i];
  if (link2)
    {
      if (!ele2)
	{
	  ele2 = g_memdup(data, sizeof(int) * nSteps);
	  g_hash_table_insert(curve->data, g_strdup(lkName), (gpointer)ele2);
	}
      else
	for (i = 0; i < nSteps - 1; i++)
	  ele2[i] += data[i];
    }
      
  max = 0;
  for (i = 0; i < nSteps - 1; i++)
    {
      max = MAX(max, data[i]);
      all[i] += data[i];
    }
  curve->heights[1] = MAX(curve->heights[1], max * 1.1f);
  DBG_fprintf(stderr, " | max %g becomes %g.\n", (double)max, curve->heights[1]);
}
/**
 * visu_ui_curve_frame_setNNodes:
 * @curve: a #VisuUiCurveFrame widget.
 * @ele: a string.
 * @n: a number.
 *
 * Modify the number of ... TODO
 *
 * Since: 3.6
 */
static void _setNNodes(VisuUiCurveFrame *curve, const gchar *ele, guint n)
{
  guint *data;

  g_return_if_fail(VISU_IS_UI_CURVE_FRAME(curve));

  data = (guint*)g_hash_table_lookup(curve->data, ele);
  g_return_if_fail(data);
  data[curve->nSteps - 1] = n;

  data = (guint*)g_hash_table_lookup(curve->data, ALL);
  g_return_if_fail(data);
  data[curve->nSteps - 1] += n;

  DBG_fprintf(stderr, "Gtk Curve: set %d nodes to '%s' (all = %d).\n",
	      n, ele, data[curve->nSteps - 1]);
}
/**
 * visu_ui_curve_frame_setStyle:
 * @curve: a #VisuUiCurveFrame object.
 * @style: a style id.
 *
 * Modify the rendering style of the graph.
 *
 * Since: 3.5
 *
 * Returns: TRUE if the style is actually changed.
 */
gboolean visu_ui_curve_frame_setStyle(VisuUiCurveFrame *curve, VisuUiCurveFrameStyle style)
{
  g_return_val_if_fail(VISU_IS_UI_CURVE_FRAME(curve), FALSE);

  if (curve->style == style)
    return FALSE;

  curve->style = style;

  curve->dirty = TRUE;
  gtk_widget_queue_draw(GTK_WIDGET(curve));

  return TRUE;
}
/**
 * visu_ui_curve_frame_setFilter:
 * @curve: a #VisuUiCurveFrame widget.
 * @filter: a string.
 *
 * Modify the filter used to draw all or single #VisuElement
 * distribution.
 *
 * Since: 3.6
 *
 * Returns: TRUE if filter is actually changed.
 */
gboolean visu_ui_curve_frame_setFilter(VisuUiCurveFrame *curve, VisuElement* filter)
{
  g_return_val_if_fail(VISU_IS_UI_CURVE_FRAME(curve), FALSE);

  if (curve->filter == filter)
    return FALSE;

  if (curve->filter)
    g_object_unref(curve->filter);
  curve->filter = filter;
  if (filter)
    g_object_ref(filter);

  g_object_notify_by_pspec(G_OBJECT(curve), _properties[FILTER_PROP]);
  g_object_notify_by_pspec(G_OBJECT(curve), _properties[LABEL_PROP]);

  curve->dirty = TRUE;
  gtk_widget_queue_draw(GTK_WIDGET(curve));

  return TRUE;
}
/**
 * visu_ui_curve_frame_setHighlightRange:
 * @curve: a #VisuUiCurveFrame widget.
 * @range: two floats.
 *
 * Modify the distance span that is used for highlight rendering and
 * calculation, see visu_ui_curve_frame_getMeanInRange().
 *
 * Since: 3.6
 *
 * Returns: TRUE if range is actually changed.
 */
gboolean visu_ui_curve_frame_setHighlightRange(VisuUiCurveFrame *curve, float range[2])
{
  gboolean updateMin, updateMax;

  g_return_val_if_fail(VISU_IS_UI_CURVE_FRAME(curve), FALSE);

  DBG_fprintf(stderr, "Gtk Curve: set highlight range to %gx%g.\n",
	      range[0], range[1]);

  updateMin = (curve->hlRange[0] != range[0]);
  updateMax = (curve->hlRange[1] != range[1]);

  if (!updateMin && !updateMax)
    return FALSE;

  if (range[0] < 0.f || range[1] <= range[0])
    return FALSE;

  curve->hlRange[0] = range[0];
  curve->hlRange[1] = range[1];
  if (updateMin)
    g_object_notify_by_pspec(G_OBJECT(curve), _properties[MIN_HL_PROP]);
  if (updateMax)
    g_object_notify_by_pspec(G_OBJECT(curve), _properties[MAX_HL_PROP]);
  g_object_notify_by_pspec(G_OBJECT(curve), _properties[INT_HL_PROP]);
  g_object_notify_by_pspec(G_OBJECT(curve), _properties[MEAN_HL_PROP]);

  curve->dirty = TRUE;
  gtk_widget_queue_draw(GTK_WIDGET(curve));

  return TRUE;
}
/**
 * visu_ui_curve_frame_getHighlightRange:
 * @curve: a #VisuUiCurveFrame widget.
 * @range: a location for two floats.
 *
 * Retrieves the distance span that is used for highlight rendering and
 * calculation, see visu_ui_curve_frame_setHighlightRange().
 *
 * Since: 3.6
 *
 * Returns: TRUE if range has been set already.
 */
gboolean visu_ui_curve_frame_getHighlightRange(VisuUiCurveFrame *curve, float range[2])
{
  g_return_val_if_fail(VISU_IS_UI_CURVE_FRAME(curve), FALSE);

  DBG_fprintf(stderr, "Gtk Curve: get highlight range of %gx%g.\n",
	      curve->hlRange[0], curve->hlRange[1]);
  if (curve->hlRange[0] < 0.f || curve->hlRange[1] <= curve->hlRange[0])
    return FALSE;

  range[0] = curve->hlRange[0];
  range[1] = curve->hlRange[1];
  return TRUE;
}
