/*
 * XFCE plugin for the MOC player
 * 
 * Control the MOC (Media On Console) player from the XFCE panel!
 * 
 * Author: Jakub Krauz
 * E-mail: jakub.krauz@volny.cz
 * Univerzity of West Bohemia
 * Department of Computer Science and Engineering
 * Pilsen, 2012
 */

#include <gtk/gtk.h>
#include <libxfce4util/libxfce4util.h>
#include <libxfce4panel/xfce-panel-plugin.h>
#include <libxfce4panel/xfce-hvbox.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>

#include "plugin.h"
#include "plugin_dialogs.h"
#include "client.h"
#include "logger.h"


#define USE_SVG_ICONS TRUE

#define CMD      moc_command*
#define BUF_LEN  1<<10



/* module's global variables */
static XfcePanelPlugin *xfce_panel_plugin;



/* Function prototypes */

void moc_plugin_construct(XfcePanelPlugin* plugin);
static MocPlugin* moc_plugin_new(XfcePanelPlugin* plugin);
static void moc_plugin_free(XfcePanelPlugin *plugin, MocPlugin* moc_plugin);
static void orientation_changed(XfcePanelPlugin *plugin, GtkOrientation orientation, MocPlugin *moc_plugin);
static gboolean size_changed(XfcePanelPlugin *plugin, gint size, MocPlugin *moc_plugin);
static GtkWidget* create_bt_box(GtkOrientation orientation, const char* icon_color);
static void add_cmdbtn(GtkBox* box, const char* icon_name, const char* icon_color, CMD command);
static void cmdbtn_clicked(GtkWidget* widget, gpointer data);
static GtkWidget* create_menu_box(GtkOrientation orientation, const char* icon_color);
static void add_menu_item(GtkMenuShell* menu, const char* label, const char* icon_name, CMD command);
static void menuitem_activate(GtkMenuItem* menu_item, gpointer data);
static gboolean menubtn_event(GtkWidget* widget, GdkEvent* event);



/* register the plugin */
XFCE_PANEL_PLUGIN_REGISTER(moc_plugin_construct);



/**
 * Construct the plugin.
 * This function is registered with the XFCE_PANEL_PLUGIN_REGISTER macro. It is called
 * everytime the plugin is created.
 * @param plugin The XfcePanelPlugin structure.
 */
void
moc_plugin_construct(XfcePanelPlugin* plugin)
{
  MocPlugin *moc_plugin;
  
  xfce_panel_plugin = plugin;

  moc_plugin = moc_plugin_new(plugin);  // create the plugin
  gtk_container_add(GTK_CONTAINER(plugin), moc_plugin->ebox);  // add the ebox to the panel
  xfce_panel_plugin_add_action_widget(plugin, moc_plugin->ebox);  // show the panel's right-click menu on this ebox

  /* connect plugin signals */
  g_signal_connect(G_OBJECT(plugin), "free-data", G_CALLBACK(moc_plugin_free), moc_plugin);
  g_signal_connect(G_OBJECT(plugin), "size-changed", G_CALLBACK(size_changed), moc_plugin);
  g_signal_connect(G_OBJECT(plugin), "orientation-changed", G_CALLBACK(orientation_changed), moc_plugin);

  /* show the configure menu item and connect signal */
  xfce_panel_plugin_menu_show_configure(plugin);
  g_signal_connect(G_OBJECT(plugin), "configure-plugin", G_CALLBACK(moc_plugin_configure), moc_plugin);

  /* show the about menu item and connect signal */
  xfce_panel_plugin_menu_show_about(plugin);
  g_signal_connect(G_OBJECT(plugin), "about", G_CALLBACK(moc_plugin_about), moc_plugin);
}


/**
 * Create widgets according to actual settings.
 * The function adds either all command buttons or one button with connected menu to the panel.
 * @param moc_plugin The MocPlugin structure.
 */
void
create_control_widgets(MocPlugin* moc_plugin)
{
  GtkOrientation orientation;
  
  orientation = xfce_panel_plugin_get_orientation(moc_plugin->plugin);
  if (moc_plugin->buttons_in_panel)
  {
    moc_plugin->bt_box = create_bt_box(orientation, moc_plugin->icon_color);
    gtk_container_add(GTK_CONTAINER(moc_plugin->hvbox), moc_plugin->bt_box);
    gtk_widget_show(moc_plugin->bt_box);
  }
  else
  {
    moc_plugin->menu_box = create_menu_box(orientation, moc_plugin->icon_color);
    gtk_container_add(GTK_CONTAINER(moc_plugin->hvbox), moc_plugin->menu_box);
    gtk_widget_show(moc_plugin->menu_box);
  }
}


/**
 * Load the plugin settings from the settings file.
 * See settings.h for the filaname.
 * @param mocp_plugin The moc-plugin structure to which load the setttings.
 */
void
load_settings(MocPlugin* moc_plugin)
{
  FILE *fr;
  char path[PATH_MAX];
  int val;
  char color[100];
  char buf[BUF_LEN];
  char *filename = SETTINGS_FILENAME;
  
  moc_plugin->buttons_in_panel = BTINPANEL_DEFAULT;
  snprintf(moc_plugin->icon_color, MAX_COLOR_LEN, "%s", ICONCOLOR_DEFAULT);

  if (filename[0] == '~')
    snprintf(path, PATH_MAX, "%s/%s", getenv("HOME"), filename + 2);
  else
    strncpy(path, filename, PATH_MAX);
  if ((fr = fopen(path, "r")) != NULL)
  {
    while (fgets(buf, BUF_LEN, fr) != NULL)
    {
      if (sscanf(buf, "buttons-in-panel=%d", &val))
      {
        logit(DBG, "Loading settings: buttons-in-panel=%d", val);
        moc_plugin->buttons_in_panel = (val == 0) ? FALSE : TRUE;
      }
      if (sscanf(buf, "icon-color=%99s", &color))
      {
        logit(DBG, "Loading settings: icon-color=%s", color);
        snprintf(moc_plugin->icon_color, MAX_COLOR_LEN, "%s", color);
      }
    }
    fclose(fr);
  }
  else
    logit(WARN, "Load settings: Cannot open the configuration file %s.", filename);
}


/**
 * Save the plugin settings into the settings file. 
 * See settings.h for the filename.
 * @param mocp_plugin The moc-plugin structure to be saved.
 */
void
save_settings(MocPlugin* moc_plugin)
{
  FILE *fw;
  char path[PATH_MAX];
  char *filename = SETTINGS_FILENAME;
  
  if (moc_plugin == NULL)
  {
    logit(WARN, "Save settings: NULL pointer to MocPlugin structure.");
    return;
  }
  
  if (filename[0] == '~')
    snprintf(path, PATH_MAX, "%s/%s", getenv("HOME"), filename + 2);
  else
    strncpy(path, filename, PATH_MAX);
  if ((fw = fopen(path, "w")) != NULL)
  {
    fprintf(fw, "%s=%d\n", BTINPANEL_STR, (moc_plugin->buttons_in_panel) ? 1 : 0);
    fprintf(fw, "%s=%s", ICONCOLOR_STR, moc_plugin->icon_color);
    fclose(fw);
  }
  else
    logit(ERR, "Cannot write to file \"%s\".", filename);
}



/***************************************** local functions ****************************************/

/**
 * Create new moc plugin.
 * This function initialises the MocPlugin structure and creates all its widgets.
 * @param plugin The XfcePanelPlugin structure.
 * @return The newly created MocPlugin structure.
 */
static MocPlugin*
moc_plugin_new(XfcePanelPlugin* plugin)
{
  MocPlugin *moc_plugin;
  GtkOrientation orientation;

  moc_plugin = panel_slice_new0(MocPlugin);  // allocate memory for the plugin structure
  moc_plugin->plugin = plugin;
  load_settings(moc_plugin);
  orientation = xfce_panel_plugin_get_orientation(plugin);  // get the orientation

  /* create basic panel widgets */
  moc_plugin->ebox = gtk_event_box_new();
  gtk_widget_show(moc_plugin->ebox);
  moc_plugin->hvbox = xfce_hvbox_new(orientation, FALSE, 2);
  gtk_widget_show(moc_plugin->hvbox);
  gtk_container_add(GTK_CONTAINER(moc_plugin->ebox), moc_plugin->hvbox);
  
  create_control_widgets(moc_plugin);  // create control widgets according to the settings

  return moc_plugin;
}


/**
 * Free the plugin
 * This function destroys all plugin widgets (including open dialog if any)
 * and frees the plugin structure. 
 * @param plugin The XfcePanelPlugin structure.
 * @param moc_plugin The MocPlugin structure.
 */
static void
moc_plugin_free(XfcePanelPlugin *plugin, MocPlugin* moc_plugin)
{
  GtkWidget *dialog;

  /* check if any of the dialogs is still open. if so, destroy it */
  dialog = g_object_get_data(G_OBJECT(plugin), "configure-dialog");
  if (G_UNLIKELY(dialog != NULL))
    gtk_widget_destroy(dialog);
  dialog = g_object_get_data(G_OBJECT(plugin), "about-dialog");
  if (G_UNLIKELY(dialog != NULL))
    gtk_widget_destroy(dialog);

  gtk_widget_destroy(moc_plugin->hvbox);  // destroy panel widgets
  panel_slice_free(MocPlugin, moc_plugin);  // free the plugin structure
}


/**
 * Change the orientation according to panel settings.
 * @param plugin Not used.
 * @param orientation The new orientation.
 * @param moc_plugin The MocPlugin structure.
 */
static void
orientation_changed(XfcePanelPlugin *plugin, GtkOrientation orientation, MocPlugin *moc_plugin)
{
  xfce_hvbox_set_orientation(XFCE_HVBOX(moc_plugin->hvbox), orientation);
}


/**
 * Change the size according to panel settings.
 * @param plugin The XfcePanelPlugin structure.
 * @param size The new size.
 * @param moc_plugin The MocPlugin structure.
 * @return TRUE if the change was handled, else FALSE
 */
static gboolean
size_changed(XfcePanelPlugin *plugin, gint size, MocPlugin *moc_plugin)
{
  GtkOrientation orientation;

  orientation = xfce_panel_plugin_get_orientation(plugin);
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    gtk_widget_set_size_request(GTK_WIDGET(plugin), -1, size);
  else
    gtk_widget_set_size_request(GTK_WIDGET(plugin), size, -1);

  return TRUE; // the orientation was handled
}


/**
 * Create a box with command buttons. These buttons are shown in the panel and enables user
 * to send commands (like play or stop) to the moc server.
 * @param orientation Orientation of the plugin.
 * @param icon_color Color of icons displayed on the buttons ("white" or "black" available).
 * @return Newly created box with the command buttons.
 */
static GtkWidget*
create_bt_box(GtkOrientation orientation, const char* icon_color)
{
  GtkWidget *bt_box;
  static moc_command commands[] = {MOC_PREV, MOC_PLAY, MOC_PAUSE, MOC_STOP, MOC_NEXT};

  bt_box = xfce_hvbox_new(orientation, FALSE, 2);
  add_cmdbtn(GTK_BOX(bt_box), "mocp-prev", icon_color, &commands[0]);
  add_cmdbtn(GTK_BOX(bt_box), "mocp-play", icon_color, &commands[1]);
  add_cmdbtn(GTK_BOX(bt_box), "mocp-pause", icon_color, &commands[2]);
  add_cmdbtn(GTK_BOX(bt_box), "mocp-stop", icon_color, &commands[3]);
  add_cmdbtn(GTK_BOX(bt_box), "mocp-next", icon_color, &commands[4]);

  return bt_box;
}


/**
 * Add a command-button to the passed-in box. The button is set an icon
 * given by the icon string. The button is also connected with the specified
 * command which will be sent to the moc server on click.
 * @param box Box to add the button in.
 * @param icon Icon name for the button.
 * @param command Command for the moc server to be connected with the button.
 */
static void
add_cmdbtn(GtkBox* box, const char* icon_name, const char* icon_color, CMD command)
{
  GtkWidget *button;
  GtkWidget *image;
  char buf[100];
  
  if (USE_SVG_ICONS)
    snprintf(buf, 100, "%s-%s-symbolic", icon_name, icon_color);
  else
    snprintf(buf, 100, "%s-%s", icon_name, icon_color);
  logit(DBG, "icon name: %s", buf);

  button = xfce_panel_create_button();
  image = gtk_image_new_from_icon_name(buf, GTK_ICON_SIZE_MENU);
  gtk_container_add(GTK_CONTAINER(button), image);
  gtk_widget_show(image);
  g_signal_connect(button, "clicked", G_CALLBACK(cmdbtn_clicked), (gpointer) command);
  xfce_panel_plugin_add_action_widget(xfce_panel_plugin, button);  // show the panel's right click menu on this button
  gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
  gtk_widget_show(button);
}


/**
 * Respond to a button-click by sending the moc server command passed-in
 * as data.
 * @param widget Not used.
 * @param data Command to be sent to the moc server.
 */
static void
cmdbtn_clicked(GtkWidget* widget, gpointer data)
{
  moc_command cmd = *((CMD) data);
  send_command(cmd);
}


/**
 * Create a box with a button and a menu which will be shown on the button-click event.
 * The menu items enable sending commands to the moc server.
 * @param orientation Orientation of the plugin.
 * @param icon_color Color of the icon displayed in the panel ("white" and "black" available).
 * @return A box with a button connected with the command-menu.
 */
static GtkWidget*
create_menu_box(GtkOrientation orientation, const char* icon_color)
{
  GtkWidget *menu_box;
  GtkWidget *menu;
  GtkWidget *button; 
  GtkWidget *image;
  char buf[BUF_LEN];
  static moc_command commands[] = {MOC_PLAY, MOC_STOP, MOC_PAUSE, MOC_PREV, MOC_NEXT};
  
  menu_box = xfce_hvbox_new(orientation, FALSE, 2);
  button = xfce_panel_create_button();
  snprintf(buf, BUF_LEN, "xfce4-moc-plugin-%s", icon_color);
  image = gtk_image_new_from_icon_name(buf, GTK_ICON_SIZE_MENU);
  gtk_container_add(GTK_CONTAINER(button), image);
  gtk_box_pack_start(GTK_BOX(menu_box), button, FALSE, FALSE, 0);
  gtk_widget_show_all(button);

  menu = gtk_menu_new();
  add_menu_item(GTK_MENU_SHELL(menu), "Play", "media-playback-start", &commands[0]);
  add_menu_item(GTK_MENU_SHELL(menu), "Stop", "media-playback-stop", &commands[1]);
  add_menu_item(GTK_MENU_SHELL(menu), "Pause", "media-playback-pause", &commands[2]);
  add_menu_item(GTK_MENU_SHELL(menu), "Previous", "media-skip-backward", &commands[3]);
  add_menu_item(GTK_MENU_SHELL(menu), "Next", "media-skip-forward", &commands[4]);
  
  g_signal_connect_swapped(button, "event", G_CALLBACK(menubtn_event), menu);
  xfce_panel_plugin_add_action_widget(xfce_panel_plugin, button);  // show the panel's right click menu on this button

  return menu_box;
}


/**
 * Create a menu item with a given label and icon and add it to the menu shell.
 * The menu item activate event is asociated with the command to be sent to the moc server.
 * @param menu The menu shell in which to add the menu item.
 * @param label The label of the menu item.
 * @param icon_name The icon name of the menu item.
 * @param command The associated command for the moc player.
 */
static void
add_menu_item(GtkMenuShell* menu, const char* label, const char* icon_name, CMD command)
{
  GtkWidget *menu_item;
  GtkWidget *image;
  GtkIconTheme *theme;
  
  menu_item = gtk_image_menu_item_new_with_label(label);
  theme = gtk_icon_theme_get_default();
  if (gtk_icon_theme_has_icon(theme, icon_name))
  {
    image = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_BUTTON);
    gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image);
    g_signal_connect(menu_item, "activate", G_CALLBACK(menuitem_activate), (gpointer) command);
    gtk_widget_show(image);
  }
  gtk_menu_shell_append(menu, menu_item);
  gtk_widget_show(menu_item);
}


/**
 * Respond to a menu-item activate event by sending the moc server command passed-in
 * as data.
 * @param menu_item Not used.
 * @param data Command to be sent to the moc server.
 */
static void
menuitem_activate(GtkMenuItem* menu_item, gpointer data)
{
  cmdbtn_clicked(NULL, data);
}


/**
 * Respond to a button-press by posting a menu passed in as widget.
 * Note that the "widget" argument is the menu being posted, NOT
 * the button that was pressed.
 * @param widget The menu to be showed.
 * @param event The button event.
 * @return TRUE if the event was handled, else FALSE
 */
static gboolean
menubtn_event(GtkWidget* widget, GdkEvent* event)
{
  
  if (event->type == GDK_BUTTON_PRESS)
  {
    GdkEventButton *bevent = (GdkEventButton *) event;
    if (bevent->button == 1)  // it was a left-click
    {
      gtk_menu_popup(GTK_MENU(widget), NULL, NULL, NULL, NULL, bevent->button, bevent->time);
      return TRUE;
    }
  }
  return FALSE;
}
