/*
 * 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
 *
 * nice black information display areas
 */

#include "globals.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include <gtk/gtk.h>

#include <xine.h>

#include "i18n.h"
#include "defs.h"
#include "gtkvideo.h"
#include "info_widgets.h"
#include "ui.h"
#include "utils.h"
#include "playlist.h"
#include "player.h"

GSList *infobars = NULL, *timewidgets = NULL;
pthread_mutex_t widgets_update_lock = PTHREAD_MUTEX_INITIALIZER;

/*
 * Object/type handling code
 */

#define MAX_LINES 3

typedef gboolean (*gxineinfo_cb) (gpointer);

struct gxineinfo_private_s
{
  gxineinfo_cb postinit, update;
  GtkLabel *line[MAX_LINES];
  int data;
  struct {
    int time, length, tcount, tno, ccount, cno;
    gboolean remaining, seekable;
  } prev;
  gboolean force;
};
typedef struct gxineinfo_private_s gxineinfo_private_t;

static GtkWidgetClass *parent_class = NULL;

static GType gxineinfo_get_type (void);

#define GTK_GXINE_INFO(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), gxineinfo_get_type (), GtkGxineInfo))
#define GTK_GXINE_INFO_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), gxineinfo_get_type (), GtkGxineInfoClass))
#define GTK_IS_GXINE_INFO(obj)		(G_TYPE_CHECK_INSTANCE_TYPE (obj, gxineinfo_get_type ()))
#define GTK_IS_GXINE_INFO_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), gxineinfo_get_type ()))

#define GET_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), gxineinfo_get_type (), \
				gxineinfo_private_t))

static void postinit(GtkWidget * widget)
{
  gxineinfo_private_t *priv;

  if ((priv = GET_PRIVATE (widget))->postinit)
  {
    priv->postinit ((GtkGxineInfo *) widget);
    priv->postinit = NULL;
  }
}

#if !GTK_CHECK_VERSION(3, 0, 0)
static gint
expose_cb (GtkWidget * widget, GdkEventExpose * event)
{
  GtkAllocation allocation = { 0 };
  gtk_widget_get_allocation (widget, &allocation);

  gtk_paint_flat_box (gtk_widget_get_style(widget),
                      gtk_widget_get_window (widget),
                      gtk_widget_get_state (widget), GTK_SHADOW_NONE,
		      &event->area, widget, gtk_widget_get_name (widget),
                      allocation.x, allocation.y,
                      allocation.width, allocation.height);
  parent_class->expose_event (widget, event);
  postinit (widget);
  return FALSE;
}
#else
static gint
draw_cb (GtkWidget * widget, cairo_t *cr)
{
  GtkAllocation allocation = { 0 };
  gtk_widget_get_allocation (widget, &allocation);

  gtk_render_background(gtk_widget_get_style_context(widget),
                        cr,
                        allocation.x, allocation.y,
                        allocation.width, allocation.height);
  parent_class->draw (widget, cr);
  postinit (widget);
  return FALSE;
}
#endif

#if !GTK_CHECK_VERSION(3, 0, 0)
static void
realise_cb (GtkWidget * widget)
{
  GtkAllocation allocation = { 0 };

  parent_class->realize (widget);

  gtk_widget_get_allocation (widget, &allocation);

  gtk_paint_flat_box (gtk_widget_get_style(widget),
                      gtk_widget_get_window (widget),
                      gtk_widget_get_state (widget), GTK_SHADOW_NONE,
		      NULL, widget, gtk_widget_get_name (widget),
                      allocation.x, allocation.y,
                      allocation.width, allocation.height);
}
#endif

static void
class_init (gpointer g_class, gpointer class_data)
{
  GtkWidgetClass *widget_class = (GtkWidgetClass *) g_class;
#if !GTK_CHECK_VERSION(3, 0, 0)
  widget_class->realize = realise_cb;
  widget_class->expose_event = expose_cb;
#else
  widget_class->draw    = draw_cb;
#endif
  parent_class = g_type_class_ref (GTK_TYPE_ALIGNMENT);
  g_type_class_add_private (g_class, sizeof (gxineinfo_private_t));
}

static GType
gxineinfo_get_type (void)
{
  static const GTypeInfo info = {
    .class_size       = sizeof (GtkGxineInfoClass),
    .class_init       = class_init,
    .instance_size    = sizeof (GtkGxineInfo),
  };
  static GType type = 0;

  if (!type)
  {
    type = g_type_register_static (GTK_TYPE_ALIGNMENT, "GtkGxineInfo",
				   &info, (GTypeFlags) 0);
    xine_config_register_bool
      (xine, "gui.show_time_remaining", 0,
       _("If set, show the length of the remaining part of the stream."),
       NULL, 0, (xine_config_cb_t) GXINE_TRUE, NULL);
  }
  return type;
}

static void
pad_table (GtkWidget *table, int l, int r, int t, int b, int pad)
{
  GtkWidget *tmp = gtk_label_new (NULL);
  gtk_widget_set_size_request (tmp, 2, 2);
  gtk_table_attach (GTK_TABLE(table), tmp, l, r, t, b,
		    (pad & 1) ? (GTK_FILL | GTK_EXPAND) : GTK_FILL,
		    (pad & 2) ? (GTK_FILL | GTK_EXPAND) : GTK_FILL,
		    0, 0);
}

static GtkWidget *
gxineinfo_new (guint height, PangoEllipsizeMode ellipsize, guint width,
	       gxineinfo_cb postinit, gxineinfo_cb update, const char *name,
	       guint buttons)
{
  GtkWidget *self, *table;
  unsigned int i;
  gxineinfo_private_t *priv;
  gchar *base, *large, *small;

  g_return_val_if_fail (height <= MAX_LINES, NULL);

  base = g_strdup_printf ("gxine_%s", name);
  large = g_strdup_printf ("%s_large", base);
  small = g_strdup_printf ("%s_small", base);

  self = g_object_new (gxineinfo_get_type (), "xalign", 0.5, "yalign", 0.5,
		       "xscale", 1.0, "yscale", 1.0, NULL);
  gtk_widget_set_name (self, base);

  priv = GET_PRIVATE (self);
  priv->postinit = postinit;
  priv->update = update;
  priv->data = 0;

  gtk_widget_push_composite_child ();

  table = gtk_table_new (height + 2, 3, FALSE);
  gtk_widget_set_name (table, base);
  gtk_container_add (GTK_CONTAINER(self), table);

  /* ensure that the whole of the table area is exposed & drawn */
  pad_table (table, 0, 3, 0, 1, 1);
  pad_table (table, 0, 1, 1, height + 1, 0);
  pad_table (table, 2, 3, 1, height + 1, 0);
  pad_table (table, 0, 3, height + 1, height + 2, 3);

  for (i = 0; i < height; ++i)
  {
    GtkWidget *ev = (buttons & 1 << i) ? gtk_event_box_new () : NULL;
    GtkWidget *line = ui_label_new_with_xalign ("", 0);
    priv->line[i] = GTK_LABEL (line);
    gtk_widget_set_name (line, i ? small : large);
    gtk_label_set_ellipsize (GTK_LABEL (line), ellipsize);
    if (!i && width)
      gtk_label_set_width_chars (GTK_LABEL (line), width);
    if (ev)
      gtk_container_add (GTK_CONTAINER (ev), line);
    gtk_table_attach (GTK_TABLE (table), ev ? : line, 1, 2, i + 1, i + 2,
		      GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
  }
  for (; i < MAX_LINES; ++i)
    priv->line[i] = NULL;

  gtk_widget_pop_composite_child ();

  g_free (base);
  g_free (large);
  g_free (small);

  return self;
}

/*
 * Utility functions
 */

void
gxineinfo_update_line (const GSList *objs, guint lineno, const char *format, ...)
{
  gchar *text = NULL;

  if (!objs || lineno >= MAX_LINES)
    return;

  if (format)
  {
    va_list argp;
    va_start (argp, format);
    text = g_strdup_vprintf (format, argp);
    va_end (argp);
  }

  foreach_glist (objs, objs)
  {
    gxineinfo_private_t *priv = GET_PRIVATE (objs->data);
    if (priv->line[lineno])
      gtk_label_set_text (priv->line[lineno], text);
  }
  free (text);
}

void
gxineinfo_update (const GSList *objs)
{
  foreach_glist (objs, objs)
    if (GTK_IS_GXINE_INFO (objs->data))
    {
      gxineinfo_private_t *priv = GET_PRIVATE (objs->data);
      if (priv->update)
	priv->update (objs->data);
    }
}

void
gxineinfo_clear (const GSList *objs)
{
  int i;
  for (i = 0; i < MAX_LINES; ++i)
    gxineinfo_update_line (objs, i, NULL);
}

/*
 * Info bar
 */

void
infobar_show_metadata (const GSList * bars)
{
  gchar *strbuf = NULL; /* for the main window's title bar */
  const char *t;
  const char *str;
  int n, w, h;
  gboolean show_meta = TRUE;

  if (!bars)
    return;

  if ((t = xine_get_meta_info (stream, XINE_META_INFO_TITLE)))
  {
    const char *a;
    if ((a = xine_get_meta_info (stream, XINE_META_INFO_ARTIST)))
    {
      strbuf = g_strdup_printf ("%s (%s) - gxine %s", t, a, VERSION);
      gxineinfo_update_line (bars, 0, "%s (%s)", t, a);
    }
    else
    {
      strbuf = g_strdup_printf ("%s - gxine %s", t, VERSION);
      gxineinfo_update_line (bars, 0, "%s", t);
    }
  }
  else
  {
    const char *str = NULL;
    if (!playlist_showing_logo ())
    {
      str = player_get_cur_title ();
      if (!str || !str[0])
      {
	const char *mrl = player_get_cur_mrl () ? : "";
	str = (strrchr (mrl, '/') ? : mrl - 1) + 1;	/* leaf */
      }
    }
    if (str && str[0])
    {
      strbuf = g_strdup_printf ("%s - gxine %s", str, VERSION);
      gxineinfo_update_line (bars, 0, "%s", str);
    }
    else
    {
      strbuf = g_strdup_printf ("gxine %s", VERSION);
      show_meta = FALSE;
      gxineinfo_update_line (bars, 0, "%s", strbuf);
    }
  }
  gtk_window_set_title (GTK_WINDOW (app), strbuf);

  /*
   * display some metainfo
   */

  strbuf[0] = 0;
  if (show_meta)
  {
    str = xine_get_meta_info (stream, XINE_META_INFO_SYSTEMLAYER);
    if (str)
      asreprintf (&strbuf, "%s%s ", strbuf, str);

    w = xine_get_stream_info (stream, XINE_STREAM_INFO_VIDEO_WIDTH);
    h = xine_get_stream_info (stream, XINE_STREAM_INFO_VIDEO_HEIGHT);
    if (w && h)
      asreprintf (&strbuf, "%s%d×%d ", strbuf, w, h);

    str = xine_get_meta_info (stream, XINE_META_INFO_VIDEOCODEC);
    if (str)
      asreprintf (&strbuf, "%s%s ", strbuf, str);

    n = xine_get_stream_info (stream, XINE_STREAM_INFO_VIDEO_BITRATE) / 1000;
    if (n > 10 && n < 20000)
      asreprintf (&strbuf, _("%s%d kBit/s "), strbuf, n);
  }
  gxineinfo_update_line (bars, 1, "%s", strbuf);

  strbuf[0] = 0;
  if (show_meta)
  {
    n = xine_get_stream_info (stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE) / 1000;
    if ((n > 0) && (n < 256))
      asreprintf (&strbuf, _("%s%d kHz "), strbuf, n);

    n = xine_get_stream_info (stream, XINE_STREAM_INFO_AUDIO_BITRATE) / 1000;
    if ((n > 0) && (n < 1024))
      asreprintf (&strbuf, _("%s%d kBit/s "), strbuf, n);

    str = xine_get_meta_info (stream, XINE_META_INFO_AUDIOCODEC);
    if (str)
      asreprintf (&strbuf, "%s%s ", strbuf, str);
  }
  gxineinfo_update_line (bars, 2, "%s", strbuf);

  g_free (strbuf);
}

GtkWidget *
create_infobar (gboolean small)
{
  return gxineinfo_new (small ? 1 : 3, PANGO_ELLIPSIZE_END, 0, NULL, NULL, "info", 0);
}

/*
 * Time display area
 */

static gboolean
update_time_widget_cb (gpointer data)
{
  GtkGxineInfo *widget = data;
  gint pos_stream, pos_time, length_time;
  gxineinfo_private_t *priv = GET_PRIVATE (widget);
  union {
    struct { gchar current[64], pad[4], length[64]; };
    gchar full[132];
  } time;

  if (pthread_mutex_trylock (&widgets_update_lock))
    return TRUE;
  gdk_threads_enter ();

  time.length[0] = time.current[0] = 0;

  xine_cfg_entry_t entry;
  gboolean state = priv->data ^
		   !!(xine_config_lookup_entry
			(xine, "gui.show_time_remaining", &entry)
		      && entry.num_value);
  gboolean seekable = xine_get_stream_info (stream, XINE_STREAM_INFO_SEEKABLE);
  gtk_widget_set_state ((GtkWidget *)widget, state ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL);

  if (xine_get_status (stream) == XINE_STATUS_PLAY && !playlist_showing_logo ())
  {
    if (!xine_get_pos_length (stream, &pos_stream, &pos_time, &length_time))
      pos_stream = pos_time = length_time = 0;

    if (state)
      pos_time = length_time - pos_time;

    pos_time -= pos_time % 1000;
    if (!priv->force && pos_time == priv->prev.time
	&& length_time == priv->prev.length && state == priv->prev.remaining
	&& seekable == priv->prev.seekable)
      goto finish; /* no need to redisplay the time */

    priv->prev.time = pos_time;
    priv->prev.length = length_time;
    priv->prev.remaining = state;
    priv->prev.seekable = seekable;

    int_to_timestring (pos_time, time.current, sizeof (time.current));

    length_time = round_second (length_time);
    if (length_time > 0)
      int_to_timestring (length_time, time.length, sizeof (time.length));
    else
      snprintf (time.length, sizeof (time.length), "%s",
		seekable ? _("??:??:??") : _("live"));
  }
  else
  {
    if (!priv->force && priv->prev.time == -1 && priv->prev.length == -1)
      goto finish; /* no need to redisplay the (lack of) time */
    priv->prev.time = priv->prev.length = -1;
    strcpy (time.current, "•");
  }

  if (priv->line[1])
  {
    gtk_label_set_text (priv->line[0], time.current);
    gtk_label_set_text (priv->line[1], time.length);
  }
  else if (time.current[0])
  {
    strcat (time.full, " / ");
    memmove (time.full + strlen (time.full), time.length, sizeof (time.length));
    gtk_label_set_text (priv->line[0], time.full);
  }
  else
    gtk_label_set_text (priv->line[0], "");

finish:
#ifdef XINE_STREAM_INFO_DVD_TITLE_NUMBER
  time.full[0] = 0;
  int tcount = xine_get_stream_info (stream, XINE_STREAM_INFO_DVD_TITLE_COUNT);
  int tno = tcount ? xine_get_stream_info (stream, XINE_STREAM_INFO_DVD_TITLE_NUMBER) : 0;
  int ccount = xine_get_stream_info (stream, XINE_STREAM_INFO_DVD_CHAPTER_COUNT);
  int cno = ccount ? xine_get_stream_info (stream, XINE_STREAM_INFO_DVD_CHAPTER_NUMBER) : 0;

  if (priv->force || tcount != priv->prev.tcount || tno != priv->prev.tno
		  || ccount != priv->prev.ccount || cno != priv->prev.cno)
  {
    priv->prev.tcount = tcount;
    priv->prev.tno = tno;
    priv->prev.ccount = ccount;
    priv->prev.cno = cno;
    if (tcount) /* "T" = DVD title number */
      sprintf (time.full, _("T%d/%d "), tno, tcount);
    if (ccount) /* "C" = DVD chapter number */
      sprintf (time.full + strlen (time.full), _("C%d/%d "), cno, ccount);
    gtk_label_set_text (priv->line[2], time.full);
  }
#endif

  priv->force = FALSE;
  gdk_threads_leave ();
  pthread_mutex_unlock (&widgets_update_lock);
  return FALSE;
}

static gboolean
frob_format_cb (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
  if (event->button == 1)
  {
    gxineinfo_private_t *priv = GET_PRIVATE (data);
    int flag = (priv->data ^= 1);
    priv->force = TRUE;

    GSList **listp = g_object_get_data ((GObject *) data, "gxine-chain");
    if (listp)
    {
      GSList *item = *listp;
      while (item)
      {
	priv = GET_PRIVATE (item->data);
	priv->data = flag;
	priv->force = TRUE;
	item = item->next;
      }
      gxineinfo_update (*listp);
    }
    else
      update_time_widget_cb (data);
  }
  return TRUE;
}

static gboolean
postinit_time_widget (gpointer widget)
{
  gxineinfo_private_t *priv = GET_PRIVATE (widget);
  GtkWidget *line = GTK_WIDGET (priv->line[0]);
  g_signal_connect (G_OBJECT (gtk_widget_get_parent (line)),
                    "button-press-event",
                    G_CALLBACK (frob_format_cb), widget);

  GdkWindow *window = gtk_widget_get_window (line);
#if !GTK_CHECK_VERSION(2, 24, 0)
  GdkDisplay *display = gdk_drawable_get_display ((GdkDrawable *)window);
#else
  GdkDisplay *display = gdk_window_get_display (window);
#endif
  GdkCursor *ptr = gdk_cursor_new_for_display (display, GDK_CLOCK);
  gdk_window_set_cursor (window, ptr);
  gdk_cursor_unref (ptr);

  return FALSE;
}

GtkWidget *
create_time_widget (gboolean small, const GSList *const *const listp)
{
  GtkWidget *w = gxineinfo_new (small ? 1 : 3, PANGO_ELLIPSIZE_NONE,
				small ? 16 : 8, postinit_time_widget,
				update_time_widget_cb, "time",
				1 /* line 0 has an event box */);
  if (listp)
    g_object_set_data ((GObject *) w, "gxine-chain", (gpointer) listp);
  return w;
}
