/* vi:set et ai sw=2 sts=2 ts=2: */
/*-
 * Copyright (c) 2009 Jannis Pohlmann <jannis@xfce.org>
 *
 * This program 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 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 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.
 */

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

#include "tumbler-scheduler.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SCHED_H
#include <sched.h>
#endif
#ifdef HAVE_LINUX_SCHED_H
#include <linux/sched.h>
#endif
#ifdef HAVE_SYSCALL_H
#include <syscall.h>
#endif


#define IOPRIO_CLASS_SHIFT 13

#ifndef SCHED_IDLE
#define SCHED_IDLE 5
#endif



enum
{
  IOPRIO_CLASS_NONE,
  IOPRIO_CLASS_RT,
  IOPRIO_CLASS_BE,
  IOPRIO_CLASS_IDLE,
};

enum
{
  IOPRIO_WHO_PROCESS = 1,
  IOPRIO_WHO_PGRP,
  IOPRIO_WHO_USER,
};

/* signal identifiers */
enum
{
  SIGNAL_ERROR,
  SIGNAL_FINISHED,
  SIGNAL_READY,
  SIGNAL_STARTED,
  LAST_SIGNAL,
};

static guint tumbler_scheduler_signals[LAST_SIGNAL];



G_DEFINE_INTERFACE (TumblerScheduler, tumbler_scheduler, G_TYPE_OBJECT)



static void
tumbler_scheduler_default_init (TumblerSchedulerIface *klass)
{
  g_object_interface_install_property (klass,
                                       g_param_spec_string ("name",
                                                            "name",
                                                            "name",
                                                            NULL,
                                                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

  tumbler_scheduler_signals[SIGNAL_ERROR] =
    g_signal_new ("error",
                  TUMBLER_TYPE_SCHEDULER,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (TumblerSchedulerIface, error),
                  NULL,
                  NULL,
                  tumbler_marshal_VOID__UINT_BOXED_UINT_INT_STRING_STRING,
                  G_TYPE_NONE,
                  6,
                  G_TYPE_UINT,
                  G_TYPE_STRV,
                  G_TYPE_UINT,
                  G_TYPE_INT,
                  G_TYPE_STRING,
                  G_TYPE_STRING);

  tumbler_scheduler_signals[SIGNAL_FINISHED] =
    g_signal_new ("finished",
                  TUMBLER_TYPE_SCHEDULER,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (TumblerSchedulerIface, finished),
                  NULL,
                  NULL,
                  tumbler_marshal_VOID__UINT_STRING,
                  G_TYPE_NONE,
                  2,
                  G_TYPE_UINT,
                  G_TYPE_STRING);

  tumbler_scheduler_signals[SIGNAL_READY] =
    g_signal_new ("ready",
                  TUMBLER_TYPE_SCHEDULER,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (TumblerSchedulerIface, ready),
                  NULL,
                  NULL,
                  tumbler_marshal_VOID__UINT_BOXED_STRING,
                  G_TYPE_NONE,
                  3,
                  G_TYPE_UINT,
                  G_TYPE_STRV,
                  G_TYPE_STRING);

  tumbler_scheduler_signals[SIGNAL_STARTED] =
    g_signal_new ("started",
                  TUMBLER_TYPE_SCHEDULER,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (TumblerSchedulerIface, started),
                  NULL,
                  NULL,
                  tumbler_marshal_VOID__UINT_STRING,
                  G_TYPE_NONE,
                  2,
                  G_TYPE_UINT,
                  G_TYPE_STRING);
}



void
tumbler_scheduler_push (TumblerScheduler *scheduler,
                        TumblerSchedulerRequest *request)
{
  g_return_if_fail (TUMBLER_IS_SCHEDULER (scheduler));
  g_return_if_fail (request != NULL);
  g_return_if_fail (TUMBLER_SCHEDULER_GET_IFACE (scheduler)->push != NULL);

  TUMBLER_SCHEDULER_GET_IFACE (scheduler)->push (scheduler, request);
}



void
tumbler_scheduler_dequeue (TumblerScheduler *scheduler,
                           guint32 handle)
{
  g_return_if_fail (TUMBLER_IS_SCHEDULER (scheduler));
  g_return_if_fail (handle != 0);
  g_return_if_fail (TUMBLER_SCHEDULER_GET_IFACE (scheduler)->dequeue != NULL);

  TUMBLER_SCHEDULER_GET_IFACE (scheduler)->dequeue (scheduler, handle);
}



void
tumbler_scheduler_cancel_by_mount (TumblerScheduler *scheduler,
                                   GMount *mount)
{
  g_return_if_fail (TUMBLER_IS_SCHEDULER (scheduler));
  g_return_if_fail (G_IS_MOUNT (mount));
  g_return_if_fail (TUMBLER_SCHEDULER_GET_IFACE (scheduler)->cancel_by_mount != NULL);

  TUMBLER_SCHEDULER_GET_IFACE (scheduler)->cancel_by_mount (scheduler, mount);
}



void
tumbler_scheduler_take_request (TumblerScheduler *scheduler,
                                TumblerSchedulerRequest *request)
{
  g_return_if_fail (TUMBLER_IS_SCHEDULER (scheduler));
  g_return_if_fail (request != NULL);

  request->scheduler = g_object_ref (scheduler);
}


gchar *
tumbler_scheduler_get_name (TumblerScheduler *scheduler)
{
  gchar *name;

  g_return_val_if_fail (TUMBLER_IS_SCHEDULER (scheduler), NULL);
  g_object_get (scheduler, "name", &name, NULL);

  return name;
}

void
tumbler_scheduler_emit_uri_error (TumblerScheduler *scheduler,
                                  TumblerSchedulerRequest *request,
                                  const gchar *uri,
                                  GError *error)
{
  const gchar *uris[] = { uri, NULL };

  g_return_if_fail (TUMBLER_IS_SCHEDULER (scheduler));
  g_return_if_fail (request != NULL);
  g_return_if_fail (uri != NULL);
  g_return_if_fail (error != NULL);

  g_signal_emit (scheduler, tumbler_scheduler_signals[SIGNAL_ERROR], 0, request->handle,
                 uris, error->domain, error->code, error->message, request->origin);
}



TumblerSchedulerRequest *
tumbler_scheduler_request_new (TumblerFileInfo **infos,
                               GList **thumbnailers,
                               guint length,
                               const gchar *origin)
{
  TumblerSchedulerRequest *request = NULL;
  static guint32 handle = 0;
  guint n;

  g_return_val_if_fail (infos != NULL, NULL);
  g_return_val_if_fail (thumbnailers != NULL, NULL);

  request = g_new0 (TumblerSchedulerRequest, 1);
  if (origin)
    request->origin = g_strdup (origin);
  request->dequeued = FALSE;
  request->scheduler = NULL;
  if (handle == 0)
    handle++;
  request->handle = handle++;
  request->infos = tumbler_file_info_array_copy (infos, length);
  request->thumbnailers = tumbler_thumbnailer_array_copy (thumbnailers, length);
  request->length = length;

  request->cancellables = g_new0 (GCancellable *, request->length + 1);

  for (n = 0; n < request->length; ++n)
    request->cancellables[n] = g_cancellable_new ();

  request->cancellables[n] = NULL;

  return request;
}



void
tumbler_scheduler_request_free (gpointer data)
{
  TumblerSchedulerRequest *request = data;
  gint n;

  g_return_if_fail (request != NULL);

  tumbler_thumbnailer_array_free (request->thumbnailers, request->length);

  if (G_LIKELY (request->scheduler != NULL))
    g_object_unref (request->scheduler);

  tumbler_file_info_array_free (request->infos);

  for (n = 0; request->cancellables != NULL && request->cancellables[n] != NULL; ++n)
    g_object_unref (request->cancellables[n]);
  g_free (request->cancellables);

  g_free (request->origin);
  g_free (request);
}



gint
tumbler_scheduler_request_compare (gconstpointer a,
                                   gconstpointer b,
                                   gpointer user_data)
{
  const TumblerSchedulerRequest *request_a = a;
  const TumblerSchedulerRequest *request_b = b;

  return request_b->handle - request_a->handle;
}

static int
ioprio_set (int which, int who, int ioprio_val)
{
#if defined(__NR_ioprio_set) && defined(HAVE_UNISTD_H)
  return syscall (__NR_ioprio_set, which, who, ioprio_val);
#else
  return 0;
#endif
}


void
tumbler_scheduler_thread_use_lower_priority (void)
{
#if defined(HAVE_sched_getparam) && defined(HAVE_sched_setscheduler)
  struct sched_param sp;
#endif
  int ioprio;
  int ioclass;

  ioprio = 7; /* priority is ignored with idle class */
  ioclass = IOPRIO_CLASS_IDLE << IOPRIO_CLASS_SHIFT;

  ioprio_set (IOPRIO_WHO_PROCESS, 0, ioprio | ioclass);

#if defined(HAVE_sched_getparam) && defined(HAVE_sched_setscheduler)
  if (sched_getparam (0, &sp) == 0)
    sched_setscheduler (0, SCHED_IDLE, &sp);
#endif
}



void
tumbler_scheduler_thumberr_debuglog (TumblerThumbnailer *thumbnailer,
                                     TumblerFileInfo *failed_info,
                                     GQuark error_domain,
                                     gint error_code,
                                     const gchar *message,
                                     TumblerSchedulerRequest *request)
{
  gchar *text;

  g_return_if_fail (TUMBLER_IS_THUMBNAILER (thumbnailer));
  g_return_if_fail (TUMBLER_IS_FILE_INFO (failed_info));
  g_return_if_fail (request != NULL);
  g_return_if_fail (TUMBLER_IS_SCHEDULER (request->scheduler));

  if (error_domain == G_IO_ERROR && error_code == G_IO_ERROR_CANCELLED)
    return;

  if (error_domain == TUMBLER_ERROR)
    text = g_strdup (message);
  else
    {
      error_code = TUMBLER_ERROR_OTHER_ERROR_DOMAIN;
      text = g_strdup_printf ("(domain %s, code %d) %s",
                              g_quark_to_string (error_domain), error_code, message);
    }

  for (guint n = 0; n < request->length; n++)
    {
      if (request->infos[n] == failed_info)
        {
          g_debug ("Failed attempt for job %d, URI '%s': Code %d, message: %s",
                   request->handle, tumbler_file_info_get_uri (failed_info), error_code, text);
          break;
        }
    }

  g_free (text);
}
