/*
     This file is part of GNUnet.
     Copyright (C) 2012 GNUnet e.V.

     GNUnet 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 3, or (at your
     option) any later version.

     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     Boston, MA 02110-1301, USA.
*/

/**
 * @file src/lib/animations.c
 * @brief drawing animations in gnunet-gtk
 * @author Christian Grothoff
 * @author LRN
 */
#include "gnunet_gtk.h"

/**
 * How often do we update animations?  Should be small enough
 * to fit with the animations used, but not so tiny that we just
 * wake up the CPU for nothing all the time.
 */
#define TICKER_DELAY \
  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100)


/**
 * Handle for an animation.  Each animation file should only
 * be loaded once and can then be used in multiple tree views.
 */
struct GNUNET_GTK_AnimationContext
{
  /**
   * This is a doublye-linked list.
   */
  struct GNUNET_GTK_AnimationContext *next;

  /**
   * This is a doublye-linked list.
   */
  struct GNUNET_GTK_AnimationContext *prev;

  /**
   * Handle to the animation.
   */
  GdkPixbufAnimation *ani;

  /**
   * Iterator of the animation.
   */
  GdkPixbufAnimationIter *iter;

  /**
   * Pixbuf with the current image from the animation.
   */
  GdkPixbuf *pixbuf;
};


/**
 * Handle to a tree view (and column) that contains animations.
 * Whenever an animation is added to a tree view, this module
 * must be told about the tree view and column for the animations
 * to work.
 */
struct GNUNET_GTK_AnimationTreeViewHandle
{
  /**
   * This is a doublye-linked list.
   */
  struct GNUNET_GTK_AnimationTreeViewHandle *next;

  /**
   * This is a doublye-linked list.
   */
  struct GNUNET_GTK_AnimationTreeViewHandle *prev;

  /**
   * Tree view to watch.
   */
  GtkTreeView *tv;

  /**
   * Column that might contain animations.
   */
  GtkTreeViewColumn *image_col;
};


/**
 * Head of linked list of animations.
 */
static struct GNUNET_GTK_AnimationContext *animation_head;

/**
 * Tail of linked list of animations.
 */
static struct GNUNET_GTK_AnimationContext *animation_tail;

/**
 * Head of linked list of tree views with animations.
 */
static struct GNUNET_GTK_AnimationTreeViewHandle *atv_head;

/**
 * Tail of linked list of tree views with animations.
 */
static struct GNUNET_GTK_AnimationTreeViewHandle *atv_tail;

/**
 * Task run to update animations.
 */
static struct GNUNET_SCHEDULER_Task *ticker_task;


/**
 * Create an animation context.  Usually animation contexts are
 * created during the startup of the application and kept around
 * until shutdown.
 *
 * @param filename name of the file with the animation to show
 * @return context to use to get the animated pixbuf, NULL on error
 */
struct GNUNET_GTK_AnimationContext *
GNUNET_GTK_animation_context_create (const char *filename)
{
  GError *err = NULL;
  struct GNUNET_GTK_AnimationContext *ac;

  ac = GNUNET_new (struct GNUNET_GTK_AnimationContext);
  ac->ani = gdk_pixbuf_animation_new_from_file (filename, &err);
  if (NULL == ac->ani)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ ("Failed to load animation from file `%s'\n"),
                filename);
    GNUNET_free (ac);
    return NULL;
  }
  ac->iter = gdk_pixbuf_animation_get_iter (ac->ani, NULL);
  ac->pixbuf =
    gdk_pixbuf_copy (gdk_pixbuf_animation_iter_get_pixbuf (ac->iter));
  GNUNET_CONTAINER_DLL_insert (animation_head, animation_tail, ac);
  return ac;
}


/**
 * Destroy an animation context.  Should only be called after the
 * respective pixbufs are no longer needed.
 *
 * @param ac animation context to destroy.
 */
void
GNUNET_GTK_animation_context_destroy (struct GNUNET_GTK_AnimationContext *ac)
{
  if (NULL == ac)
    return;
  g_object_unref (ac->pixbuf);
  g_object_unref (ac->iter);
  g_object_unref (ac->ani);
  GNUNET_CONTAINER_DLL_remove (animation_head, animation_tail, ac);
  GNUNET_free (ac);
}


/**
 * Obtain the animated pixbuf from an animation context.  Note
 * that the pixbuf will only properly work within GtkTreeViews
 * where the column with the image has been registered using
 * GNUNET_GTK_animation_tree_view_register.
 *
 * @param ac animation context to query
 * @return pixbuf of the AC, NULL on error loading the pixbuf
 */
GdkPixbuf *
GNUNET_GTK_animation_context_get_pixbuf (struct GNUNET_GTK_AnimationContext *ac)
{
  if (NULL == ac)
    return NULL;
  return ac->pixbuf;
}


/**
 * Advance the given animation by a frame, if the time is ripe.
 *
 * @param ac animation to advance
 * @return 0 if nothing needed to be done
 */
static int
tick_animation (struct GNUNET_GTK_AnimationContext *ac)
{
  GdkPixbuf *pixbuf;
  gint width;
  gint height;

  if (! gdk_pixbuf_animation_iter_advance (ac->iter, NULL))
    return 0;
  pixbuf = gdk_pixbuf_animation_iter_get_pixbuf (ac->iter);
  width = gdk_pixbuf_get_width (pixbuf);
  height = gdk_pixbuf_get_height (pixbuf);
  gdk_pixbuf_copy_area (pixbuf, 0, 0, width, height, ac->pixbuf, 0, 0);
  return 1;
}


/**
 * Redraw the visible portion of the tree view column (the animations
 * there might have changed).
 *
 * @param atv handle to the tree view to redraw
 */
static void
redraw_tree_view (struct GNUNET_GTK_AnimationTreeViewHandle *atv)
{
  GdkWindow *gw;
  GdkRectangle r;
  GdkRectangle tr;
  gint x;
  gint width;

  gw = gtk_widget_get_window (GTK_WIDGET (atv->tv));
  if (! gtk_widget_get_realized (GTK_WIDGET (atv->tv)))
    return;
  /* Get column x and width in bin window coordinates */
  gtk_tree_view_get_cell_area (atv->tv, NULL, atv->image_col, &r);
  /* Convert x and width to window coordinates */
  x = r.x;
  width = r.width;
  gtk_tree_view_convert_bin_window_to_widget_coords (atv->tv, x, 0, &r.x, NULL);
  gtk_tree_view_convert_bin_window_to_widget_coords (atv->tv,
                                                     x + width,
                                                     0,
                                                     &r.width,
                                                     NULL);

  /* Get visible area of the treeview, in tree coordinates */
  gtk_tree_view_get_visible_rect (atv->tv, &tr);

  /* Convert y and height of the visible area to widget coordinates */
  gtk_tree_view_convert_tree_to_widget_coords (atv->tv, 0, tr.y, NULL, &r.y);
  gtk_tree_view_convert_tree_to_widget_coords (atv->tv,
                                               0,
                                               tr.y + tr.height,
                                               NULL,
                                               &r.height);

  /* r now encloses only column image_col, redraw it */
  gdk_window_invalidate_rect (gw, &r, TRUE);
}


/**
 * Task run periodically to advance all of our animations.
 *
 * @param cls closure, unused
 */
static void
ticker (void *cls)
{
  struct GNUNET_GTK_AnimationContext *pos;
  struct GNUNET_GTK_AnimationTreeViewHandle *atv;
  unsigned int counter;

  ticker_task = GNUNET_SCHEDULER_add_delayed (TICKER_DELAY, &ticker, NULL);
  counter = 0;
  for (pos = animation_head; NULL != pos; pos = pos->next)
    counter += tick_animation (pos);
  if (0 == counter)
    return; /* nothing to be done */
  for (atv = atv_head; NULL != atv; atv = atv->next)
    redraw_tree_view (atv);
}


/**
 * Register a tree view column with the animation subsystem.  This
 * column may contain animated pixbufs and thus needs to be periodically
 * redrawn.
 *
 * @param tv tree view to register
 * @param image_col column in the tree view with the animated pixbufs
 * @return handle to unregister the tree view later
 */
struct GNUNET_GTK_AnimationTreeViewHandle *
GNUNET_GTK_animation_tree_view_register (GtkTreeView *tv,
                                         GtkTreeViewColumn *image_col)
{
  struct GNUNET_GTK_AnimationTreeViewHandle *atv;

  atv = GNUNET_new (struct GNUNET_GTK_AnimationTreeViewHandle);
  atv->tv = tv;
  atv->image_col = image_col;
  GNUNET_CONTAINER_DLL_insert (atv_head, atv_tail, atv);
  if (NULL == ticker_task)
    ticker_task = GNUNET_SCHEDULER_add_delayed (TICKER_DELAY, &ticker, NULL);
  return atv;
}


/**
 * Unregister a tree view, it no longer needs to be updated.
 *
 * @param atv tree view to unregister
 */
void
GNUNET_GTK_animation_tree_view_unregister (
  struct GNUNET_GTK_AnimationTreeViewHandle *atv)
{
  GNUNET_CONTAINER_DLL_remove (atv_head, atv_tail, atv);
  GNUNET_free (atv);
  if (NULL != atv_head)
    return;
  GNUNET_SCHEDULER_cancel (ticker_task);
  ticker_task = NULL;
}


/* end of animations.c */
