/*
 * Copyright (C) 2010 Marco Diego Aurélio Mesquita
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Authors:
 *   Marco Diego Aurélio Mesquita <marcodiegomesquita@gmail.com>
 */

#include <config.h>

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

#include <gladeui/glade.h>

#include <stdlib.h>
#include <locale.h>
#include <glib/gi18n-lib.h>

#include "glade-preview-tokens.h"

static void
display_help_and_quit (const GOptionEntry * entries)
{
  GOptionContext *context;
  context = g_option_context_new (_("- previews a glade UI definition"));
  g_option_context_add_main_entries (context, entries, NULL);
  g_option_context_add_group (context, gtk_get_option_group (TRUE));

  g_print ("%s\n", g_option_context_get_help (context, TRUE, NULL));

  g_option_context_free (context);
  exit (1);
}

static void
parse_arguments (int argc, char **argv,
                 gchar **toplevel_name,
                 gchar **file_name,
                 gchar **css_file_name,
                 gchar **screenshot_file_name)
{
  gboolean listen = FALSE;
  gboolean version = FALSE;
  GError *error = NULL;
  GOptionEntry entries[] =
    {
        {"filename", 'f', G_OPTION_ARG_NONE, G_OPTION_ARG_FILENAME, file_name, _("Name of the file to preview"), "FILENAME"},
        {"toplevel", 't', G_OPTION_ARG_NONE, G_OPTION_ARG_STRING, toplevel_name, _("Name of the toplevel to preview"), "TOPLEVELNAME"},
        {"screenshot", 0, G_OPTION_ARG_NONE, G_OPTION_ARG_FILENAME, screenshot_file_name, _("File name to save a screenshot"), NULL},
        {"css", 0, G_OPTION_ARG_NONE, G_OPTION_ARG_FILENAME, css_file_name, _("CSS file to use"), NULL},
        {"listen", 'l', 0, G_OPTION_ARG_NONE, &listen, _("Listen standard input"), NULL},
        {"version", 'v', 0, G_OPTION_ARG_NONE, &version, _("Display previewer version"), NULL},
        {NULL}
    };

  *toplevel_name = NULL;
  *file_name = NULL;
  *css_file_name = NULL;
  *screenshot_file_name = NULL;
  
  if (!gtk_init_with_args (&argc, &argv, _("- previews a glade UI definition"),
                           entries, NULL, &error))
    {
      g_printerr (_
                  ("%s\nRun '%s --help' to see a full list of available command line "
                   "options.\n"), error->message, argv[0]);
      g_error_free (error);
      exit (1);
    }

  if (version)
    {
      g_print ("glade-previewer " VERSION "\n");
      exit (0);
    }

  if (listen && *file_name != NULL)
    {
      g_printerr (_
                  ("--listen and --filename must not be simultaneously specified.\n"));
      display_help_and_quit (entries);
    }

  if (!listen && *file_name == NULL)
    {
      g_printerr (_("Either --listen or --filename must be specified.\n"));
      display_help_and_quit (entries);
    }
}

static GtkWidget *
get_toplevel (gchar *name, gchar *string, gsize length)
{
  GError *error = NULL;
  GtkWidget *toplevel = NULL;
  GtkBuilder *builder;
  GObject *object;

  builder = gtk_builder_new ();
  if (!gtk_builder_add_from_string (builder, string, length, &error))
    {
      g_printerr (_("Couldn't load builder definition: %s"), error->message);
      g_error_free (error);
      exit (1);
    }

  if (name == NULL)
    {
      GSList *objects;

      /* Iterate trough objects and search for a window or widget */
      for (objects = gtk_builder_get_objects (builder); objects; objects = g_slist_next (objects))
        {
          GObject *obj = objects->data;

          if (!GTK_IS_WIDGET (obj) || gtk_widget_get_parent (GTK_WIDGET (obj)))
            continue;

          if (toplevel == NULL)
            toplevel = GTK_WIDGET (obj);
          else if (GTK_IS_WINDOW (obj))
            toplevel = GTK_WIDGET (obj);
        }

      g_slist_free (objects);
      if (toplevel == NULL)
        {
          g_printerr (_("UI definition has no previewable widgets.\n"));
          exit (1);
        }
    }
  else
    {
      object = gtk_builder_get_object (builder, name);

      if (object == NULL)
        {
          g_printerr (_("Object %s not found in UI definition.\n"), name);
          exit (1);
        }

      if (!GTK_IS_WIDGET (object))
        {
          g_printerr (_("Object is not previewable.\n"));
          exit (1);
        }

      toplevel = GTK_WIDGET (object);
    }

  g_object_ref_sink (toplevel);
  g_object_unref (builder);

  return toplevel;
}

static gboolean 
on_window_key_press_event (GtkWidget *widget, GdkEventKey *event)
{
  if (event->keyval == GDK_KEY_F12)
    {
      GdkPixbuf *pix = gdk_pixbuf_get_from_window (gtk_widget_get_window (widget), 0, 0, 
                                                   gtk_widget_get_allocated_width (widget),
                                                   gtk_widget_get_allocated_height (widget));
      gchar tmp_file[] = "glade-screenshot-XXXXXX.png"; 

      g_mkstemp (tmp_file);
      gdk_pixbuf_save (pix, tmp_file, "png", NULL, NULL);
      g_object_unref (pix);
      return TRUE;
    }
  
  return FALSE;
}

static GtkWidget*
show_widget (GtkWidget *widget)
{
  GtkWidget *widget_parent;
  GtkWidget *window;

  if (!GTK_IS_WINDOW (widget))
    {
      window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
      
      gtk_window_set_title (GTK_WINDOW (window), _("Preview"));

      /* Reparenting snippet */
      g_object_ref (G_OBJECT (widget));
      widget_parent = gtk_widget_get_parent (widget);
      if (widget_parent != NULL)
        gtk_container_remove (GTK_CONTAINER (widget_parent), widget);
      gtk_container_add (GTK_CONTAINER (window), widget);
      g_object_unref (widget);
    }
  else
    {
      window = widget;
    }

  gtk_widget_add_events (window, GDK_KEY_PRESS_MASK);
  g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
  g_signal_connect (window, "key-press-event", G_CALLBACK (on_window_key_press_event), NULL);
  return window;
}


static gboolean
quit_when_idle (gpointer loop)
{
  g_main_loop_quit (loop);

  return G_SOURCE_REMOVE;
}

static void
check_for_draw (GdkEvent *event, gpointer loop)
{
  if (event->type == GDK_EXPOSE)
    {
      g_idle_add (quit_when_idle, loop);
      gdk_event_handler_set ((GdkEventFunc) gtk_main_do_event, NULL, NULL);
    }

  gtk_main_do_event (event);
}

/* Taken from Gtk sources gtk-reftest.c  */
static void
wait_for_drawing (GdkWindow *window)
{
  GMainLoop *loop;

  loop = g_main_loop_new (NULL, FALSE);
  /* We wait until the widget is drawn for the first time.
   * We can not wait for a GtkWidget::draw event, because that might not
   * happen if the window is fully obscured by windowed child widgets.
   * Alternatively, we could wait for an expose event on widget's window.
   * Both of these are rather hairy, not sure what's best. */
  gdk_event_handler_set (check_for_draw, loop, NULL);
  g_main_loop_run (loop);

  /* give the WM/server some time to sync. They need it.
   * Also, do use popups instead of toplevls in your tests
   * whenever you can. */
  gdk_display_sync (gdk_window_get_display (window));
  g_timeout_add (500, quit_when_idle, loop);
  g_main_loop_run (loop);
}

static GtkWidget *
preview_file (gchar *toplevel_name, gchar *file_name)
{
  GError *error = NULL;
  gchar *buffer;
  gsize length;

  if (g_file_get_contents (file_name, &buffer, &length, &error))
    {
      GtkWidget *widget = get_toplevel (toplevel_name, buffer, length);
      g_free (buffer);

      return show_widget (widget);
    }
  else
    {
      g_printerr (_("Error: %s.\n"), error->message);
      g_error_free (error);
    }

  return NULL;
}

static gchar *
read_buffer (GIOChannel * source)
{
  gchar *buffer;
  gchar *token;
  gchar *tmp;
  GError *error = NULL;

  if (g_io_channel_read_line (source, &token, NULL, NULL, &error) !=
      G_IO_STATUS_NORMAL)
    {
      g_printerr (_("Error: %s.\n"), error->message);
      g_error_free (error);
      exit (1);
    }

  /* Check for quit token */
  if (g_strcmp0 (QUIT_TOKEN, token) == 0)
    {
      g_free (token);
      return NULL;
    }

  /* Loop to load the UI */
  buffer = g_strdup (token);
  do
    {
      g_free (token);
      if (g_io_channel_read_line (source, &token, NULL, NULL, &error) !=
          G_IO_STATUS_NORMAL)
        {
          g_printerr (_("Error: %s.\n"), error->message);
          g_error_free (error);
          exit (1);
        }
      tmp = buffer;
      buffer = g_strconcat (buffer, token, NULL);
      g_free (tmp);
    }
  while (g_strcmp0 ("</interface>\n", token) != 0);
  g_free (token);

  return buffer;
}

static void
update_window (GtkWindow *old_window, GtkWidget *new_widget)
{
  GtkWidget *old_window_child;

  if (!old_window) return;

  /* gtk_widget_destroy the *running preview's* window child */
  old_window_child = gtk_bin_get_child (GTK_BIN (old_window));
  if (old_window_child)
    gtk_widget_destroy (old_window_child);

  /* add new widget as child to the old preview's window */
  if (new_widget)
    {
      gtk_widget_set_parent_window (new_widget, gtk_widget_get_window (GTK_WIDGET (old_window)));
      gtk_container_add (GTK_CONTAINER (old_window), new_widget);
    }

  gtk_widget_show_all (GTK_WIDGET (old_window));
}

static gboolean
on_data_incoming (GIOChannel * source, GIOCondition condition, gpointer data)
{
  gchar *buffer;
  gsize length;
  static GtkWidget *preview_window = NULL;
  GtkWidget *new_widget;
  gchar **split_buffer = NULL;

  gchar *toplevel_name = (gchar *) data;

  buffer = read_buffer (source);
  if (buffer == NULL)
    {
      gtk_main_quit ();
      return FALSE;
    }

  if (condition & G_IO_HUP)
    {
      g_printerr (_("Broken pipe!\n"));
      exit (1);
    }

  length = strlen (buffer);

  /* if it is the first time this is called */
  if (!preview_window)
  {
    new_widget = get_toplevel (toplevel_name, buffer, length);
    preview_window = show_widget (new_widget);
    gtk_widget_show_all (preview_window);
  }
  else
  {
    /* We have an update */
    split_buffer = g_strsplit_set (buffer + UPDATE_TOKEN_SIZE, "\n", 2);

    g_free (buffer);
    if (!split_buffer) return FALSE;

    toplevel_name = split_buffer[0];
    buffer = split_buffer[1];
    length = strlen (buffer);

    new_widget = get_toplevel (toplevel_name, buffer, length);

    update_window (GTK_WINDOW (preview_window), new_widget);
  }

  if (!split_buffer)
  {
    /* This means we're not in an update, we should free the buffer. */
    g_free (buffer);
  }
  else
  {
    /* This means we've had an update, buffer was already freed. */
    g_free (split_buffer[0]);
    g_free (split_buffer[1]);
    g_free (split_buffer);
  }

  return TRUE;
}

static void
start_listener (gchar * toplevel_name)
{
  GIOChannel *input;

#ifdef WINDOWS
  input = g_io_channel_win32_new_fd (fileno (stdin));
#else
  input = g_io_channel_unix_new (fileno (stdin));
#endif

  g_io_add_watch (input, G_IO_IN | G_IO_HUP, on_data_incoming, toplevel_name);
}

int
main (int argc, char **argv)
{
  gchar *file_name;
  gchar *toplevel_name;
  gchar *css_file_name;
  gchar *shot_file_name;

#ifdef ENABLE_NLS
  setlocale (LC_ALL, "");
  bindtextdomain (GETTEXT_PACKAGE, glade_app_get_locale_dir ());
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  textdomain (GETTEXT_PACKAGE);
#endif

  parse_arguments (argc, argv, &toplevel_name, &file_name, &css_file_name, &shot_file_name);

  gtk_init (&argc, &argv);
  glade_app_get ();

  if (css_file_name)
    {
      GtkCssProvider *css = gtk_css_provider_new ();
      GError *error = NULL;
      
      gtk_css_provider_load_from_path (css, css_file_name, &error);
      if (error)
        {
          g_message ("CSS parsing failed: %s", error->message);
          g_error_free (error);
        }
      else
        gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
                                                   GTK_STYLE_PROVIDER (css),
                                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    }

  if (file_name != NULL)
    {
      GtkWidget *toplevel = preview_file (toplevel_name, file_name);

      gtk_widget_show_all (toplevel);
      
      if (shot_file_name)
        {
          gchar *tmp_file = g_strconcat (shot_file_name, ".png", NULL);
          GdkWindow *window = gtk_widget_get_window (toplevel);
          GdkPixbuf *pix;

          wait_for_drawing (window);
          pix = gdk_pixbuf_get_from_window (window, 0, 0, 
                                            gtk_widget_get_allocated_width (toplevel),
                                            gtk_widget_get_allocated_height (toplevel));
          gdk_pixbuf_save (pix, tmp_file, "png", NULL, NULL);

          g_object_unref (pix);
          g_free (tmp_file);
          return 0;
        }
    }
  else
    {
      start_listener (toplevel_name);
    }

  gtk_main ();

  return 0;
}
