/*
 * Copyright (C) 2003-2007 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
 *
 * snapshot implementation
 *
 * code based on:
 *    totem     Bastien Nocera <hadess@hadess.net>
 *    transcode Copyright (C) Thomas Oestreich - June 2001
 *    enix      enix.berlios.de
 */

#include "globals.h"

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

#include <inttypes.h>

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

#include "snapshot.h"
#include "menu.h"
#include "utils.h"

#define clip_8bit(val) ((val) < 0 ? 0 : (val) > 255 ? 255 : (val))

static GtkWidget *bbox, *scalebtn, *blendbtn;

/*
 *   Create rgb data from yuy2
 */

static uint8_t *yuy2torgb (const uint8_t *data,
			   const int width, const int height)
{
  /* Packed YUV 422 is: Y1, U, Y2, V for each (horizontal) pixel pair.
   * Left pixel is Y1,U,V, right pixel is Y2,U,V.
   */

  int i;
  guchar *rgb, *ptr;

  ptr = rgb = g_malloc (width * height * 3);
  if (!rgb)
    return NULL;

  for (i = 0; i < width * height / 2; ++i)
  {
    int y = (data[0] - 16) * 11644;
    int u = data[1] - 128;
    int v = data[3] - 128;

    *ptr++ = clip_8bit ((y + 15960 * v) / 10000);		/* R */
    *ptr++ = clip_8bit ((y - 3918 * u - 8130 * v) / 10000);	/* G */
    *ptr++ = clip_8bit ((y + 20172 * u) / 10000);		/* B */

    y = (data[2] - 16) * 11644;
    data += 4;

    *ptr++ = clip_8bit ((y + 15960 * v) / 10000);		/* R */
    *ptr++ = clip_8bit ((y - 3918 * u - 8130 * v) / 10000);	/* G */
    *ptr++ = clip_8bit ((y + 20172 * u) / 10000);		/* B */
  }

  return rgb;
}

/*
 *   Create rgb data from yv12
 */

static uint8_t *yv12torgb (const uint8_t *data,
			   const int width, const int height)
{
  /* YV12 is planar: Y h*w, U (h/2)*(w/2), V (h/2)*(w/2).
   * => Each U and V byte is for 4 pixels (2x2).
   */

  const uint8_t *src_y = data;				/* [h][w] */
  const uint8_t *src_u = data + width * height;		/* [h/2][w/2] */
  const uint8_t *src_v = data + width * height * 5 / 4;	/* [h/2][w/2] */
  int i, j;
  guchar *rgb, *ptr;

  ptr = rgb = g_malloc (width * height * 3);
  if (!rgb)
    return NULL;

  for (i = 0; i < height; ++i)
  {
    for (j = 0; j < width; ++j)
    {
      int y = (src_y[j] - 16) * 11644;
      int u = src_u[j / 2] - 128;
      int v = src_v[j / 2] - 128;

      *ptr++ = clip_8bit ((y + 15960 * v) / 10000);		/* R */
      *ptr++ = clip_8bit ((y - 3918 * u - 8130 * v) / 10000);	/* G */
      *ptr++ = clip_8bit ((y + 20172 * u) / 10000);		/* B */
    }

    src_y += width;
    if (i & 1)
    {
      src_u += width / 2;
      src_v += width / 2;
    }
  }

  return rgb;
}

static const char *const blends[] = {
  /* Snapshot processing options. I *think* that "Hyper" is trilinear */
  N_("Nearest"), N_("Tiles"), N_("Bilinear"), N_("Hyper"), NULL
};

void make_snapshot (char *fname, int scale, int blend)
{
  uint8_t   *yuv, *rgb;
  int        width, height, ratio, format = 0;
  GdkPixbuf *pixbuf, *scaled;
  double     desired_ratio, image_ratio, f;
  gboolean   requested_fname = FALSE;

  xine_get_current_frame (stream, &width, &height, &ratio, &format, NULL);

  /* catch "no image" and unrecognised formats */

  switch (format)
  {
  case XINE_IMGFMT_YUY2:
  case XINE_IMGFMT_YV12:
    break;

  case 0: /* no image */
    display_info (FROM_GXINE, _("Cannot create a snapshot"),
		  _("There is no picture from which to create a snapshot."));
    return;

  default:
    display_error (FROM_GXINE, _("Cannot create a snapshot"),
		   _("Sorry, format '%.4s' is unsupported."),
		   (char *)&format);
    return;
  }

  yuv = malloc ((width+8) * (height+1) * 2);

  xine_get_current_frame (stream, &width, &height, &ratio, &format, yuv);

  if (scale >= 0)
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(scalebtn), scale != 0);
  if (blend >= 0 && blend < (int) G_N_ELEMENTS(blends))
    gtk_combo_box_set_active (GTK_COMBO_BOX(blendbtn), blend);

  /* get filename & options */

  if (!fname)
  {
    fname = modal_file_dialog (_("Enter filename for snapshot:"), FALSE, TRUE,
			       FALSE, "*.png", bbox, app);
    if (!fname)
    {
      /* cancelled */
      free (yuv);
      return;
    }
    scale = blend = -1;
    requested_fname = TRUE;
  }

  /* convert to rgb */

  switch (format)
  {
  case XINE_IMGFMT_YUY2:
    rgb = yuy2torgb (yuv, width, height);
    break;

  case XINE_IMGFMT_YV12:
    rgb = yv12torgb (yuv, width, height);
    break;

  default:
    g_printerr (_("snapshot: eek, how did I get here? (format '%.4s')\n"),
	     (char *) &format);
    abort();
  }

  free (yuv); /* we've now finished with this */

  pixbuf = gdk_pixbuf_new_from_data (rgb,
				     GDK_COLORSPACE_RGB, FALSE,
				     8, width, height, 3 * width,
				     (GdkPixbufDestroyNotify) g_free, NULL);

  if (scale < 0)
    scale = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(scalebtn));
  if (blend < 0)
    blend = gtk_combo_box_get_active (GTK_COMBO_BOX(blendbtn));

  if (!scale && !blend)
  {
    /* no scaling, no blending => simple save */
    gdk_pixbuf_save (pixbuf, fname, "png", NULL, NULL);
    g_object_unref (pixbuf);
    if (requested_fname)
      free (fname);
    return;
  }

  /* scale the image */

  if (!scale)
  {
    ratio = XINE_VO_ASPECT_DONT_TOUCH;
    image_ratio = 1;
  }
  else
    image_ratio = (double) width / (double) height;

  switch (ratio)
  {
  case XINE_VO_ASPECT_ANAMORPHIC:  /* anamorphic     */
  case XINE_VO_ASPECT_PAN_SCAN:    /* we display pan&scan as widescreen */
    desired_ratio = 16.0 /9.0;
    break;
  case XINE_VO_ASPECT_DVB:         /* 2.11:1 */
    desired_ratio = 2.11/1.0;
    break;
  case XINE_VO_ASPECT_SQUARE:      /* square pels */
  case XINE_VO_ASPECT_DONT_TOUCH:  /* probably non-mpeg stream => don't touch aspect ratio */
    desired_ratio = image_ratio;
    break;
  default:
    g_print (_("vo_scale: unknown/forbidden aspect ratio (%d) in stream => using 4:3\n"),
	    ratio);
    /* fall thru */
  case XINE_VO_ASPECT_4_3:         /* 4:3             */
    desired_ratio = 4.0 / 3.0;
    break;
  }

  f = scale ? (desired_ratio / image_ratio) : 1;

  if (f >= 1.0)
    scaled = gdk_pixbuf_scale_simple (pixbuf,
				      (int) ((double) width * f), height,
				      (GdkInterpType) blend);
  else
    scaled = gdk_pixbuf_scale_simple (pixbuf,
				      width, (int) ((double) height / f),
				      (GdkInterpType) blend);

  g_object_unref (pixbuf);

  /*
   * save pixbuf to file
   */

  GError *error = NULL;
  gboolean ret = gdk_pixbuf_save (scaled, fname, "png", &error, NULL);

  g_object_unref (scaled);
  if (requested_fname)
    free (fname);

  if (!ret)
  {
    display_error (FROM_GXINE, _("Cannot create a snapshot"),
		   "%s", error->message);
    g_error_free (error);
  }

}

void snapshot_init (void)
{
  int i;
  GtkWidget *w;
  GtkBox *b;

  bbox = gtk_hbox_new (FALSE, 2);
  b = GTK_BOX (bbox);
  g_object_ref (bbox);

  w = gtk_label_new_with_mnemonic (_("_Blend:"));
  gtk_box_pack_start (b, w, FALSE, FALSE, 2);

  blendbtn = gtk_combo_box_text_new ();
  for (i = 0; blends[i]; ++i)
    gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT(blendbtn), gettext (blends[i]));
  gtk_box_pack_start (b, blendbtn, FALSE, FALSE, 2);
  gtk_combo_box_set_active (GTK_COMBO_BOX(blendbtn), 2); /* bilinear */

  gtk_label_set_mnemonic_widget (GTK_LABEL(w), blendbtn);

  scalebtn = gtk_check_button_new_with_mnemonic (_("_Scaled"));
  gtk_box_pack_start (b, scalebtn, FALSE, FALSE, 2);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(scalebtn), TRUE);

  gtk_widget_show_all (bbox);
}
