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

#include <string.h>

#include <glib.h>
#include <pango/pango.h>

#include <visu_tools.h>

/**
 * SECTION:toolColor
 * @short_description: Simple handling and storage of RGBA colours.
 *
 * <para>This file defines a basic structure to store colours (not
 * using the GDK one beca use V_Sim core should not rely on GDK and
 * GTK): #ToolColor. Several transformations are possible on a colour,
 * going from and to RGB encoding. Use tool_color_convertHSVtoRGB() and
 * tool_color_convertHSLtoRGB() to do that.</para>
 *
 * <para>This file gives also the capability to store known colours in
 * a list. Use methods such as tool_color_addColor() or
 * tool_color_addFloatRGBA(). Them, one can access to stored colours, using
 * tool_color_getByValues() or tool_color_getByColor().</para>
 */

static ToolColor* color_copy(const ToolColor *boxed);
static gint _compare(const ToolColor *color1, const ToolColor *color2);

/******************/
/* Storing colors */
/******************/
static ToolPool *colorPool = NULL;

#define N_BRIGHT_COLORS 20
static ToolColor brightColors[N_BRIGHT_COLORS] =
  {{{1,0,0, 1.f}, "", (gpointer)0},
   {{0,1,0, 1.f}, "", (gpointer)0},
   {{0,0,1, 1.f}, "", (gpointer)0},
   {{1,0,1, 1.f}, "", (gpointer)0},
   {{0,1,1, 1.f}, "", (gpointer)0},
   {{1,1,0, 1.f}, "", (gpointer)0},
   {{1,0.5,0, 1.f}, "", (gpointer)0},
   {{0.5,0,0.5, 1.f}, "", (gpointer)0},
   {{0,0.5,0.5, 1.f}, "", (gpointer)0},
   {{0.5,0.5,0, 1.f}, "", (gpointer)0},
   {{0.5,0,0, 1.f}, "", (gpointer)0},
   {{0,0.5,0, 1.f}, "", (gpointer)0},
   {{0,0,0.5, 1.f}, "", (gpointer)0},
   {{0.5,0.5,0.5, 1.f}, "", (gpointer)0},
   {{1,0,0.5, 1.f}, "", (gpointer)0},
   {{0.5,1,0, 1.f}, "", (gpointer)0},
   {{0.5,0,1, 1.f}, "", (gpointer)0},
   {{0,1,0.5, 1.f}, "", (gpointer)0},
   {{0,0.5,1, 1.f}, "", (gpointer)0},
   {{0,0,0, 1.f}, "", (gpointer)0}};

/******************/
/* Storing colors */
/******************/
static void _initPool()
{
  colorPool = tool_pool_new(TOOL_TYPE_COLOR, (GCompareFunc)_compare);
}
/**
 * tool_color_getStorage:
 *
 * Give access to the #ToolPool instance storing all the colors.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a #ToolPool object.
 **/
ToolPool* tool_color_getStorage()
{
  if (!colorPool)
    _initPool();
  return colorPool;
}
/**
 * tool_color_new_bright:
 * @id: an index
 *
 * V_Sim has a list of 20 bright colors. One can get one by calling
 * this routine. The @id is taken modulo the number of available colors.
 *
 * Since: 3.7
 *
 * Returns: (transfer none): a bright color.
 **/
const ToolColor* tool_color_new_bright(guint id)
{
  return brightColors + (id % N_BRIGHT_COLORS);
}

/**
 * tool_color_get_type:
 *
 * Create and retrieve a #GType for a #ToolColor object.
 *
 * Since: 3.7
 *
 * Returns: a new type for #ToolColor structures.
 */
GType tool_color_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("ToolColor", 
                                                    (GBoxedCopyFunc)color_copy,
                                                    (GBoxedFreeFunc)g_free);
  return g_define_type_id;
}

/**
 * tool_color_new:
 * @rgba: (in) (array fixed-size=4): four values between 0. and
 * 1. that represent [Red, Green, Blue, Alpha].
 * 
 * Create a new color with initial values given as arguments.
 *
 * Returns: (transfer full): a new allocated #ToolColor (use g_free() to free it).
 */
ToolColor* tool_color_new(const float rgba[4])
{
  ToolColor *color;
  int i;

  color = g_malloc(sizeof(ToolColor));
  for (i = 0; i< 4; i++)
    color->rgba[i] = CLAMP(rgba[i], 0.f, 1.f);
  color->userData = (gpointer)0;
  return color;
}

static gint _compare(const ToolColor *color1, const ToolColor *color2)
{
  if (!color1 && !color2)
    return 0;
  if (!color1)
    return -1;
  if (!color2)
    return +1;
  if (color1->rgba[0] == color2->rgba[0] &&
      color1->rgba[1] == color2->rgba[1] &&
      color1->rgba[2] == color2->rgba[2] &&
      color1->rgba[3] == color2->rgba[3])
    return 0;
  if (color1 < color2)
    return -1;
  else
    return +1;
}

/**
 * tool_color_equal:
 * @color1: a #ToolColor ;
 * @color2: an other #ToolColor.
 *
 * Test if the two colours are the same.
 *
 * Returns: TRUE if the @rgba attributes are the same.
 */
gboolean tool_color_equal(const ToolColor *color1, const ToolColor *color2)
{
  return (_compare(color1, color2) == 0);
}

/**
 * tool_color_getByValues:
 * @pos: (out caller-allocates) (allow-none): an allocated int to store the position of the found color ;
 * @red: a value between 0. and 1. ;
 * @green: a value between 0. and 1. ;
 * @blue: a value between 0. and 1. ;
 * @alpha: a value between 0. and 1..
 *
 * This method is used to look for a specific color in the stored list. The argument @pos
 * is -1 if nothing is found or stores the position (beginning at 0) of the found color.
 *
 * Returns: (transfer none): the found color, or NULL if none exists.
 */
ToolColor* tool_color_getByValues(int *pos, float red, float green, float blue, float alpha)
{
  gint found;
  ToolColor color;

  color.rgba[0] = CLAMP(red, 0.f, 1.f);
  color.rgba[1] = CLAMP(green, 0.f, 1.f);
  color.rgba[2] = CLAMP(blue, 0.f, 1.f);
  color.rgba[3] = CLAMP(alpha, 0.f, 1.f);

  if (!colorPool)
    _initPool();

  found = tool_pool_index(colorPool, &color);
  if (pos)
    *pos = found;

  if (found < 0)
    return (ToolColor*)0;
  else
    return (ToolColor*)tool_pool_getById(colorPool, found);
}
static ToolColor* color_copy(const ToolColor *boxed)
{
  ToolColor *color;

  if (!boxed)
    return (ToolColor*)0;

  color = g_malloc(sizeof(ToolColor));
  tool_color_copy(color, boxed);
  return color;
}
/**
 * tool_color_copy:
 * @color: an allocated #ToolColor object to receive values ;
 * @color_old: a #ToolColor to read the values from.
 * 
 * This method copies all values from @color_old to @color.
 */
void tool_color_copy(ToolColor *color, const ToolColor *color_old)
{
  g_return_if_fail(color && color_old);

  memcpy(color->rgba, color_old->rgba, sizeof(float) * 4);
  color->userData = color_old->userData;
}
/**
 * tool_color_addFloatRGBA:
 * @rgba: four values between 0. and 1. that represent [Red, Green, Blue, Alpha] ;
 * @position: (out caller-allocates) (allow-none): an int pointer to store the position
 *  of the returned colour.
 *
 * This method adds a new color in the list of stored colors with the given values.
 * If it already exits it returns the pointer of that color.
 *
 * Returns: (transfer none): a newly created #ToolColor or the already existing one.
 */
ToolColor* tool_color_addFloatRGBA(const float rgba[4], int *position)
{
  ToolColor color, *found;

  found = tool_color_getByValues(position, rgba[0], rgba[1], rgba[2], rgba[3]);
  if (found)
    {
      DBG_fprintf(stderr, "Visu Tools : color already exist, no color added.\n");
      return found;
    }
  
  color.rgba[0] = CLAMP(rgba[0], 0.f, 1.f);
  color.rgba[1] = CLAMP(rgba[1], 0.f, 1.f);
  color.rgba[2] = CLAMP(rgba[2], 0.f, 1.f);
  color.rgba[3] = CLAMP(rgba[3], 0.f, 1.f);

  found = (ToolColor*)tool_pool_add(colorPool, &color);
  if (position)
    *position = tool_pool_index(colorPool, found);
  DBG_fprintf(stderr, "Tool Color: adding a new color at %p (%d).\n",
	      (gpointer)found, (position) ? *position : -1);
  return found;
}
/**
 * tool_color_addIntRGBA:
 * @rgba: four values between 0 and 255 that represent [Red, Green, Blue, Alpha].
 *
 * This method adds a new color in the list of stored colors with the given values.
 *
 * Returns: (transfer none): a newly created #ToolColor or the already existing one.
 */
ToolColor* tool_color_addIntRGBA(int rgba[4])
{
  float rgbaf[4];
  int i;

  for (i = 0; i < 4; i++)
    g_return_val_if_fail(rgba[i] >= 0 && rgba[i] < 256, (ToolColor*)0);

  for (i = 0; i < 4; i++)
    rgbaf[i] = (float)CLAMP(rgba[i], 0.f, 255.f) / 255.;
  return tool_color_addFloatRGBA(rgbaf, NULL);
}

/**
 * tool_color_fromStr:
 * @str: a string.
 * @pos: (out caller-allocates) (allow-none): a location.
 *
 * This method parse @str with the format %RRGGBB or %RRGGBBAA to
 * create a #ToolColor. This color is added to the pool, see
 * tool_color_getStorage().
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a corresponding #ToolColor, creating it
 * if necessary.
 **/
const ToolColor* tool_color_fromStr(const gchar *str, int *pos)
{
  guint repr[4];
  gfloat rgba[4];
  ToolColor *color;

  g_return_val_if_fail(str, (ToolColor*)0);
  
  if (str[0] != '#' || strlen(str) < 7)
    return (ToolColor*)0;

  if (sscanf(str + 1, "%2x%2x%2x", repr, repr + 1, repr + 2) != 3)
    return (ToolColor*)0;
  if (sscanf(str + 7, "%2x", repr + 3) != 1)
    repr[3] = 255;

  rgba[0] = (float)repr[0] / 255.;
  rgba[1] = (float)repr[1] / 255.;
  rgba[2] = (float)repr[2] / 255.;
  rgba[3] = (float)repr[3] / 255.;
  color = tool_color_getByValues(pos, rgba[0], rgba[1], rgba[2], rgba[3]);
  if (!color)
    color = tool_color_addFloatRGBA(rgba, pos);
  return color;
}
/**
 * tool_color_fromName:
 * @name: a string.
 * @pos: (out caller-allocates) (allow-none): a location.
 *
 * Parse a name like 'black' and generate a colour. This color is
 * added to the pool, see tool_color_getStorage().
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a corresponding #ToolColor, creating it
 * if necessary.
 **/
const ToolColor* tool_color_fromName(const gchar *name, int *pos)
{
  PangoColor color;
  gfloat rgba[4];
  ToolColor *out;

  if (!pango_color_parse(&color, name))
    {
      g_warning(_("cannot read a color from '%s' (name, #rgb, #rrggbb ... awaited).\n"),
                name);
      color.red   = 0;
      color.green = 0;
      color.blue  = 0;
    }
  rgba[0] = (float)color.red   / (float)G_MAXUINT16;
  rgba[1] = (float)color.green / (float)G_MAXUINT16;
  rgba[2] = (float)color.blue  / (float)G_MAXUINT16;
  rgba[3] = 1.f;
  out = tool_color_getByValues(pos, rgba[0], rgba[1], rgba[2], rgba[3]);
  if (!out)
    out = tool_color_addFloatRGBA(rgba, pos);
  return out;
}
/**
 * tool_color_asStr:
 * @color: a #ToolColor object.
 *
 * Gives a string representation of @color. The string is computed at
 * each call.
 *
 * Since: 3.8
 *
 * Returns: a string representing @color.
 **/
const gchar* tool_color_asStr(ToolColor *color)
{
  g_return_val_if_fail(color, (gchar*)0);

  sprintf(color->repr, "#%02x%02x%02x%02x", (guint)(color->rgba[0] * 255.),
           (guint)(color->rgba[1] * 255.), (guint)(color->rgba[2] * 255.),
          (guint)(color->rgba[3] * 255.));
  return color->repr;
}

/**
 * tool_color_convertHSVtoRGB:
 * @rgb: an allocated 3 elements array to receive the RGB values ;
 * @hsv: a 3 elements array to retrieve the HSV values from.
 *
 * This methods convert a HSV color to a RGB one.
 */
void tool_color_convertHSVtoRGB(float* rgb, const float* hsv)
{
  float var_h, var_0, var_1, var_2, var_3;
  int var_i;
  
  g_return_if_fail(rgb && hsv);

  if (hsv[1] == 0)
    {
      rgb[0] = hsv[2];
      rgb[1] = hsv[2];
      rgb[2] = hsv[2];
    }
  else
    {
      var_h = hsv[0] * 6.;
      var_i = (int)var_h;
      var_0 = hsv[2];
      var_1 = hsv[2] * (1. - hsv[1]);
      var_2 = hsv[2] * (1. - hsv[1] * (var_h - (float)var_i));
      var_3 = hsv[2] * (1. - hsv[1] * (1. - (var_h - (float)var_i)));
      switch (var_i % 6)
	{
	case 0:
	  rgb[0] = var_0;
	  rgb[1] = var_3;
	  rgb[2] = var_1;
	  break;
	case 1:
	  rgb[0] = var_2;
	  rgb[1] = var_0;
	  rgb[2] = var_1;
	  break;
	case 2:
	  rgb[0] = var_1;
	  rgb[1] = var_0;
	  rgb[2] = var_3;
	  break;
	case 3:
	  rgb[0] = var_1;
	  rgb[1] = var_2;
	  rgb[2] = var_0;
	  break;
	case 4:
	  rgb[0] = var_3;
	  rgb[1] = var_1;
	  rgb[2] = var_0;
	  break;
	case 5:
	  rgb[0] = var_0;
	  rgb[1] = var_1;
	  rgb[2] = var_2;
	  break;
	}
    }
}

float Hue_2_RGB(float v1, float v2, float vH)
{
  /* from www.easyrgb.com */
  if (vH < 0) vH += 1;
  if (vH > 1) vH -= 1;
  if ((6*vH) < 1) 
    return (v1+(v2-v1)*6*vH);
  if ((2*vH) < 1) 
    return v2;
  if ((3*vH) < 2) 
    return (v1+(v2-v1)*((2./3.)-vH)*6);
  return v1;
}

/**
 * tool_color_convertHSLtoRGB:
 * @rgb: an allocated 3 elements array to receive the RGB values ;
 * @hsl: a 3 elements array to retrieve the HSL values from.
 *
 * This methods convert a HSL color to a RGB one.
 */
void tool_color_convertHSLtoRGB(float *rgb, const float *hsl)
{
  /* from www.easyrgb.com */
  if (hsl[1] == 0)                       /*HSL values = 0 ÷ 1*/
    {
      rgb[0] = hsl[2];                      /*RGB results = 0 ÷ 255*/
      rgb[1] = hsl[2];
      rgb[2] = hsl[2];
    }

  else
    {
      float var_1, var_2;
      if (hsl[2] < 0.5) 
	var_2 = hsl[2] * (1 + hsl[1]);		       
      else           
	var_2 = (hsl[2] + hsl[1]) - (hsl[1] * hsl[2]);
					
      var_1 = 2 * hsl[2] - var_2;

      rgb[0] = Hue_2_RGB( var_1, var_2, hsl[0] + ( 1. / 3. ) );
      rgb[1] = Hue_2_RGB( var_1, var_2, hsl[0] );
      rgb[2] = Hue_2_RGB( var_1, var_2, hsl[0] - ( 1. / 3. ) );
    }  
}
/**
 * tool_color_convertRGBtoHSL:
 * @hsl: three float to store the HSL value ;
 * @rgb: three floats giving the RGB values.
 *
 * Convert a RGB colour into a HSL one.
 */
void tool_color_convertRGBtoHSL(float *hsl, const float *rgb)
{
  float var_Min, var_Max, del_Max, del_R, del_G, del_B;

  var_Min = MIN(MIN( rgb[0], rgb[1]), rgb[2] );
  var_Max = MAX(MAX( rgb[0], rgb[1]), rgb[2] );
  del_Max = var_Max - var_Min;
  
  hsl[2] = ( var_Max + var_Min ) / 2;

  if ( del_Max == 0 )
    {
      hsl[0] = 0.f;
      hsl[1] = 0.f;
    }
  else
    {
      if ( hsl[2] < 0.5f )
	hsl[1] = del_Max / ( var_Max + var_Min );
      else
	hsl[1] = del_Max / ( 2.f - var_Max - var_Min );

      del_R = ( ( ( var_Max - rgb[0] ) / 6 ) + ( del_Max / 2 ) ) / del_Max;
      del_G = ( ( ( var_Max - rgb[1] ) / 6 ) + ( del_Max / 2 ) ) / del_Max;
      del_B = ( ( ( var_Max - rgb[2] ) / 6 ) + ( del_Max / 2 ) ) / del_Max;

      if ( rgb[0] == var_Max )
	hsl[0] = del_B - del_G;
      else if ( rgb[1] == var_Max )
	hsl[0] = ( 1.f / 3.f ) + del_R - del_B;
      else if ( rgb[2] == var_Max )
	hsl[0] = ( 2.f / 3.f ) + del_G - del_R;
 
      if ( hsl[0] < 0.f )
	hsl[0] += 1;
      if ( hsl[0] > 1.f )
	hsl[0] -= 1;
    }
}

/**
 * tool_color_invertRGBA:
 * @inv: a location to store the inverted colour.
 * @rgba: a colour defined in RGBA.
 *
 * Invert the colour on the stack.
 *
 * Since: 3.8
 **/
void tool_color_invertRGBA(float inv[4], const float rgba[4])
{
  inv[0] = 1.f - rgba[0];
  inv[1] = 1.f - rgba[1];
  inv[2] = 1.f - rgba[2];
  inv[3] = rgba[3];
}

static gpointer material_copy(gpointer boxed)
{
  return g_memdup(boxed, sizeof(gfloat) * TOOL_MATERIAL_N_VALUES);
}
/**
 * tool_material_get_type:
 *
 * Create and retrieve a #GType for a #ToolMaterial object.
 *
 * Since: 3.8
 *
 * Returns: a new type for #ToolVector structures.
 */
GType tool_material_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("ToolMaterial",
                                                    material_copy, g_free);
  return g_define_type_id;
}
