/*
 * Copyright (C) 2003-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
 *
 * playlist item / media mark:
 * an mrl + options (e.g. volume, start time, brightness/contrast ...)
 */

#include "globals.h"

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

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

#include <xine/xmlparser.h>

#include "play_item.h"
#include "globals.h"
#include "ui.h"
#include "utils.h"
#include "playlist.h"
#include "script_engine.h"
#include "engine.h"
#include "player.h"

static GtkWidget *gettitle;

play_item_t *play_item_new (const char *title, const char *mrl,
			    int start_time, int duration)
{
  play_item_t *play_item = g_malloc0 (sizeof (play_item_t));

  play_item->untitled = !title;
  if (!title)
  {
    char *host = NULL, *file = g_filename_from_uri (mrl, &host, NULL);
    if (host)
    {
      free (host);
      free (file);
      file = NULL;
    }
    else if (file)
    {
      char *link = g_file_read_link (file, NULL);
      if (link)
      {
	free (file);
	file = link;
      }
    }

    /* find the leafname of the MRL */
    char *end, *retitle = strdup (file ? file : mrl);
    if ((end = strchr (retitle, '#')))
      *end = 0; /* lose parameters suffix (if present) */

    title = strrchr (retitle, '/');
    if (title && !title[1]) /* ends in '/' - look for preceding '/' */
    {
      while (*title == '/')
	if (--title == retitle)
	  break;
      while (*title != '/')
	if (--title < retitle)
	  break;
    }

    /* at this point, title is NULL or >= retitle - 1
     * retitle[-1] being invalid doesn't matter
     */

    if (title && title[1])
    {
      int i = 0, j = -1;

      /* Strip the extension and suffix (if present) */
      if ((end = strrchr (title + 1, '.')))
	*end = 0;

      /* URL-decode the leafname unless g_filename_from_uri() succeeded */
      if (file)
        memmove (retitle, title + 1, strlen(title + 1) + 1);
      else
      {
	while (title[++i])
	{
	  if (title[i] == '%')
	  {
	    char c[3] = { title[i+1], title[i+2], 0 };
	    i += 2;
	    retitle[++j] = (char) strtol (c, NULL, 16);
	  }
	  else
	    retitle[++j] = title[i];
	}
	/* NUL-terminate, strip trailing / */
	retitle[j + (retitle[j] != '/')] = 0;
      }
      play_item->title = realloc (retitle, strlen (retitle) + 1);
    }
    else
    {
      free (retitle);
      play_item->title = strdup (mrl);
    }
    free (file);
  }
  else
    play_item->title    = strdup (title);

  play_item->mrl        = strdup (mrl);
  play_item->start_time = start_time;
  play_item->duration   = duration;
  play_item->options    = NULL;

  return play_item;
}

play_item_t *play_item_copy (play_item_t *src)
{
  play_item_t *item;
  GList       *option;

  if (!src)
    return NULL;

  item = play_item_new (src->title, src->mrl, src->start_time, src->duration);
  foreach_glist (option, src->options)
    item->options = g_list_append (item->options, strdup (option->data));
  item->untitled = src->untitled;

  return item;
}

void play_item_dispose (play_item_t *play_item)
{
  if (play_item)
  {
    free (play_item->mrl);
    free (play_item->title);
    g_list_foreach (play_item->options, (GFunc)free, NULL);
    free (play_item);
  }
}

void play_item_add_option (play_item_t *item, const char *option)
{
  item->options = g_list_append (item->options, strdup (option));
}

char *play_item_xml (const play_item_t *item, int depth)
{
  char *title = xml_escape_string (item->title, XML_ESCAPE_NO_QUOTE);
  char *mrl = xml_escape_string (item->mrl, XML_ESCAPE_DOUBLE_QUOTE);
  char *indent = malloc (depth * 2 + 1);
  gchar *xml;
  gchar *sub = NULL;

  memset (indent, ' ', depth * 2);
  indent[depth * 2] = 0;

  xml = g_strdup_printf ("%s<ENTRY>\n"
		"%s  <TITLE%s>%s</TITLE>\n"
		"%s  <REF HREF=\"%s\"",
		indent,
		indent, item->untitled ? " default=\"y\"" : "", title,
		indent, mrl);

  if (item->start_time)
  {
    char timestring[64];
    int_to_timestring (item->start_time, timestring, sizeof (timestring));
    asreprintf (&sub, "%s%s    <STARTTIME VALUE=\"%s\" />\n",
		sub ? : "", indent, timestring);
  }

  if (item->duration > 0)
  {
    char timestring[64];
    int_to_timestring (item->duration, timestring, sizeof (timestring));
    asreprintf (&sub, "%s%s    <DURATION VALUE=\"%s\" />\n",
		sub ? : "", indent, timestring);
  }

  if (item->av.set)
    asreprintf (&sub, "%s%s    <AVOFFSET VALUE=\"%d\" />\n",
		sub ? : "", indent, item->av.offset);

  if (item->spu.set)
    asreprintf (&sub, "%s%s    <SPUOFFSET VALUE=\"%d\" />\n",
		sub ? : "", indent, item->spu.offset);

  if (sub)
    asreprintf (&xml, "%s>\n%s%s  </REF>\n%s</ENTRY>\n", xml, sub, indent, indent);
  else
    asreprintf (&xml, "%s />\n%s</ENTRY>\n", xml, indent);

  free (title);
  g_free (sub);
  g_free (mrl);
  free (indent);

  return xml;
}

#define GET_TIME(NODE) \
  (parse_timestring (xml_parser_get_property ((NODE), "VALUE")))

play_item_t *play_item_load (xml_node_t *node)
/* FIXME: add parameter: global base href */
{
  play_item_t *play_item = g_malloc0 (sizeof (play_item_t));

  play_item->title      = NULL;
  play_item->options    = NULL;
  play_item->mrl        = NULL;
  play_item->start_time = -1;
  play_item->duration   = -1;

  /* assumption: node is the first child of an ENTRY node */

  /* Attributes: CLIENTSKIP, SKIPIFREF
   * Child elements: ABSTRACT, AUTHOR, BASE, COPYRIGHT, DURATION, ENDMARKER,
   *		     MOREINFO, PARAM, REF, STARTMARKER, STARTTIME, TITLE
   */
  foreach_glist (node, node)
  {
    if (!strcasecmp (node->name, "REF"))
    {
      /* Attributes: HREF
       * Child elements: DURATION, ENDMARKER, STARTMARKER, STARTTIME
       */
      if (!play_item->mrl)
      {
	xml_node_t *asx_sub;

	/* FIXME: multiple REFs => alternative streams
	 * (and per-ref start times and durations?).
	 * Just the one title, though.
	 */
	play_item->mrl = strdup (xml_parser_get_property (node, "HREF"));
	logprintf ("play_item: mrl = %s\n", play_item->mrl);

	foreach_glist (asx_sub, node->child)
	{
	  if (!strcasecmp (asx_sub->name, "STARTTIME"))
	    play_item->start_time = GET_TIME (asx_sub);
	  else if (!strcasecmp (asx_sub->name, "DURATION"))
	    play_item->duration = GET_TIME (asx_sub);
	}
      }
    }

    else if (!strcasecmp (node->name, "TITLE"))
    {
      if (!play_item->title)
      {
	play_item->untitled = !!xml_parser_get_property (node, "default");
	play_item->title = strdup (node->data ? : "");
	logprintf ("play_item: title = %s\n", play_item->title);
      }
    }

    else if (!strcasecmp (node->name, "STARTTIME"))
    {
      if (play_item->start_time < 0)
      {
	play_item->start_time = GET_TIME (node);
	logprintf ("play_item: start time = %lf s\n",
		   play_item->start_time / 1000.0);
      }
    }

    else if (!strcasecmp (node->name, "DURATION"))
    {
      if (play_item->duration < 0)
      {
	play_item->duration = GET_TIME (node);
	logprintf ("play_item: duration = %lf s\n",
		   play_item->duration / 1000.0);
      }
    }

    else if (!strcasecmp (node->name, "TIME"))
    {
      /* DEPRECATED; for compatibility with older versions of gxine */
      play_item->start_time = xml_parser_get_property_int (node, "START", 0);
      logprintf ("play_item: start time = %d s\n", play_item->start_time);
    }
/*
    else if (!strcasecmp (node->name, "BASE"))
      ref_base_href = xml_parser_get_property (asx_entry, "HREF");
*/
  }
  if (play_item->start_time < 0)
    play_item->start_time = 0;

  return play_item;
}

void play_item_play (play_item_t *play_item,
		     int pos, int pos_time, int duration)
{
  GList *option;
  int count = -1;

  /*
   * execute any optional commands which may be associated with this play item
   * (e.g. set volume etc.)
   */
  foreach_glist (option, play_item->options)
  {
    gchar *source = g_strdup_printf (_("play item ‘%s’, chunk %d"),
				    play_item->title, ++count);
    engine_exec (option->data, NULL, NULL, source);
    g_free (source);
  }

  player_launch (play_item->title, play_item->mrl, pos,
		 pos_time >= 0 ? pos_time : play_item->start_time,
		 duration > 0 ? duration : play_item->duration);
}

/* gui part */

static GtkWidget *dlg;
static GtkWidget *entry, *mrl_entry, *time_entry, *duration_entry;
static int        is_visible;

static play_item_t *edit_item = NULL;
static char *item_title;
static int edited = 0;

static void play_item_set_text (void)
{
  char time_str[256];
  gtk_entry_set_text (GTK_ENTRY (mrl_entry), edit_item->mrl);
  gtk_entry_set_text (GTK_ENTRY (entry), edit_item->title);
  int_to_timestring (round_second (edit_item->start_time),
		     time_str, sizeof (time_str));
  gtk_entry_set_text (GTK_ENTRY (time_entry), time_str);
  int_to_timestring (edit_item->duration < 0 ? 0 : edit_item->duration,
		     time_str, sizeof (time_str));
  gtk_entry_set_text (GTK_ENTRY (duration_entry), time_str);
}

static gboolean close_cb (GtkWidget* widget, gpointer data)
{
  gtk_widget_hide (dlg);
  is_visible = 0;
  gtk_main_quit();
  return TRUE;
}

static void response_cb (GtkDialog *dbox, int response, gpointer data)
{
  const char *tmp;
  switch (response)
  {
  case 1:
    get_mrl_from_filesystem ((GtkWindow *)dlg, (GtkEntry *)mrl_entry);
    break;
  case GTK_RESPONSE_REJECT:
    play_item_set_text ();
    break;
  case GTK_RESPONSE_OK:
    tmp = gtk_entry_get_text (GTK_ENTRY (entry));
    edit_item->untitled &= !strcmp (tmp, edit_item->title);
    free (edit_item->mrl);
    free (edit_item->title);
    edit_item->mrl   = strdup(gtk_entry_get_text (GTK_ENTRY (mrl_entry)));
    edit_item->title = strdup(tmp);
    edit_item->start_time = parse_timestring
			      (gtk_entry_get_text (GTK_ENTRY (time_entry)));
    edit_item->duration = parse_timestring
			    (gtk_entry_get_text (GTK_ENTRY (duration_entry)));
    edited = 1;
  default:
    free (item_title);
    item_title = NULL;
    is_visible = 0;
    gtk_widget_hide (dlg);
    gtk_main_quit();
  }
}

static void get_title_cb (GtkWidget *widget, gpointer data)
{
  if (item_title)
    gtk_entry_set_text (GTK_ENTRY (entry), item_title);
}

void play_item_init (void)
{
  GtkWidget *table;

  dlg = gtk_dialog_new_with_buttons ("", NULL, 0, NULL);
  gtk_dialog_add_action_widget (GTK_DIALOG (dlg),
				ui_button_new_stock_mnemonic
				  (GTK_STOCK_OPEN, _("_File...")),
				1);
  gtk_dialog_add_buttons (GTK_DIALOG (dlg),
			  GTK_STOCK_UNDO, GTK_RESPONSE_REJECT,
			  GTK_STOCK_CANCEL, GTK_RESPONSE_DELETE_EVENT,
			  GTK_STOCK_OK, GTK_RESPONSE_OK,
			  NULL);

  gtk_dialog_set_default_response (GTK_DIALOG(dlg), GTK_RESPONSE_OK);
  gtk_window_set_default_size (GTK_WINDOW (dlg), 500, 1);
  g_object_connect (G_OBJECT (dlg),
	"signal::delete-event", G_CALLBACK (close_cb), NULL,
	"signal::response", G_CALLBACK (response_cb), NULL,
	NULL);
  ui_add_undo_response (dlg, NULL);

  table = gtk_table_new (4, 3, FALSE);

  add_table_row_items (table, 0, FALSE, _("MRL"),
		       mrl_entry = gtk_entry_new (), NULL);
  add_table_row_items (table, 1, FALSE, _("Name"),
		       entry = gtk_entry_new (),
		       gettitle = gtk_button_new_with_label (_("From stream")),
		       NULL);
  add_table_row_items (table, 2, FALSE, _("Start time"),
		       time_entry = gtk_entry_new (), NULL);
  add_table_row_items (table, 3, FALSE, _("Duration"),
		       duration_entry = gtk_entry_new (), NULL);
  gtk_entry_set_activates_default (GTK_ENTRY(mrl_entry), TRUE);
  gtk_entry_set_activates_default (GTK_ENTRY(entry), TRUE);
  gtk_entry_set_activates_default (GTK_ENTRY(time_entry), TRUE);
  gtk_entry_set_activates_default (GTK_ENTRY(duration_entry), TRUE);
  g_object_set_data (G_OBJECT (mrl_entry), "mrl_component", "M");
  g_object_set_data (G_OBJECT (entry), "mrl_component", "N");
  g_object_set_data (G_OBJECT (time_entry), "mrl_component", "T");
  g_object_set_data (G_OBJECT (duration_entry), "mrl_component", "D");

  g_signal_connect (G_OBJECT (gettitle), "clicked",
		    (GCallback) get_title_cb, NULL);

  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
                      table, FALSE, TRUE, 2);

  is_visible = 0;
}

int play_item_edit (play_item_t *item, play_item_type_t type,
		    const char *newtitle, GtkWidget *parent)
{
  static const char *const title[] = {
    N_("Edit play item"),
    N_("Edit media mark"),
    N_("Add play item"),
    N_("Add media mark"),
  };

  edit_item = item;
  item_title = newtitle ? g_strstrip (strdup (newtitle)) : NULL;

  gtk_widget_set_sensitive (gettitle, item_title && *item_title);
  play_item_set_text ();

  GtkWindow *w = GTK_WINDOW (dlg);
  gtk_window_set_title (w, gettext (title[type]));
  gtk_window_set_modal (w, TRUE);
  window_show (dlg, parent);
  gtk_window_set_focus (w, entry);

  gtk_main();

  return edited;
}

/* clipboard */

static const GtkTargetEntry targets[] = {
  { "text/xml",		0, 0 },
  { "GXINE_PLAY_ITEM_T", GTK_TARGET_SAME_APP, 1 },
  /* Various well-known types */
  { "text/plain",	0, 2},
  { "STRING",		0, 2},
  { "TEXT",		0, 2},
  { "COMPOUND_TEXT",	0, 2},
  { "UTF8_STRING",	0, 2},
};

static void clip_get_cb (GtkClipboard *cb, GtkSelectionData *sel, guint info,
			 gpointer data)
{
  const play_item_t *const item = data;
  switch (info)
  {
  case 0:
    {
      char *xml = play_item_xml (item, 0);
      gtk_selection_data_set (sel, gdk_atom_intern ("text/xml", FALSE),
			      8, (guchar *)xml, strlen (xml));
      free (xml);
    }
    break;
  case 1:
    gtk_selection_data_set (sel, GDK_POINTER_TO_ATOM (&targets[1]),
			    8, data, sizeof (play_item_t));
    break;
  case 2:
    {
      char start[64], length[64];
      gchar *text;
      int_to_timestring (item->start_time, start, sizeof (start));
      int_to_timestring (item->start_time, length, sizeof (length));
      text = g_strdup_printf (_("Title: %s\nMRL: %s\nStart: %s\nDuration: %s"),
			      item->title, item->mrl, start, length);
      gtk_selection_data_set_text (sel, text, -1);
      g_free (text);
    }
    break;
  }
}

static void clip_clear_cb (GtkClipboard *cb, gpointer data)
{
  play_item_dispose (data);
}

void clip_set_play_item_from_selection (GtkWidget *view)
{
  GtkTreeIter iter;
  GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
  GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));

  if (gtk_tree_selection_get_selected (sel, NULL, &iter))
  {
    GValue vi = {0};
    play_item_t *item;

    gtk_tree_model_get_value (model, &iter, 2, &vi);

    item = g_value_peek_pointer (&vi);
    if (item)
    {
      item = play_item_copy (g_value_peek_pointer (&vi));
      gtk_clipboard_set_with_data (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
				   targets, G_N_ELEMENTS (targets),
				   clip_get_cb, clip_clear_cb, item);
/*
      gtk_clipboard_set_with_data (gtk_clipboard_get (GDK_SELECTION_PRIMARY),
				   targets, G_N_ELEMENTS (targets),
				   clip_get_cb, clip_clear_cb,
				   play_item_copy (item));
*/
    }
    g_value_unset (&vi);
  }
}

play_item_t *clip_get_play_item (void)
{
  GtkSelectionData *sel = gtk_clipboard_wait_for_contents
			    (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
			     gdk_atom_intern ("GXINE_PLAY_ITEM_T", TRUE));
  if (sel)
  {
    play_item_t *item = play_item_copy ((play_item_t *)gtk_selection_data_get_data (sel));
    gtk_selection_data_free (sel);
    return item;
  }
  return NULL;
}
