/*
 * Copyright (C) 2002-2017 the xine-project
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 *
 * settings implementation
 */

#include "globals.h"

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <glib.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>

#include "settings.h"
#include "ui.h"
#include "utils.h"

struct slider_s {
  const char *label, *icon;
  control_adjustment_t type;
};

struct slider_group_s {
  const char *label;
  const struct slider_s *sliders;
};

struct slider_tab_s {
  const gchar		*title;
  const struct slider_s *sliders;
  GtkWidget		*tabbox;
};

static const struct slider_s settings_video[] = {
  { N_("A/V sync"),	"gxine-settings-av-sync",	Control_AV_SYNC },
  { N_("SPU sync"),	"gxine-settings-spu",		Control_SPU_SYNC },
  { N_("Hue"),		"gxine-settings-hue",		Control_HUE },
  { N_("Saturation"),	"gxine-settings-saturation",	Control_SATURATION },
  { N_("Contrast"),	"gxine-settings-contrast",	Control_CONTRAST },
  { N_("Brightness"),	"gxine-settings-brightness",	Control_BRIGHTNESS },
#ifdef XINE_PARAM_VO_GAMMA
  { N_("Gamma"),	"gxine-settings-gamma",		Control_GAMMA },
#endif
#ifdef XINE_PARAM_VO_SHARPNESS
  { N_("Sharpness"),	"gxine-settings-sharpness",	Control_SHARPNESS },
  { N_("Noise reduction"), "gxine-settings-noise-reduce", Control_NOISE_REDUCE },
#endif
  { NULL }
};

static const struct slider_s settings_audio[] = {
  { N_("Volume"),	"gxine-settings-volume",	Control_VOLUME },
  { N_("Compressor"),	"gxine-settings-compressor",	Control_COMPRESSOR },
  { N_("Amplifier"),	"gxine-settings-amplifier",	Control_AMPLIFIER },
  { NULL }
};

static const struct slider_s settings_equaliser[] = {
  { N_("30Hz"),		NULL, Control_EQ_30 },
  { N_("60Hz"),		NULL, Control_EQ_60 },
  { N_("125Hz"),	NULL, Control_EQ_125 },
  { N_("250Hz"),	NULL, Control_EQ_250 },
  { N_("500Hz"),	NULL, Control_EQ_500 },
  { N_("1kHz"),		NULL, Control_EQ_1K },
  { N_("2kHz"),		NULL, Control_EQ_2K },
  { N_("4kHz"),		NULL, Control_EQ_4K },
  { N_("8kHz"),		NULL, Control_EQ_8K },
  { N_("16kHz"),	NULL, Control_EQ_16K },
  { NULL }
};

static const struct slider_group_s settings_page[] = {
  { N_("Video"),	settings_video },
  { N_("Audio"),	settings_audio },
  { N_("Equaliser"),	settings_equaliser },
  { NULL }
};
#define NUM_PAGES (G_N_ELEMENTS (settings_page) - 1)

struct slider_window_s {
  const gchar			*title;
  const struct slider_group_s	*sliders;
  GtkWidget			*window, *tabbox;
  gboolean			 is_visible;
};

static struct slider_window_s settings_window = {
  N_("Controls"), settings_page, NULL, FALSE
};

static const struct slider_s *
get_sliders (const struct slider_window_s *window, guint page)
{
  if (page >= NUM_PAGES)
  {
    page = (guint) gtk_notebook_get_current_page (GTK_NOTEBOOK(window->tabbox));
    if (page >= NUM_PAGES)
      return NULL;
  }
  return window->sliders[page].sliders;
}

#define SLIDERS_SET(FUNC) \
  static gboolean sliders_##FUNC (const struct slider_window_s *window, \
				  guint page, gboolean allow_vol) \
  { \
    const struct slider_s *slider = get_sliders (window, page); \
    if (slider) \
      for (; slider->label; ++slider) \
      { \
	if (slider->type != Control_VOLUME || allow_vol) \
	  ui_##FUNC##_control_adjustment (slider->type); \
      } \
    else \
      for (guint _page = 0; _page < NUM_PAGES; ++_page) \
	sliders_##FUNC (window, _page, allow_vol); \
    return FALSE; \
  }

#define JSFUNC(OBJ,FUNC) \
  static JSBool js_##OBJ##_##FUNC (JSContext *cx, uintN argc, jsval *vp) \
  { \
    int page = -1; \
    jsval *argv = JS_ARGV (cx, vp); \
    se_log_fncall_checkinit (#OBJ"_"#FUNC); \
    se_argc_check_max (1, #OBJ"_"#FUNC); \
    if (argc) \
    { \
      se_arg_is_int_or_bool (0, #OBJ"_"#FUNC); \
      JS_ValueToInt32 (cx, argv[0], &page); \
    } \
    sliders_##FUNC (&OBJ##_window, page, TRUE); \
    if (vp) { \
      JS_SET_RVAL (cx, vp, JSVAL_VOID); \
    } \
    return JS_TRUE; \
  }

#define JSDECL(OBJ,FUNC) \
  { #OBJ"_"#FUNC, js_##OBJ##_##FUNC, 0, JSFUN_FAST_NATIVE,SE_GROUP_DIALOGUE, NULL, NULL }

static void sliders_show (struct slider_window_s *window, guint page,
			  gboolean unused)
{
  guint current =
    (guint) gtk_notebook_get_current_page (GTK_NOTEBOOK(window->tabbox));
  window->is_visible = (page != (guint) -1 && page != current)
		       ? TRUE : !window->is_visible;
  if (window->is_visible)
  {
    window_show (window->window, NULL);
    if (page < NUM_PAGES)
      gtk_notebook_set_current_page (GTK_NOTEBOOK(window->tabbox), page);
  }
  else
    gtk_widget_hide (window->window);
}

SLIDERS_SET(revert)
SLIDERS_SET(reset)
SLIDERS_SET(clear)

static void response_cb (GtkDialog *dbox, gint response, gpointer data)
{
  struct slider_window_s *window = data;
  switch (response)
  {
  case GTK_RESPONSE_REJECT:
    sliders_revert (window, -1, TRUE);
    break;
  case 1:
    sliders_reset (window, -1, TRUE);
    break;
  default:
    window->is_visible = FALSE;
    gtk_widget_hide (window->window);
  }
}

JSFUNC (settings, show)
JSFUNC (settings, revert)
JSFUNC (settings, reset)
JSFUNC (settings, clear)

struct slider_data_s {
  GtkLabel *label;
  gdouble value;
  uintptr_t sequence;
};

static gboolean slider_set_text (struct slider_data_s *data)
{
  if (data->sequence >= (intptr_t) g_object_get_data ((GObject *)data->label, "slider_seq"))
  {
    char n[12];
    snprintf (n, sizeof(n), "%.0f", data->value);
    gtk_label_set_text (data->label, n);
  }
  free (data);
  return FALSE;
}

static gchar *slider_format_value (GtkScale *scale, gdouble value,
				   gpointer data)
{
  struct slider_data_s *slider = malloc (sizeof (struct slider_data_s));
  slider->sequence = (intptr_t) g_object_get_data (data, "slider_seq");
  g_object_set_data (data, "slider_seq", (gpointer) (++slider->sequence));
  slider->label = data;
  slider->value = gtk_adjustment_get_value (gtk_range_get_adjustment (&scale->range)); /* don't trust the input value */
  g_idle_add ((GSourceFunc) slider_set_text, slider);
  return g_strdup ("");
}

static void sliders_create (struct slider_window_s *window)
{
  unsigned int page;
  int lock;
  xine_cfg_entry_t entry;

  /* window */

  window->window = gtk_dialog_new_with_buttons (gettext (window->title),
						NULL, 0, NULL);
  GtkDialog *dbox = GTK_DIALOG (window->window);
  gtk_dialog_add_action_widget (dbox,
				ui_button_new_stock_mnemonic
				  (GTK_STOCK_CLEAR, _("_Default")),
				1);
  gtk_dialog_add_buttons (dbox,
			GTK_STOCK_UNDO, GTK_RESPONSE_REJECT,
			GTK_STOCK_CLOSE, GTK_RESPONSE_DELETE_EVENT,
			NULL);
  gtk_window_set_default_size (GTK_WINDOW (dbox), 500, 150);
  hide_on_delete (window->window, &window->is_visible);
  ui_add_undo_response (window->window, NULL);
  g_signal_connect (G_OBJECT(window->window), "response",
		    G_CALLBACK(response_cb), window);

  window->tabbox = gtk_notebook_new ();

  /* sliders */

  for (page = 0; page < NUM_PAGES; ++page)
  {
    GtkWidget *table = gtk_table_new (3, 2, FALSE);
    const struct slider_s *sliders = window->sliders[page].sliders;
    int i;
    gdouble min = INT_MAX, max = INT_MIN;

    for (i = 0; sliders[i].label; ++i)
    {
      GtkAdjustment *adj;
      GtkWidget *w, *v;

      gtk_table_attach (GTK_TABLE(table),
			ui_label_new_with_xalign (gettext (sliders[i].label), 1),
			0, 1, i, i+1, GTK_FILL, GTK_FILL, 2, 2);

      v = ui_label_new_with_xalign ("", 1);
      gtk_table_attach (GTK_TABLE(table), v, 1, 2, i, i+1, GTK_FILL, GTK_FILL, 2, 2);

      if (sliders[i].icon)
	gtk_table_attach (GTK_TABLE(table), gtk_image_new_from_stock (sliders[i].icon, GTK_ICON_SIZE_MENU),
			  2, 3, i, i+1, GTK_FILL, GTK_FILL, 2, 2);

      adj = (GtkAdjustment *) ui_register_control_adjustment (sliders[i].type);
      if (gtk_adjustment_get_lower(adj) < min)
        min = gtk_adjustment_get_lower(adj);
      if (gtk_adjustment_get_upper(adj) > max)
        max = gtk_adjustment_get_upper(adj);
      w = ui_hscale_new (adj, GTK_POS_LEFT, 0);
      ui_register_adjustment_widget (sliders[i].type, w);
      g_signal_connect (G_OBJECT(w), "format-value",
			G_CALLBACK(slider_format_value), v);
      gtk_table_attach (GTK_TABLE(table), w,
			3, 4, i, i+1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
    }

    min = min ? floor (log10 (fabs (min))) + 1 + (min < 0) : 1;
    max = max ? floor (log10 (fabs (max))) + 1 + (max < 0) : 1;
    if (min > max)
      max = min;

    { /* create an 'invisible' label to set the width of the second column */
      static const char pad[] = "              "; /* all U+2007; three bytes each */
      GtkWidget *w =
	gtk_label_new (pad + sizeof (pad) - 4 -
		       (max * 3 > (int) sizeof (pad) ? (int) sizeof (pad)
						     : (int) max * 3));
      gtk_widget_set_size_request (w, -1, 1);
      gtk_table_attach (GTK_TABLE(table), w,
			1, 2, i, i+1, GTK_FILL, GTK_FILL, 2, 0);
      /* add the table to the notebook */
      w = gtk_label_new (gettext (window->sliders[page].label));
      gtk_notebook_insert_page (GTK_NOTEBOOK(window->tabbox), table, w, -1);
    }
  }

  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (dbox)),
                      window->tabbox, TRUE, TRUE, 2);

  lock = xine_config_lookup_entry (xine, "audio.volume.remember_volume", &entry)
	 && !entry.num_value;

  for (page = 0; page < NUM_PAGES; ++page)
    sliders_revert (window, page, lock);
}

void settings_init (void)
{
  static const se_f_def_t defs[] = {
    JSDECL (settings, show),
    JSDECL (settings, revert),
    JSDECL (settings, reset),
    JSDECL (settings, clear),
    { NULL }
  };

  sliders_create (&settings_window);

  se_defuns (gse, NULL, defs);
}

void settings_clear (void)
{
  js_settings_clear (gse->cx, 0, NULL);
}
