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

#ifdef HAVE_GETRUSAGE
#include "gst-cpu-throttling-clock.h"

#include <unistd.h>
#include <sys/resource.h>

#include "gst-cpu-throttling-clock.h"

/**
 * SECTION: gst-cpu-throttling-clock
 * @title: GstCpuThrottlingClock
 * @short_description: TODO
 *
 * TODO
 */

/* *INDENT-OFF* */
GST_DEBUG_CATEGORY_STATIC (gst_cpu_throttling_clock_debug);
#define GST_CAT_DEFAULT gst_cpu_throttling_clock_debug

struct _GstCpuThrottlingClockPrivate
{
  guint wanted_cpu_usage;

  GstClock *sclock;
  GstClockTime current_wait_time;
  GstPoll *timer;
  struct rusage last_usage;

  GstClockID evaluate_wait_time;
  GstClockTime time_between_evals;
};

#define parent_class gst_cpu_throttling_clock_parent_class
G_DEFINE_TYPE_WITH_CODE (GstCpuThrottlingClock, gst_cpu_throttling_clock, GST_TYPE_CLOCK, G_ADD_PRIVATE(GstCpuThrottlingClock))

enum
{
  PROP_FIRST,
  PROP_CPU_USAGE,
  PROP_LAST
};

static GParamSpec *param_specs[PROP_LAST] = { NULL, };
/* *INDENT-ON* */

static void
gst_cpu_throttling_clock_get_property (GObject * object,
    guint property_id, GValue * value, GParamSpec * pspec)
{
  GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object);

  switch (property_id) {
    case PROP_CPU_USAGE:
      g_value_set_uint (value, self->priv->wanted_cpu_usage);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void
gst_cpu_throttling_clock_set_property (GObject * object,
    guint property_id, const GValue * value, GParamSpec * pspec)
{
  GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object);

  switch (property_id) {
    case PROP_CPU_USAGE:
      self->priv->wanted_cpu_usage = g_value_get_uint (value);
      if (self->priv->wanted_cpu_usage == 0)
        self->priv->wanted_cpu_usage = 100;
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

typedef struct
{
  GWeakRef cpu_throttling_clock;
} WaitData;

static void
_delete_wait_data (WaitData * wait_data)
{
  g_weak_ref_clear (&wait_data->cpu_throttling_clock);
  g_free (wait_data);
}

static gboolean
gst_transcoder_adjust_wait_time (GstClock * sync_clock, GstClockTime time,
    GstClockID id, WaitData * wait_data)
{
  struct rusage ru;
  float delta_usage, usage, coef;
  GstCpuThrottlingClockPrivate *priv;

  GstCpuThrottlingClock *self =
      g_weak_ref_get (&wait_data->cpu_throttling_clock);
  if (!self)
    return FALSE;

  priv = self->priv;

  getrusage (RUSAGE_SELF, &ru);
  delta_usage = GST_TIMEVAL_TO_TIME (ru.ru_utime) -
      GST_TIMEVAL_TO_TIME (self->priv->last_usage.ru_utime);
  usage =
      ((float) delta_usage / self->priv->time_between_evals * 100) /
      g_get_num_processors ();

  self->priv->last_usage = ru;

  coef = GST_MSECOND / 10;
  if (usage < (gfloat) priv->wanted_cpu_usage) {
    coef = -coef;
  }

  priv->current_wait_time = CLAMP (0,
      (GstClockTime) priv->current_wait_time + coef, GST_SECOND);

  GST_DEBUG_OBJECT (self,
      "Avg is %f (wanted %d) => %" GST_TIME_FORMAT, usage,
      self->priv->wanted_cpu_usage, GST_TIME_ARGS (priv->current_wait_time));

  g_object_unref (self);
  return TRUE;
}

static GstClockReturn
_wait (GstClock * clock, GstClockEntry * entry, GstClockTimeDiff * jitter)
{
  GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (clock);

  if (!self->priv->evaluate_wait_time) {
    if (!(self->priv->sclock)) {
      GST_ERROR_OBJECT (clock, "Could not find any system clock"
          " to start the wait time evaluation task");
    } else {
      WaitData *wait_data = g_new (WaitData, 1);
      g_weak_ref_init (&wait_data->cpu_throttling_clock, self);

      self->priv->evaluate_wait_time =
          gst_clock_new_periodic_id (self->priv->sclock,
          gst_clock_get_time (self->priv->sclock),
          self->priv->time_between_evals);

      gst_clock_id_wait_async (self->priv->evaluate_wait_time,
          (GstClockCallback) gst_transcoder_adjust_wait_time,
          (gpointer) wait_data, (GDestroyNotify) _delete_wait_data);
    }
  }

  if (G_UNLIKELY (GST_CLOCK_ENTRY_STATUS (entry) == GST_CLOCK_UNSCHEDULED))
    return GST_CLOCK_UNSCHEDULED;

  if (gst_poll_wait (self->priv->timer, self->priv->current_wait_time)) {
    GST_INFO_OBJECT (self, "Something happened on the poll");
  }

  return GST_CLOCK_ENTRY_STATUS (entry);
}

static GstClockTime
_get_internal_time (GstClock * clock)
{
  GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (clock);

  return gst_clock_get_internal_time (self->priv->sclock);
}

static void
gst_cpu_throttling_clock_dispose (GObject * object)
{
  GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object);

  if (self->priv->evaluate_wait_time) {
    gst_clock_id_unschedule (self->priv->evaluate_wait_time);
    gst_clock_id_unref (self->priv->evaluate_wait_time);
    self->priv->evaluate_wait_time = 0;
  }
  if (self->priv->timer) {
    gst_poll_free (self->priv->timer);
    self->priv->timer = NULL;
  }
  g_clear_object (&self->priv->sclock);

  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
gst_cpu_throttling_clock_class_init (GstCpuThrottlingClockClass * klass)
{
  GObjectClass *oclass = G_OBJECT_CLASS (klass);
  GstClockClass *clock_klass = GST_CLOCK_CLASS (klass);

  GST_DEBUG_CATEGORY_INIT (gst_cpu_throttling_clock_debug, "cpuclock", 0,
      "UriTranscodebin element");

  oclass->get_property = gst_cpu_throttling_clock_get_property;
  oclass->set_property = gst_cpu_throttling_clock_set_property;
  oclass->dispose = gst_cpu_throttling_clock_dispose;

  /**
   * GstCpuThrottlingClock:cpu-usage:
   *
   * Since: UNRELEASED
   */
  param_specs[PROP_CPU_USAGE] = g_param_spec_uint ("cpu-usage", "cpu-usage",
      "The percentage of CPU to try to use with the processus running the "
      "pipeline driven by the clock", 0, 100,
      100, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (oclass, PROP_LAST, param_specs);

  clock_klass->wait = GST_DEBUG_FUNCPTR (_wait);
  clock_klass->get_internal_time = _get_internal_time;
}

static void
gst_cpu_throttling_clock_init (GstCpuThrottlingClock * self)
{
  self->priv = gst_cpu_throttling_clock_get_instance_private (self);

  self->priv->current_wait_time = GST_MSECOND;
  self->priv->wanted_cpu_usage = 100;
  self->priv->timer = gst_poll_new_timer ();
  self->priv->time_between_evals = GST_SECOND / 4;
  self->priv->sclock = GST_CLOCK (gst_system_clock_obtain ());


  getrusage (RUSAGE_SELF, &self->priv->last_usage);
}

GstCpuThrottlingClock *
gst_cpu_throttling_clock_new (guint cpu_usage)
{
  return g_object_new (GST_TYPE_CPU_THROTTLING_CLOCK, "cpu-usage",
      cpu_usage, NULL);
}
#endif
