/*****************************************************************
 * gmerlin - a general purpose multimedia framework and applications
 *
 * Copyright (c) 2001 - 2012 Members of the Gmerlin project
 * gmerlin-general@lists.sourceforge.net
 * http://gmerlin.sourceforge.net
 *
 * 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, see <http://www.gnu.org/licenses/>.
 * *****************************************************************/

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

#include <config.h>

#include <gavl/metatags.h>

#include <gmerlin/translation.h>

#include <gmerlin/parameter.h>
#include <gmerlin/streaminfo.h>
//#include <msgqueue.h>
#include <gmerlin/player.h>
#include <gmerlin/playermsg.h>
#include <gui_gtk/infowindow.h>
//#include <gui_gtk/textview.h>
#include <gui_gtk/gtkutils.h>

#include <gmerlin/utils.h>

#define FG_SENSITIVE   "#000000"
#define FG_INSENSITIVE "#808080"

enum
  {
  COLUMN_1,
  COLUMN_2,
  COLUMN_FG_COLOR,
  NUM_COLUMNS
  };



#define INFOWINDOW_STATE "infowindow" // Hope there will be just 1 infowindow per application

/* Delay between update calls in Milliseconds */

static const bg_state_var_desc_t state_vars[] =
  {
    { GAVL_META_X,              GAVL_TYPE_INT, GAVL_VALUE_INIT_INT(10)  },
    { GAVL_META_Y,              GAVL_TYPE_INT, GAVL_VALUE_INIT_INT(10)  },
    { GAVL_META_WIDTH,          GAVL_TYPE_INT, GAVL_VALUE_INIT_INT(100) },
    { GAVL_META_HEIGHT,         GAVL_TYPE_INT, GAVL_VALUE_INIT_INT(100) },
    { BG_GTK_INFOWINDOW_VISIBLE, GAVL_TYPE_INT, GAVL_VALUE_INIT_INT(0)   },
    { /* End */ },
  };

#define DELAY_TIME 50

struct bg_gtk_info_window_s
  {
  int x, y, width, height;
  int visible;
  
  /* We store everything interesting locally */
  
  int num_audio_streams;
  int num_video_streams;
  int num_subtitle_streams;
  
  GtkWidget * window;
  
  GtkWidget * treeview;

  bg_control_t ctrl;

  bg_controllable_t * ctrl_p;
  
  guint expand_id;
  guint collapse_id;

  /* Clipboard */
  
  char * clipboard;
  int clipboard_len;

  struct
    {
    GtkWidget * copy_all;
    GtkWidget * copy_selected;
    GtkWidget * menu;
    } menu;

  guint idle_id;

  int have_state;
  };

static void set_dict_internal(GtkTreeModel * model,
                              GtkTreeIter * parent,
                              const gavl_dictionary_t * dict);



static char * iter_to_string(bg_gtk_info_window_t * w, char * ret,
                             int depth,
                             GtkTreeIter * iter, int append_children)
  {
  int i;
  int num_children;
  GtkTreeModel * model;
  char * str;
  GtkTreeIter child;
  
  /* */
  model = gtk_tree_view_get_model(GTK_TREE_VIEW(w->treeview));

  if(iter)
    {
    /* Check if the entry is present at all */
    gtk_tree_model_get(model, iter, COLUMN_FG_COLOR, &str, -1);
    
    if(!strcmp(str, FG_INSENSITIVE))
      {
      g_free(str);
      return ret;
      }
    
    g_free(str);

    /*
     *  Ugly, that's right, but the code isn't meant to be run
     *  for each video pixel :)
     */

    for(i = 0; i < depth; i++)
      ret = gavl_strcat(ret, "  ");

    /* First column */
    gtk_tree_model_get(model, iter, COLUMN_1, &str, -1);

    if(*str)
      ret = gavl_strcat(ret, str);
    else
      {
      g_free(str);
      return ret;
      }

    g_free(str);
    
    /* Second column */
    gtk_tree_model_get(model, iter, COLUMN_2, &str, -1);

    if(*str)
      {
      ret = gavl_strcat(ret, "\t");
      ret = gavl_strcat(ret, str);
      }
    
    g_free(str);

    ret = gavl_strcat(ret, "\n");
    }

  if(!append_children)
    return ret;
  
  num_children = gtk_tree_model_iter_n_children(model, iter);

  if(!num_children)
    return ret;
  
  gtk_tree_model_iter_children(model, &child, iter);
  
  for(i = 0; i < num_children; i++)
    {
    ret = iter_to_string(w, ret, depth + !!(iter),
                         &child, append_children);
    gtk_tree_model_iter_next(model, &child);
    }
  
  return ret;
  
  }


static void set_value(GtkTreeModel * model,
                      GtkTreeIter * iter, const gavl_value_t * val)
  {
  char * tmp_string = NULL;
  switch(val->type)
    {
    case GAVL_TYPE_UNDEFINED:
      break;
    case GAVL_TYPE_INT:
    case GAVL_TYPE_LONG:
    case GAVL_TYPE_FLOAT:
    case GAVL_TYPE_STRING:
      tmp_string = gavl_value_to_string(val);
      break;
    case GAVL_TYPE_AUDIOFORMAT:
      {
      gavl_dictionary_t tmp;
      gavl_dictionary_init(&tmp);
      gavl_audio_format_to_dictionary(val->v.audioformat, &tmp);
      set_dict_internal(model, iter, &tmp);
      gavl_dictionary_free(&tmp);
      }
      break;
    case GAVL_TYPE_VIDEOFORMAT:
      {
      gavl_dictionary_t tmp;
      gavl_dictionary_init(&tmp);
      gavl_video_format_to_dictionary(val->v.videoformat, &tmp);
      set_dict_internal(model, iter, &tmp);
      gavl_dictionary_free(&tmp);
      }
      break;
    case GAVL_TYPE_COLOR_RGB:
      tmp_string = bg_sprintf("%f %f %f",
                              val->v.color[0],
                              val->v.color[1],
                              val->v.color[2]);
      break;
    case GAVL_TYPE_COLOR_RGBA:
      tmp_string = bg_sprintf("%f %f %f %f",
                              val->v.color[0],
                              val->v.color[1],
                              val->v.color[2],
                              val->v.color[3]);
      break;
    case GAVL_TYPE_POSITION:
      tmp_string = bg_sprintf("%f %f",
                              val->v.color[0],
                              val->v.color[1]);
      break;
    case GAVL_TYPE_DICTIONARY:
      {
      set_dict_internal(model, iter,  val->v.dictionary);
      }
      break;
    case GAVL_TYPE_ARRAY:
      {
      GtkTreeIter child;
      const gavl_array_t * arr;
      int i;

      arr = val->v.array;

      for(i = 0; i < arr->num_entries; i++)
        {
        gtk_tree_store_append(GTK_TREE_STORE(model), &child, iter);
        tmp_string = bg_sprintf("#%d", i+1);
        gtk_tree_store_set(GTK_TREE_STORE(model), &child, COLUMN_1,
                           tmp_string, -1);
        free(tmp_string);
        tmp_string = NULL;
        set_value(model, &child, &arr->entries[i]);
        
        }
      }
      break;
    case GAVL_TYPE_BINARY:
      {
      tmp_string = bg_sprintf("Binary data (%d bytes)", val->v.buffer->len);
      }
      
    }

  if(tmp_string)
    {
    gtk_tree_store_set(GTK_TREE_STORE(model), iter, COLUMN_2, tmp_string, -1);
    free(tmp_string);
    }
  
  }

static void set_dict_internal(GtkTreeModel * model,
                              GtkTreeIter * parent,
                              const gavl_dictionary_t * dict)
  {
  int i;
  GtkTreeIter item;
  
  for(i = 0; i < dict->num_entries; i++)
    {
    gtk_tree_store_append(GTK_TREE_STORE(model), &item, parent);
    gtk_tree_store_set(GTK_TREE_STORE(model), &item, COLUMN_1,
                       dict->entries[i].name, -1);

    set_value(model, &item, &dict->entries[i].v);
    }
  }

static void set_dict(bg_gtk_info_window_t * w,
                     const gavl_dictionary_t * dict)
  {
  GtkTreeModel * model;
  
  model = gtk_tree_view_get_model(GTK_TREE_VIEW(w->treeview));
  gtk_tree_store_clear(GTK_TREE_STORE(model));
  set_dict_internal(model, NULL, dict);
  }

static int put_msg(void * data, gavl_msg_t * msg)
  {

  bg_gtk_info_window_t * w = data;

  switch(msg->NS)
    {
    case GAVL_MSG_NS_SRC:
      switch(msg->ID)
        {
        }
      break;
    case BG_MSG_NS_STATE:
      switch(gavl_msg_get_id(msg))
        {
        case BG_MSG_STATE_CHANGED:
          {
          gavl_value_t val;
          const char * ctx;
          const char * var;
          int last;
          
          gavl_value_init(&val);

          bg_msg_get_state(msg,
                           &last,
                           &ctx,
                           &var,
                           &val, NULL);

          if(!strcmp(ctx, BG_PLAYER_STATE_CTX))
            {
            if(!strcmp(var, BG_PLAYER_STATE_CURRENT_TRACK))         // dictionary
              {
              const gavl_dictionary_t * t;

              if(!(t = gavl_value_get_dictionary(&val)))
                {
                gavl_value_free(&val);
                break;
                }
              set_dict(w, t);
              gavl_value_free(&val);
              break;
              }
            else if(!strcmp(var, BG_PLAYER_STATE_MODE))          // int
              {
              }
            else if(!strcmp(var, BG_PLAYER_STATE_MUTE))          // int
              {
              }
            }
          else if(!strcmp(gavl_msg_get_arg_string_c(msg, 1), BG_GTK_INFOWINDOW_STATE_CTX))
            {
            if(w->have_state)
              {
              gavl_value_free(&val);
              break;
              }
            if(!strcmp(var, GAVL_META_X))
              gavl_value_get_int(&val, &w->x);
            else if(!strcmp(var, GAVL_META_Y))
              gavl_value_get_int(&val, &w->y);
            else if(!strcmp(var, GAVL_META_WIDTH))
              gavl_value_get_int(&val, &w->width);
            else if(!strcmp(var, GAVL_META_HEIGHT))
              gavl_value_get_int(&val, &w->height);
            else if(!strcmp(var, BG_GTK_INFOWINDOW_VISIBLE))
              gavl_value_get_int(&val, &w->visible);
            
            if(last)
              {
              if(w->visible)
                bg_gtk_info_window_show(w);
              else
                bg_gtk_info_window_hide(w);

              w->have_state = 1;
              }
            }
          gavl_value_free(&val);
          break;
          }
        }
      break;
    }
  
  return 1;
  }

static gboolean idle_callback(gpointer data)
  {
  bg_gtk_info_window_t * w = data;
  bg_msg_sink_iteration(w->ctrl.evt_sink);
  return TRUE;
  }

static gboolean delete_callback(GtkWidget * w, GdkEventAny * event,
                                gpointer data)
  {
  bg_gtk_info_window_hide(data);  
  return TRUE;
  }

static gboolean configure_callback(GtkWidget * w, GdkEventConfigure *event,
                                   gpointer data)
  {
  bg_gtk_info_window_t * win;
  gavl_value_t val;
  gavl_value_init(&val);
  
  win = data;
  win->x = event->x;
  win->y = event->y;
  win->width = event->width;
  win->height = event->height;
  gdk_window_get_root_origin(gtk_widget_get_window(win->window), &win->x, &win->y);

  gavl_value_set_int(&val, win->x);
  bg_state_set(NULL, 0, BG_GTK_INFOWINDOW_STATE_CTX, GAVL_META_X, &val, win->ctrl.cmd_sink, BG_CMD_SET_STATE);
  gavl_value_set_int(&val, win->y);
  bg_state_set(NULL, 0, BG_GTK_INFOWINDOW_STATE_CTX, GAVL_META_Y, &val, win->ctrl.cmd_sink, BG_CMD_SET_STATE);
  gavl_value_set_int(&val, win->width);
  bg_state_set(NULL, 0, BG_GTK_INFOWINDOW_STATE_CTX, GAVL_META_WIDTH, &val, win->ctrl.cmd_sink, BG_CMD_SET_STATE);
  gavl_value_set_int(&val, win->height);
  bg_state_set(NULL, 1, BG_GTK_INFOWINDOW_STATE_CTX, GAVL_META_HEIGHT, &val, win->ctrl.cmd_sink, BG_CMD_SET_STATE);
  
  return FALSE;
  }



// #define FREE(str) if(str) free(str);str=NULL;

/* Clipboard */

#define TARGET_TEXT_PLAIN 1

static const GtkTargetEntry copy_paste_entries[] =
  {
    { "STRING", 0, TARGET_TEXT_PLAIN },
  };

/* Callback functions for the clipboard */

static void clipboard_get_func(GtkClipboard *clipboard,
                               GtkSelectionData *selection_data,
                               guint info,
                               gpointer data)
  {
  GdkAtom type_atom;
  bg_gtk_info_window_t * w = (bg_gtk_info_window_t*)data;

  type_atom = gdk_atom_intern("STRING", FALSE);
  if(!type_atom)
    return;
  
  gtk_selection_data_set(selection_data, type_atom, 8, (uint8_t*)w->clipboard,
                         w->clipboard_len);
  }

static void clipboard_clear_func(GtkClipboard *clipboard,
                                 gpointer data)
  {
  bg_gtk_info_window_t * w = (bg_gtk_info_window_t*)data;
  if(w->clipboard)
    {
    free(w->clipboard);
    w->clipboard_len = 0;
    w->clipboard = NULL;
    }
  }

static void copy_selected(bg_gtk_info_window_t * w)
  {
  GtkTreeIter iter;
  GtkTreeSelection * selection;
  GtkClipboard *clipboard;
  GdkAtom clipboard_atom;
  
  clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE);   
  clipboard = gtk_clipboard_get(clipboard_atom);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(w->treeview));
  
  gtk_clipboard_set_with_data(clipboard,
                              copy_paste_entries,
                              sizeof(copy_paste_entries)/
                              sizeof(copy_paste_entries[0]),
                              clipboard_get_func,
                              clipboard_clear_func,
                              (gpointer)w);

  
  
  if(w->clipboard)
    free(w->clipboard);

  gtk_tree_selection_get_selected(selection,
                                  NULL,
                                  &iter);
  
  w->clipboard = iter_to_string(w, NULL, 0, &iter, 1);

  if(w->clipboard)
    {
    /* Remove trailing '\n' */
    w->clipboard[strlen(w->clipboard)-1] = '\0';
    w->clipboard_len = strlen(w->clipboard) + 1;
    }
  else
    w->clipboard_len = 0;
  }

static void copy_all(bg_gtk_info_window_t * w)
  {
  GtkClipboard *clipboard;
  GdkAtom clipboard_atom;
  clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE);   
  clipboard = gtk_clipboard_get(clipboard_atom);
  
  gtk_clipboard_set_with_data(clipboard,
                              copy_paste_entries,
                              sizeof(copy_paste_entries)/
                              sizeof(copy_paste_entries[0]),
                              clipboard_get_func,
                              clipboard_clear_func,
                              (gpointer)w);
  
  if(w->clipboard)
    free(w->clipboard);
  w->clipboard = iter_to_string(w, NULL, 0, NULL, 1);

  if(w->clipboard)
    {
    w->clipboard_len = strlen(w->clipboard) + 1;
    }
  else
    w->clipboard_len = 0;
  }

static void menu_callback(GtkWidget * wid, gpointer data)
  {
  bg_gtk_info_window_t * w = (bg_gtk_info_window_t*)data;
  
  /* Add files */
  
  if(wid == w->menu.copy_all)
    copy_all(w);
  else if(wid == w->menu.copy_selected)
    copy_selected(w);
  }

static GtkWidget *
create_item(bg_gtk_info_window_t * w, GtkWidget * parent,
            const char * label, const char * pixmap)
  {
  GtkWidget * ret;
  char * path = NULL;
  
  if(pixmap)
    path = bg_search_file_read("icons", pixmap);

  ret = bg_gtk_image_menu_item_new(label, path);

  if(path)
    free(path);
  
  g_signal_connect(G_OBJECT(ret), "activate", G_CALLBACK(menu_callback),
                   (gpointer)w);
  gtk_widget_show(ret);
  gtk_menu_shell_append(GTK_MENU_SHELL(parent), ret);
  return ret;
  }


static void init_menu(bg_gtk_info_window_t * w)
  {
  w->menu.menu = gtk_menu_new();
  w->menu.copy_selected =
    create_item(w, w->menu.menu, TR("Copy selected"), "copy_16.png");
  w->menu.copy_all =
    create_item(w, w->menu.menu, TR("Copy all"), "copy_16.png");
  
  }

static gboolean button_press_callback(GtkWidget * wid, GdkEventButton * evt,
                                      gpointer data)
  {
  bg_gtk_info_window_t * w = (bg_gtk_info_window_t *)data;

  if(evt->button == 3)
    {
    gtk_menu_popup_at_pointer(GTK_MENU(w->menu.menu), (GdkEvent*)evt);
    return  FALSE;
    }
  return FALSE;
  }

void
bg_gtk_info_window_disconnect(bg_gtk_info_window_t * w)
  {
  if(!w->ctrl_p)
    return;
  
  bg_controllable_disconnect(w->ctrl_p, &w->ctrl);
  w->ctrl_p = NULL;
  }

void
bg_gtk_info_window_connect(bg_gtk_info_window_t * w, bg_controllable_t * ctrl_p)
  {
  w->ctrl_p = ctrl_p;
  bg_controllable_connect(w->ctrl_p, &w->ctrl);
  }

bg_gtk_info_window_t *
bg_gtk_info_window_create(bg_player_t * player)
  {
  GtkTreeStore * store;
  GtkWidget * scrolledwin;
  GtkCellRenderer * text_renderer;
  GtkTreeViewColumn *column;
  GtkTreeSelection  *selection;

  bg_gtk_info_window_t * ret;

  ret = calloc(1, sizeof(*ret));
  
  /* Create objects */
  
  ret->window = bg_gtk_window_new(GTK_WINDOW_TOPLEVEL);
  
  g_signal_connect(G_OBJECT(ret->window), "configure-event",
                   G_CALLBACK(configure_callback), (gpointer)ret);
  
  gtk_window_set_title(GTK_WINDOW(ret->window), TR("Gmerlin Track Info"));
  
  bg_control_init(&ret->ctrl, bg_msg_sink_create(put_msg, ret, 0));
  
  /* Link message queue to the player */

  ret->ctrl_p = bg_player_get_controllable(player);

  bg_controllable_connect(ret->ctrl_p, &ret->ctrl);
  
  /* Create treeview */
  
  store = gtk_tree_store_new(NUM_COLUMNS, G_TYPE_STRING,
                             G_TYPE_STRING, G_TYPE_STRING);
  
  ret->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(ret->treeview), 0);
    
  gtk_widget_set_events(ret->treeview, GDK_BUTTON_PRESS_MASK);
  
  g_signal_connect(G_OBJECT(ret->treeview), "button-press-event",
                   G_CALLBACK(button_press_callback), (gpointer)ret);
  
  /* Column 1 */
  text_renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new();
  gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
  gtk_tree_view_column_add_attribute(column, text_renderer, "text", COLUMN_1);
  gtk_tree_view_column_add_attribute(column, text_renderer,
                                     "foreground", COLUMN_FG_COLOR);

  gtk_tree_view_append_column (GTK_TREE_VIEW(ret->treeview), column);

  /* Column 2 */
  text_renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new();
  gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
  gtk_tree_view_column_add_attribute(column, text_renderer, "text", COLUMN_2);
  gtk_tree_view_append_column (GTK_TREE_VIEW(ret->treeview), column);
  gtk_tree_view_column_add_attribute(column, text_renderer,
                                     "foreground", COLUMN_FG_COLOR);
  
  gtk_widget_show(ret->treeview);
  
  /* Selection mode */
  
  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ret->treeview));
  gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
  
  /* Set callbacks */
  ret->idle_id = g_timeout_add(DELAY_TIME, idle_callback, (gpointer)ret);
  g_signal_connect(G_OBJECT(ret->window), "delete_event",
                   G_CALLBACK(delete_callback), (gpointer)ret);

  /* pack objects */
  
  scrolledwin = gtk_scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  
  gtk_container_add(GTK_CONTAINER(scrolledwin), ret->treeview);
  gtk_widget_show(scrolledwin);

  /* */
  gtk_container_add(GTK_CONTAINER(ret->window), scrolledwin);

  init_menu(ret);
  
  return ret;
  }

void bg_gtk_info_window_destroy(bg_gtk_info_window_t * w)
  {
  bg_control_cleanup(&w->ctrl);
  if(w->clipboard)
    free(w->clipboard);

  g_source_remove(w->idle_id);
  
  gtk_widget_destroy(w->window);
  free(w);
  }


/* Show/hide the window */

void bg_gtk_info_window_show(bg_gtk_info_window_t * w)
  {
  gavl_value_t val;
  
  if(!w->width || !w->height)
    gtk_window_set_position(GTK_WINDOW(w->window), GTK_WIN_POS_CENTER);
  
  gtk_widget_show(w->window);

  if(w->width && w->height)
    bg_gtk_decorated_window_move_resize_window(w->window,
                                            w->x, w->y, w->width, w->height);
  else
    gtk_window_resize(GTK_WINDOW(w->window), 400, 400);

  gavl_value_init(&val);
  gavl_value_set_int(&val, 1);
  
  bg_state_set(NULL, 1, BG_GTK_INFOWINDOW_STATE_CTX, BG_GTK_INFOWINDOW_VISIBLE,
               &val, w->ctrl.cmd_sink, BG_CMD_SET_STATE);
  
  w->visible = 1;

  }

void bg_gtk_info_window_hide(bg_gtk_info_window_t * win)
  {
  gavl_value_t val;

  gavl_value_init(&val);
  gavl_value_set_int(&val, 0);
  
  bg_state_set(NULL, 1, BG_GTK_INFOWINDOW_STATE_CTX, BG_GTK_INFOWINDOW_VISIBLE,
               &val, win->ctrl.cmd_sink, BG_CMD_SET_STATE);
  
  gtk_widget_hide(win->window);
  win->visible = 0;
  }

void bg_gtk_info_window_init_state(gavl_dictionary_t * dict)
  {
  bg_state_init_ctx(dict, BG_GTK_INFOWINDOW_STATE_CTX, state_vars);
  }
