/*   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>
#ifdef HAVE_LIBGTKGLEXT_X11_1_0
#include <gtk/gtkgl.h>
#endif
#include <unistd.h> /* For the access markers R_OK, W_OK ... */
#include <string.h>

#include "support.h"
#include "opengl.h"
#include "visu_gtk.h"
#include "visu_basic.h"
#include "visu_commandLine.h"
#include "visu_extension.h"
#include "visu_plugins.h"
#include "coreTools/toolShade.h"
#include "coreTools/toolConfigFile.h"
#include "extraFunctions/plane.h"
#include "extraFunctions/dataFile.h"
#include "panelModules/panelSurfaces.h"
#include "gtk_renderingWindowWidget.h"
#include "extraGtkFunctions/gtk_colorComboBoxWidget.h"
#include "OSOpenGL/visu_openGL.h"
#include "visu_extset.h"
#include "extensions/nodes.h"
#include "extensions/box.h"
#include "extensions/scale.h"

#include <pixmaps/icone-observe.xpm>

/**
 * SECTION: visu_gtk
 * @short_description: Basic GUI routines, standard
 * dialogs...
 *
 * <para>There are some common UI routines here. Error messages can be
 * displayed via dialogs, see visu_ui_raiseWarning()...</para>
 */

/* This hashtable associate a #RenderingMethod with a Gtk dialog to
   choose files. */
static GHashTable *visuGtkLoadMethods = NULL;

static GtkWindow *visuGtkPanel;
static GtkWindow *visuGtkRender;
static GtkWidget *visuGtkRenderArea = (GtkWidget*)0;

/* Local routines. */
static void initVisuGtk();


/**
 * visu_ui_raiseWarning:
 * @action: a string ;
 * @message: another string ;
 * @window: (allow-none): the parent window to raise the warning on.
 *
 * Raise a warning window with the action in bold and the message written
 * underneath.
 */
void visu_ui_raiseWarning(gchar *action, gchar *message, GtkWindow *window)
{
  GtkWidget *alert;
  gchar *str;
  
  if (!window)
    window = visuGtkRender;

  DBG_fprintf(stderr, "Visu Gtk: raise the error dialog (parent %p).\n",
	      (gpointer)window);

#if GTK_MAJOR_VERSION > 2 || GTK_MINOR_VERSION > 5
  str = action;
#else
  str = message;
#endif
  alert = gtk_message_dialog_new(GTK_WINDOW(window),
				 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
				 GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "%s", str);
  gtk_window_set_title(GTK_WINDOW(alert), _("V_Sim error message"));
#if GTK_MAJOR_VERSION > 2 || GTK_MINOR_VERSION > 5
  gtk_widget_set_name(alert, "error");
#else
  gtk_widget_set_name(alert, action);
#endif
#if GTK_MAJOR_VERSION > 2 || GTK_MINOR_VERSION > 5
  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(alert), "%s", message);
#endif
  gtk_widget_show_all(alert);

  /* block in a loop waiting for reply. */
  gtk_dialog_run(GTK_DIALOG(alert));
  gtk_widget_destroy(alert);
}
/**
 * visu_ui_raiseWarningLong:
 * @action: a string ;
 * @message: another string ;
 * @window: the parent window to raise the warning on.
 *
 * Same as visu_ui_raiseWarning() except that the message is displayed
 * in a text buffer, ideal for a log.
 */
void visu_ui_raiseWarningLong(gchar *action, gchar *message, GtkWindow *window)
{
  GtkWidget *alert;
  GtkWidget *text, *scroll;
  GtkTextBuffer *buf;

  if (!window)
    window = visuGtkRender;

  alert = gtk_message_dialog_new(GTK_WINDOW(window),
				 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
				 GTK_MESSAGE_WARNING,
				 GTK_BUTTONS_OK,
				 "%s", action);
  gtk_window_set_resizable(GTK_WINDOW(alert), TRUE);
  gtk_widget_set_name(alert, "error");
#if GTK_MAJOR_VERSION > 2 || GTK_MINOR_VERSION > 5
  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(alert),
					   _("Output errors:"));
#endif
  scroll = gtk_scrolled_window_new((GtkAdjustment*)0, (GtkAdjustment*)0);
  gtk_widget_set_size_request(scroll, 300, 200);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
				 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
				      GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(alert))),
		     scroll, TRUE, TRUE, 2);
  text = gtk_text_view_new();
  gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
  gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
  gtk_container_add(GTK_CONTAINER(scroll), text);
  buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW(text));
  gtk_text_buffer_set_text(GTK_TEXT_BUFFER(buf), message, -1);
  gtk_widget_show_all(alert);

  /* block in a loop waiting for reply. */
  gtk_dialog_run (GTK_DIALOG (alert));
  gtk_widget_destroy(alert);
}
/**
 * visu_ui_wait:
 *
 * It runs the Gtk event loop, flushing it before returning.
 */
void visu_ui_wait(void)
{
  while(gtk_events_pending())
    gtk_main_iteration();
}

/**
 * visu_ui_createInterface:
 * @panel: always NULL here.
 * @renderWindow: a location for a #GtkWindow ;
 * @renderArea: a location for a #GtkWidget.
 *
 * A convenient routine that creates a #VisuUiRenderingWindow alone. To
 * create also a command panel, visu_ui_main_class_createMain() should be
 * used instead.
 */
void visu_ui_createInterface(GtkWindow **panel,
                             GtkWindow **renderWindow, GtkWidget **renderArea)
{
  int width, height;

  /* Force the creation of the Scale class. */
  commandLineGet_XWindowGeometry(&width, &height);
  DBG_fprintf(stderr,"Visu Gtk: create a rendering window (%dx%d).\n",
	      width, height);
  *renderArea = visu_ui_rendering_window_new(width, height, FALSE, TRUE);
  *renderWindow = GTK_WINDOW(visu_ui_buildRenderingWindow(VISU_UI_RENDERING_WINDOW(*renderArea)));
  g_signal_connect(G_OBJECT(*renderWindow), "delete-event",
		   G_CALLBACK(gtk_main_quit), (gpointer)0);
  g_signal_connect(G_OBJECT(*renderWindow), "destroy-event",
		   G_CALLBACK(gtk_main_quit), (gpointer)0);
  g_object_bind_property(*renderArea, "label", *renderWindow, "title",
                         G_BINDING_SYNC_CREATE);
  gtk_widget_show(GTK_WIDGET(*renderWindow));
  *panel = (GtkWindow*)0;

  return;
}
/**
 * visu_ui_buildRenderingWindow:
 * @renderWindow: a #VisuUiRenderingWindow object.
 *
 * Create a #GtkWindow with V_sim render window icon and wmclass set
 * to "V_Sim:v_sim_render". It also set the accelerators of
 * @renderWindow to the newly created window and pack @renderWindow inside.
 *
 * Returns: (transfer full): a newly created #GtkWindow.
 *
 * Since: 3.7
 **/
GtkWidget* visu_ui_buildRenderingWindow(VisuUiRenderingWindow *renderWindow)
{
  GtkWidget *window;
  const gchar *window_name = "v_sim_render";
  const gchar *class_name = "V_Sim";
  GdkPixbuf *iconPixBuf;

  /* We create the rendering window. */
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), class_name);
#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION < 14
  gtk_window_set_has_resize_grip(GTK_WINDOW(window), FALSE);
#endif
#if GTK_MAJOR_VERSION < 3 || (GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION < 22)
  gtk_window_set_wmclass(GTK_WINDOW(window), window_name, class_name);
#else
  gtk_window_set_role(GTK_WINDOW(window), window_name);
#endif
  iconPixBuf = gdk_pixbuf_new_from_xpm_data((const char**)icone_observe_xpm);
  gtk_window_set_icon(GTK_WINDOW(window), iconPixBuf);
  g_object_unref(iconPixBuf);
  gtk_window_add_accel_group(GTK_WINDOW(window),
                             visu_ui_rendering_window_getAccelGroup(renderWindow));

  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(renderWindow));

  return window;
}

static void initVisuGtk(void)
{
  visuGtkLoadMethods = g_hash_table_new_full(g_direct_hash, g_direct_equal,
					     NULL, g_free);
}
/**
 * visu_ui_mainCreate:
 * @panelFunc: (scope call): function to be called to create the different windows.
 *
 * It initializses the GTK part of V_Sim. During this initialisation,
 * the @panelFunc is called. It should create all the windows needed
 * by V_Sim, like the command panel and the rendering area. The return
 * widget is the widget returned itself by @panelFunc. It must be the
 * main widget: it is the command panel if used, the window containing
 * the rendering area if no command panel or the rendering area itself
 * if no container window.
 */
void visu_ui_mainCreate(VisuUiInitWidgetFunc panelFunc)
{
  GError *error;

  g_return_if_fail(panelFunc);

  if (!visuGtkLoadMethods)
    initVisuGtk();

  panelFunc(&visuGtkPanel, &visuGtkRender, &visuGtkRenderArea);
  g_return_if_fail(visuGtkRender && visuGtkRenderArea);

  DBG_fprintf(stderr,"--- Initialising plugins ---\n");
  error = (GError*)0;
  visu_plugins_init(&error);
  if (error)
    {
      visu_ui_raiseWarningLong(_("Loading plug-ins"), error->message,
			       visuGtkRender);
      g_clear_error(&error);
    }

  if (!visu_basic_parseConfigFiles(&error))
    {
      visu_ui_raiseWarningLong(_("Reading the configuration files"), error->message,
			       visuGtkRender);
      g_clear_error(&error);
    }

  return;
}
/**
 * visu_ui_runCommandLine:
 * @data: a pointer to the command panel (see #VisuUiMain).
 *
 * Call the get routines from the command line module and deal with
 * them. This method is not aware of the panels and is intended to be
 * called only when the command panel is not used. In the opposite
 * case, use visu_ui_main_runCommandLine() instead.
 *
 * Returns: FALSE always.
 */
gboolean visu_ui_runCommandLine(gpointer data _U_)
{
  VisuGlNodeScene *scene;
  GError *error;

  scene = visu_ui_rendering_window_getGlScene(VISU_UI_RENDERING_WINDOW(visuGtkRenderArea));
  error = (GError*)0;
  if (!visu_gl_node_scene_applyCLI(scene, &error))
    {
      visu_ui_raiseWarning(_("Parsing command line"), error->message, (GtkWindow*)0);
      g_error_free(error);
    }
  
  return FALSE;
}
/**
 * visu_ui_createFilter:
 * @list: (element-type ToolFileFormat*): a GList of #ToolFileFormat ;
 * @fileChooser: a file chooser to associate filters with.
 *
 * Create a list of GtkFileFilter created from the given list of file formats
 * and attach it to the given @fileChooser.
 *
 * Returns: (element-type VisuUiFileFilter) (transfer full): a list of
 * #VisuUiFileFilter. This list should be freed after use.
 */
GList* visu_ui_createFilter(GList *list, GtkWidget *fileChooser)
{
  GtkFileFilter *filter, *filterAll;
  GList *tmpLst;
  const GList *tmpLst2;
  const char *name;
  VisuUiFileFilter *data;
  GList *returnedFilters;

  DBG_fprintf(stderr, "Visu Gtk: creating list of filters.\n");
  returnedFilters = (GList*)0;
  filterAll = gtk_file_filter_new ();
  gtk_file_filter_set_name(filterAll, _("All supported formats"));
  for (tmpLst = list; tmpLst; tmpLst = g_list_next(tmpLst))
    {
      filter = gtk_file_filter_new ();
      name = tool_file_format_getLabel((ToolFileFormat*)tmpLst->data);
      if (name)
	gtk_file_filter_set_name(filter, name);
      else
	gtk_file_filter_set_name(filter, _("No description"));
      for (tmpLst2 = tool_file_format_getFilePatterns((ToolFileFormat*)tmpLst->data);
           tmpLst2; tmpLst2 = g_list_next(tmpLst2))
	{
	  gtk_file_filter_add_pattern (filter, (gchar*)tmpLst2->data);
	  gtk_file_filter_add_pattern (filterAll, (gchar*)tmpLst2->data);
	}
      data = g_malloc(sizeof(VisuUiFileFilter));
      data->gtkFilter = filter;
      data->visuFilter = (ToolFileFormat*)tmpLst->data;
      returnedFilters = g_list_append(returnedFilters, (gpointer)data);
    }
  data = g_malloc(sizeof(VisuUiFileFilter));
  data->gtkFilter = filterAll;
  data->visuFilter = (ToolFileFormat*)0;
  returnedFilters = g_list_append(returnedFilters, (gpointer)data);
  filter = gtk_file_filter_new ();
  gtk_file_filter_set_name(filter, _("All files"));
  gtk_file_filter_add_pattern (filter, "*");
  data = g_malloc(sizeof(VisuUiFileFilter));
  data->gtkFilter = filter;
  data->visuFilter = (ToolFileFormat*)0;
  returnedFilters = g_list_append(returnedFilters, (gpointer)data);
  
  DBG_fprintf(stderr, "Gtk Main : attach list to the given filechooser.\n");
  tmpLst = returnedFilters;
  while(tmpLst)
    {
      gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fileChooser),
				  ((VisuUiFileFilter*)tmpLst->data)->gtkFilter);
      tmpLst = g_list_next(tmpLst);
    }
  gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fileChooser), filterAll);

  return returnedFilters;
}

/**
 * visu_ui_createPixbuf:
 * @filename: (type filename): a file name (must be a base name).
 *
 * Replace the create_pixbuf() routine from Glade. It looks only in
 * the default pixmap directory of V_Sim to find the given file.
 *
 * Returns: (transfer full): a newly created GdkPixbuf on success.
 */
GdkPixbuf* visu_ui_createPixbuf(const gchar *filename)
{
  gchar *pathname = NULL;
  GdkPixbuf *pixbuf;
  GError *error = NULL;

  g_return_val_if_fail(filename && filename[0], (GdkPixbuf*)0);

  pathname = g_build_filename(V_SIM_PIXMAPS_DIR, filename, NULL);

  pixbuf = gdk_pixbuf_new_from_file(pathname, &error);
  if (!pixbuf)
    {
      g_warning(_("failed to load pixbuf file '%s': %s\n"),
		pathname, error->message);
      g_error_free(error);
    }
  g_free(pathname);

  return pixbuf;
}

/**
 * visu_ui_getRenderWindow:
 *
 * A convenient function to get the rendering area window.
 *
 * Returns: (transfer none): a #GtkWindow.
 */
GtkWindow* visu_ui_getRenderWindow(void)
{
  return visuGtkRender;
}
/**
 * visu_ui_getPanel:
 *
 * A convenient function to get the command panel window.
 *
 * Returns: (transfer none): a #GtkWindow.
 */
GtkWindow* visu_ui_getPanel(void)
{
  return visuGtkPanel;
}
/**
 * visu_ui_getRenderWidget:
 *
 * A convenient function to get the rendering area widget.
 *
 * Returns: (transfer none): a #GtkWidget.
 */
GtkWidget* visu_ui_getRenderWidget(void)
{
  return visuGtkRenderArea;
}
/**
 * visu_ui_setRenderWidget:
 * @render: a #VisuUiRenderingWindow widget.
 *
 * A convenient function to set the rendering area widget.
 *
 * Since: 3.7
 */
void visu_ui_setRenderWidget(VisuUiRenderingWindow *render)
{
  visuGtkRenderArea = GTK_WIDGET(render);
}

/* Store a tree model to remember colors. */
#define COLOR_BOX_WIDTH  16
#define COLOR_BOX_HEIGHT 16
#define COLOR_BOX_BITS   8
/**
 * tool_color_get_stamp:
 * @color: a #ToolColor object ;
 * @alpha: a boolean.
 *
 * This method is used by #VisuUiColorCombobox object to create little stamps
 * representing the color. If the pixbuf of such stamps are needed, use
 * visu_ui_color_combobox_getPixbufFromColor() if the color is registered in an
 * already existing #VisuUiColorCombobox object or use this method to create
 * a new stamp.
 *
 * Returns: (transfer full): a pixbuf pointer corresponding to the
 * little image as shown on a @colorComboBox (use g_object_unref() to
 * free this pixbuf).
 *
 * Since: 3.7
 */
GdkPixbuf* tool_color_get_stamp(const ToolColor *color, gboolean alpha)
{
  GdkPixbuf *pixbufColorBox;
  int rowstride, x, y;
  guchar *pixels, *p;
  float grey;

  pixbufColorBox = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE,
				  COLOR_BOX_BITS,
				  COLOR_BOX_WIDTH,
				  COLOR_BOX_HEIGHT);
  rowstride = gdk_pixbuf_get_rowstride(pixbufColorBox);
  pixels = gdk_pixbuf_get_pixels(pixbufColorBox);
  for (y = 0; y < COLOR_BOX_HEIGHT; y++)
    for (x = 0; x < COLOR_BOX_WIDTH; x++)
      {
	p = pixels + y * rowstride + x * 3;
	if (x < COLOR_BOX_WIDTH / 2)
	  {
	    if (y < COLOR_BOX_HEIGHT / 2)
	      grey = 0.75;
	    else
	      grey = 0.5;
	  }
	else
	  {
	    if (y < COLOR_BOX_HEIGHT / 2)
	      grey = 0.5;
	    else
	      grey = 0.75;
	  }
	if (alpha)
	  {
	    p[0] = (guchar)((color->rgba[0] * color->rgba[3] +
			     (1. - color->rgba[3]) * grey) * 255.);
	    p[1] = (guchar)((color->rgba[1] * color->rgba[3] +
			     (1. - color->rgba[3]) * grey) * 255.);
	    p[2] = (guchar)((color->rgba[2] * color->rgba[3] +
			     (1. - color->rgba[3]) * grey) * 255.);
	  }
	else
	  {
	    p[0] = (guchar)(color->rgba[0] * 255.);
	    p[1] = (guchar)(color->rgba[1] * 255.);
	    p[2] = (guchar)(color->rgba[2] * 255.);
	  }
      }
  return pixbufColorBox;
}

/**
 * visu_ui_storeRecent:
 * @filename: a filename.
 *
 * Add @filename to the list of recent files.
 *
 * Since: 3.8
 **/
void visu_ui_storeRecent(const gchar *filename)
{
#if GTK_MAJOR_VERSION > 2 || GTK_MINOR_VERSION > 9
  GtkRecentManager *manager;
  gchar *uri, *absolut;
  GError *error;

  manager = gtk_recent_manager_get_default();
  error = (GError*)0;
  uri = g_filename_to_uri(filename, NULL, &error);
  if (error && error->code == G_CONVERT_ERROR_NOT_ABSOLUTE_PATH)
    {
      g_error_free(error);
      error = (GError*)0;
      absolut = tool_path_normalize(filename);
      uri = g_filename_to_uri(absolut, NULL, &error);
      g_free(absolut);
    }
  if (error)
    {
      g_warning("%s", error->message);
      g_error_free(error);
      return;
    }
  DBG_fprintf(stderr, "VisuUiRenderingWindow: add '%s' to recent files.\n", uri);
  gtk_recent_manager_add_item(manager, uri);
  g_free(uri);
#endif
}
