/*
 * Copyright (c) 2004 Jean-Yves Lefort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Jean-Yves Lefort nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include <glib/gi18n.h>
#include "sg-util.h"
#include "sgtk-find-dialog.h"
#include "sgtk-util.h"
#include "sgtk-stock.h"
#include "sgtk-hig.h"

/*** cpp *********************************************************************/

#define ENTRY(dialog) (GTK_BIN((dialog)->priv->combo_entry)->child)

/*** type definitions ********************************************************/

enum {
  PROP_0,
  PROP_TOKEN,
  PROP_HISTORY,
  PROP_CASE_SENSITIVE,
  PROP_WRAP_AROUND
};
    
enum {
  COLUMN_TOKEN,
  N_COLUMNS
};
    
struct _sGtkFindDialogPrivate
{
  GtkWidget	*combo_entry;
  GtkWidget	*case_sensitive;
  GtkWidget	*wrap_around;
  GtkListStore	*history_store;
};

/*** variable declarations ***************************************************/

static GObjectClass *parent_class = NULL;

/*** function declarations ***************************************************/

static void sgtk_find_dialog_class_init	(sGtkFindDialogClass	*class);
static void sgtk_find_dialog_init	(sGtkFindDialog		*dialog);
static void sgtk_find_dialog_finalize	(GObject		*object);

static void sgtk_find_dialog_entry_changed_h          (GtkEditable	*editable,
						       gpointer		user_data);
static void sgtk_find_dialog_case_sensitive_toggled_h (GtkToggleButton	*togglebutton,
						       gpointer		user_data);
static void sgtk_find_dialog_wrap_around_toggled_h    (GtkToggleButton	*togglebutton,
						       gpointer		user_data);

static void sgtk_find_dialog_response_h	(GtkDialog		*dialog,
					 int			response,
					 gpointer		data);

static void sgtk_find_dialog_set_property	(GObject	*object,
						 unsigned int	prop_id,
						 const GValue	*value,
						 GParamSpec	*pspec);
static void sgtk_find_dialog_get_property	(GObject	*object,
						 unsigned int	prop_id,
						 GValue		*value,
						 GParamSpec	*pspec);

/*** implementation **********************************************************/

GType
sgtk_find_dialog_get_type (void)
{
  static GType find_dialog_type = 0;
  
  if (! find_dialog_type)
    {
      static const GTypeInfo find_dialog_info = {
	sizeof(sGtkFindDialogClass),
	NULL,
	NULL,
	(GClassInitFunc) sgtk_find_dialog_class_init,
	NULL,
	NULL,
	sizeof(sGtkFindDialog),
	0,
	(GInstanceInitFunc) sgtk_find_dialog_init,
      };
      
      find_dialog_type = g_type_register_static(SGTK_TYPE_DIALOG,
						"sGtkFindDialog",
						&find_dialog_info,
						0);
    }

  return find_dialog_type;
}

static void
sgtk_find_dialog_class_init (sGtkFindDialogClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS(class);

  parent_class = g_type_class_peek_parent(class);

  g_type_class_add_private(class, sizeof(sGtkFindDialogPrivate));

  object_class->finalize = sgtk_find_dialog_finalize;
  object_class->set_property = sgtk_find_dialog_set_property;
  object_class->get_property = sgtk_find_dialog_get_property;

  g_object_class_install_property(object_class,
				  PROP_TOKEN,
				  g_param_spec_string("token",
						      _("Token"),
						      _("The current token"),
						      NULL,
						      G_PARAM_WRITABLE | G_PARAM_READABLE));
  g_object_class_install_property(object_class,
				  PROP_HISTORY,
				  g_param_spec_pointer("history",
						       _("History"),
						       _("The tokens history list"),
						       G_PARAM_WRITABLE | G_PARAM_READABLE));
  g_object_class_install_property(object_class,
				  PROP_CASE_SENSITIVE,
				  g_param_spec_boolean("case-sensitive",
						       _("Case sensitive"),
						       _("The state of the case sensitive knob"),
						       FALSE,
						       G_PARAM_WRITABLE | G_PARAM_READABLE));
  g_object_class_install_property(object_class,
				  PROP_WRAP_AROUND,
				  g_param_spec_boolean("wrap-around",
						       _("Wrap around"),
						       _("The state of the wrap around knob"),
						       FALSE,
						       G_PARAM_WRITABLE | G_PARAM_READABLE));
}

static void
sgtk_find_dialog_init (sGtkFindDialog *dialog)
{
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *label;

  dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE(dialog, SGTK_TYPE_FIND_DIALOG, sGtkFindDialogPrivate);

  gtk_window_set_title(GTK_WINDOW(dialog), _("Find"));
  gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);

  gtk_dialog_add_buttons(GTK_DIALOG(dialog),
			 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
			 SGTK_STOCK_PREVIOUS, SGTK_RESPONSE_PREVIOUS,
			 SGTK_STOCK_NEXT, SGTK_RESPONSE_NEXT,
			 NULL);
  gtk_dialog_set_default_response(GTK_DIALOG(dialog), SGTK_RESPONSE_NEXT);

  vbox = gtk_vbox_new(FALSE, SGTK_HIG_CONTROL_SPACING);
  
  hbox = gtk_hbox_new(FALSE, SGTK_HIG_CONTROL_LABEL_SPACING);
  label = gtk_label_new_with_mnemonic(_("_Find:"));

  dialog->priv->combo_entry = gtk_combo_box_entry_new();
  gtk_label_set_mnemonic_widget(GTK_LABEL(label), dialog->priv->combo_entry);

  dialog->priv->history_store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING);
  gtk_combo_box_set_model(GTK_COMBO_BOX(dialog->priv->combo_entry), GTK_TREE_MODEL(dialog->priv->history_store));
  gtk_combo_box_entry_set_text_column(GTK_COMBO_BOX_ENTRY(dialog->priv->combo_entry), COLUMN_TOKEN);

  gtk_entry_set_activates_default(GTK_ENTRY(ENTRY(dialog)), TRUE);

  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(hbox), dialog->priv->combo_entry, TRUE, TRUE, 0);

  dialog->priv->case_sensitive = gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
  dialog->priv->wrap_around = gtk_check_button_new_with_mnemonic(_("_Wrap around"));

  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), dialog->priv->case_sensitive, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), dialog->priv->wrap_around, FALSE, FALSE, 0);

  gtk_container_add(GTK_CONTAINER(SGTK_DIALOG(dialog)->contents), vbox);
  gtk_widget_show_all(vbox);

  g_signal_connect(ENTRY(dialog), "changed", G_CALLBACK(sgtk_find_dialog_entry_changed_h), dialog);
  g_signal_connect(dialog->priv->case_sensitive, "toggled", G_CALLBACK(sgtk_find_dialog_case_sensitive_toggled_h), dialog);
  g_signal_connect(dialog->priv->wrap_around, "toggled", G_CALLBACK(sgtk_find_dialog_wrap_around_toggled_h), dialog);
  g_signal_connect(dialog, "response", G_CALLBACK(sgtk_find_dialog_response_h), NULL);
}

static void
sgtk_find_dialog_finalize (GObject *object)
{
  sGtkFindDialog *dialog = SGTK_FIND_DIALOG(object);

  g_object_unref(dialog->priv->history_store);

  parent_class->finalize(object);
}

static void
sgtk_find_dialog_entry_changed_h (GtkEditable *editable,
				  gpointer user_data)
{
  sGtkFindDialog *dialog = user_data;

  g_object_notify(G_OBJECT(dialog), "token");
}

static void
sgtk_find_dialog_case_sensitive_toggled_h (GtkToggleButton *togglebutton,
					   gpointer user_data)
{
  sGtkFindDialog *dialog = user_data;

  g_object_notify(G_OBJECT(dialog), "case-sensitive");
}

static void
sgtk_find_dialog_wrap_around_toggled_h (GtkToggleButton *togglebutton,
					gpointer user_data)
{
  sGtkFindDialog *dialog = user_data;

  g_object_notify(G_OBJECT(dialog), "wrap-around");
}

static void
sgtk_find_dialog_response_h (GtkDialog *dialog,
			     int response,
			     gpointer data)
{
  sGtkFindDialog *find_dialog = SGTK_FIND_DIALOG(dialog);

  switch (response)
    {
    case SGTK_RESPONSE_PREVIOUS:
    case SGTK_RESPONSE_NEXT:
      {
	const char *token = gtk_entry_get_text(GTK_ENTRY(ENTRY(find_dialog)));
	gboolean valid;
	gboolean already_exists = FALSE;
	GtkTreeIter iter;

	valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(find_dialog->priv->history_store), &iter);
	while (valid)
	  {
	    char *this_token;

	    gtk_tree_model_get(GTK_TREE_MODEL(find_dialog->priv->history_store), &iter, COLUMN_TOKEN, &this_token, -1);
	    already_exists = ! sg_utf8_strcmp(token, this_token);
	    g_free(this_token);

	    if (already_exists)
	      break;

	    valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(find_dialog->priv->history_store), &iter);
	  }

	if (already_exists)
	  gtk_list_store_move_after(find_dialog->priv->history_store, &iter, NULL);
	else
	  {
	    gtk_list_store_prepend(find_dialog->priv->history_store, &iter);
	    gtk_list_store_set(find_dialog->priv->history_store, &iter, COLUMN_TOKEN, gtk_entry_get_text(GTK_ENTRY(ENTRY(find_dialog))), -1);
	  }

	g_object_notify(G_OBJECT(dialog), "history");
      }
      break;
    }
}

static void
sgtk_find_dialog_set_property (GObject *object,
			       unsigned int prop_id,
			       const GValue *value,
			       GParamSpec *pspec)
{
  sGtkFindDialog *dialog = SGTK_FIND_DIALOG(object);

  switch (prop_id)
    {
    case PROP_TOKEN:
      gtk_entry_set_text(GTK_ENTRY(ENTRY(dialog)), g_value_get_string(value));
      break;

    case PROP_HISTORY:
      {
	GSList *history = g_value_get_pointer(value);
	GSList *l;

	gtk_list_store_clear(dialog->priv->history_store);
	SG_LIST_FOREACH(l, history)
	  {
	    GtkTreeIter iter;

	    gtk_list_store_append(dialog->priv->history_store, &iter);
	    gtk_list_store_set(dialog->priv->history_store, &iter, COLUMN_TOKEN, l->data, -1);
	  }
      }
      break;

    case PROP_CASE_SENSITIVE:
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->priv->case_sensitive), g_value_get_boolean(value));
      break;

    case PROP_WRAP_AROUND:
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->priv->wrap_around), g_value_get_boolean(value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
      break;
    }
}

void
sgtk_find_dialog_set_token (sGtkFindDialog *dialog, const char *token)
{
  g_return_if_fail(SGTK_IS_FIND_DIALOG(dialog));

  g_object_set(G_OBJECT(dialog), "token", token, NULL);
}

void
sgtk_find_dialog_set_history (sGtkFindDialog *dialog, GSList *history)
{
  g_return_if_fail(SGTK_IS_FIND_DIALOG(dialog));

  g_object_set(G_OBJECT(dialog), "history", history, NULL);
}

void
sgtk_find_dialog_set_case_sensitive (sGtkFindDialog *dialog,
				     gboolean case_sensitive)
{
  g_return_if_fail(SGTK_IS_FIND_DIALOG(dialog));

  g_object_set(G_OBJECT(dialog), "case-sensitive", case_sensitive, NULL);
}

void
sgtk_find_dialog_set_wrap_around (sGtkFindDialog *dialog,
				  gboolean wrap_around)
{
  g_return_if_fail(SGTK_IS_FIND_DIALOG(dialog));

  g_object_set(G_OBJECT(dialog), "wrap-around", wrap_around, NULL);
}

static void
sgtk_find_dialog_get_property (GObject *object,
			       unsigned int prop_id,
			       GValue *value,
			       GParamSpec *pspec)
{
  sGtkFindDialog *dialog = SGTK_FIND_DIALOG(object);

  switch (prop_id)
    {
    case PROP_TOKEN:
      g_value_set_string(value, sgtk_find_dialog_get_token(dialog));
      break;

    case PROP_HISTORY:
      g_value_set_pointer(value, sgtk_find_dialog_get_history(dialog));
      break;

    case PROP_CASE_SENSITIVE:
      g_value_set_boolean(value, sgtk_find_dialog_get_case_sensitive(dialog));
      break;

    case PROP_WRAP_AROUND:
      g_value_set_boolean(value, sgtk_find_dialog_get_wrap_around(dialog));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
      break;
    }
}

const char *
sgtk_find_dialog_get_token (sGtkFindDialog *dialog)
{
  g_return_val_if_fail(SGTK_IS_FIND_DIALOG(dialog), NULL);

  return gtk_entry_get_text(GTK_ENTRY(ENTRY(dialog)));
}

GSList *
sgtk_find_dialog_get_history (sGtkFindDialog *dialog)
{
  GSList *history = NULL;
  gboolean valid;
  GtkTreeIter iter;

  g_return_val_if_fail(SGTK_IS_FIND_DIALOG(dialog), NULL);

  valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog->priv->history_store), &iter);
  while (valid)
    {
      char *token;

      gtk_tree_model_get(GTK_TREE_MODEL(dialog->priv->history_store), &iter, COLUMN_TOKEN, &token, -1);
      history = g_slist_append(history, token);

      valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(dialog->priv->history_store), &iter);
    }

  return history;
}

gboolean
sgtk_find_dialog_get_case_sensitive (sGtkFindDialog *dialog)
{
  g_return_val_if_fail(SGTK_IS_FIND_DIALOG(dialog), FALSE);

  return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->priv->case_sensitive));
}

gboolean
sgtk_find_dialog_get_wrap_around (sGtkFindDialog *dialog)
{
  g_return_val_if_fail(SGTK_IS_FIND_DIALOG(dialog), FALSE);

  return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->priv->wrap_around));
}

GtkWidget *
sgtk_find_dialog_new (GtkWindow *parent)
{
  GtkWidget *dialog;

  dialog = g_object_new(SGTK_TYPE_FIND_DIALOG, NULL);
  if (parent)
    gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);

  return dialog;
}

void
sgtk_find_dialog_set_previous_sensitive (sGtkFindDialog *dialog,
					 gboolean sensitive)
{
  g_return_if_fail(SGTK_IS_FIND_DIALOG(dialog));

  gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), SGTK_RESPONSE_PREVIOUS, sensitive);
}

void
sgtk_find_dialog_set_next_sensitive (sGtkFindDialog *dialog,
				     gboolean sensitive)
{
  g_return_if_fail(SGTK_IS_FIND_DIALOG(dialog));

  gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), SGTK_RESPONSE_NEXT, sensitive);
}
