/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
 * Authored By Matthew Allum  <mallum@openedhand.com>
 *
 * Copyright (C) 2008 OpenedHand
 *
 * This library 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 library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "clutter-frame-source.h"

typedef struct _ClutterFrameSource ClutterFrameSource;

struct _ClutterFrameSource
{
  GSource source;

  GTimeVal start_time;
  guint last_time, frame_time;
};

static gboolean clutter_frame_source_prepare  (GSource *source, gint *timeout);
static gboolean clutter_frame_source_check    (GSource *source);
static gboolean clutter_frame_source_dispatch (GSource    *source,
					       GSourceFunc callback,
					       gpointer    user_data);

static GSourceFuncs clutter_frame_source_funcs = 
  {
    clutter_frame_source_prepare,
    clutter_frame_source_check,
    clutter_frame_source_dispatch,
    NULL
  };

guint
clutter_frame_source_add_full (gint           priority,
			       guint          interval,
			       GSourceFunc    function,
			       gpointer       data,
			       GDestroyNotify notify)
{
  guint ret;
  GSource *source = g_source_new (&clutter_frame_source_funcs,
				  sizeof (ClutterFrameSource));
  ClutterFrameSource *frame_source = (ClutterFrameSource *) source;

  frame_source->last_time = 0;
  frame_source->frame_time = interval;
  g_get_current_time (&frame_source->start_time);

  if (priority != G_PRIORITY_DEFAULT)
    g_source_set_priority (source, priority);

  g_source_set_callback (source, function, data, notify);

  ret = g_source_attach (source, NULL);

  g_source_unref (source);

  return ret;
}

guint
clutter_frame_source_add (guint          interval,
			  GSourceFunc    function,
			  gpointer       data)
{
  return clutter_frame_source_add_full (G_PRIORITY_DEFAULT,
					interval, function, data, NULL);
}

static guint
clutter_frame_source_get_ticks (ClutterFrameSource *frame_source)
{
  GTimeVal time_now;

  g_source_get_current_time ((GSource *) frame_source, &time_now);
  
  return (time_now.tv_sec - frame_source->start_time.tv_sec) * 1000
         + (time_now.tv_usec - frame_source->start_time.tv_usec) / 1000;
}

static gboolean
clutter_frame_source_prepare (GSource *source, gint *timeout)
{
  ClutterFrameSource *frame_source = (ClutterFrameSource *) source;

  guint now = clutter_frame_source_get_ticks (frame_source);

  /* If time has gone backwards or the time since the last frame is
     greater than the two frames worth then reset the time and do a
     frame now */
  if (frame_source->last_time > now ||
      (now - frame_source->last_time) > frame_source->frame_time * 2)
    {
      frame_source->last_time = now - frame_source->frame_time;
      if (timeout)
	*timeout = 0;
      return TRUE;
    }
  else if (now - frame_source->last_time >= frame_source->frame_time)
    {
      if (timeout)
	*timeout = 0;
      return TRUE;
    }
  else
    {
      if (timeout)
	*timeout = frame_source->frame_time + frame_source->last_time - now;
      return FALSE;
    }
}

static gboolean
clutter_frame_source_check (GSource *source)
{
  return clutter_frame_source_prepare (source, NULL);
}

static gboolean
clutter_frame_source_dispatch (GSource     *source,
			       GSourceFunc callback,
			       gpointer    user_data)
{
  ClutterFrameSource *frame_source = (ClutterFrameSource *) source;

  if ((* callback) (user_data))
    {
      frame_source->last_time += frame_source->frame_time;
      return TRUE;
    }
  else
    return FALSE;
}