/*
 * Copyright (c) 2002, 2003, 2004 Jean-Yves Lefort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Jean-Yves Lefort nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include <stdlib.h>
#include <stdarg.h>
#include <glib.h>
#include <glib/gi18n.h>
#include "st-dialog-api.h"
#include "st-thread.h"
#include "st-handler.h"

/*** type definitions ********************************************************/

struct _STThreadPrivate
{
  gboolean	aborted;
  GMutex	*aborted_mutex;
  gboolean	cleaned_up;

  GTimer	*print_timer;
  GTimer	*set_progress_timer;
};

/*** function declarations ***************************************************/

static gpointer	st_thread_cb (gpointer data);
static gboolean st_thread_time (STThread *thread, GTimer **timer, gboolean force);

/*** implementation **********************************************************/

void
st_thread_init (void)
{
  g_thread_init(NULL);

  if (! g_thread_supported())
    {
      /*
       * We can't use st_error_dialog() because gtk_init() has not
       * been called yet.
       */
      st_notice(_("fatal error: the GLib thread system is unavailable"));
      exit(1);
    }

  gdk_threads_init();
}

STThread *
st_thread_new (STHandler *handler)
{
  STThread *thread;

  thread = g_new0(STThread, 1);
  thread->handler = handler;
  thread->priv = g_new0(STThreadPrivate, 1);
  thread->priv->aborted_mutex = g_mutex_new();
  
  return thread;
}

void
st_thread_run (STThread *thread)
{
  GError *err = NULL;

  g_return_if_fail(thread != NULL);
  g_return_if_fail(thread->thread != NULL);

  /*
   * libcurl uses a 64k stack buffer for decompression operations.
   *
   * On some platforms, the default thread stack size might not be
   * large enough, so we use a 96k stack (for instance, the default
   * thread stack size on FreeBSD is 64k).
   */
  if (! g_thread_create_full(st_thread_cb,
			     thread,
			     0x18000, /* 96k, big enough for libcurl */
			     FALSE,
			     FALSE,
			     G_THREAD_PRIORITY_NORMAL,
			     &err))
    {
      char *secondary;
      char *normalized;

      secondary = g_strdup_printf(_("Unable to create a thread: %s"),
				  err->message);
      normalized = st_dialog_normalize(secondary);

      st_error_dialog(_("A fatal error has occurred"), "%s", normalized);

      g_free(secondary);
      g_free(normalized);
      g_error_free(err);

      exit(1);
    }
}

static gpointer
st_thread_cb (gpointer data)
{
  STThread *thread = data;
  gpointer thread_data = NULL;

  g_return_val_if_fail(thread != NULL, NULL);
  g_return_val_if_fail(thread->thread != NULL, NULL);

  if (st_handler_event_is_bound(thread->handler, ST_HANDLER_EVENT_THREAD_BEGIN))
    thread_data = st_handler_event_thread_begin(thread->handler);

  thread->thread(thread->data);

  if (! thread->priv->cleaned_up)
    {
      GDK_THREADS_ENTER();
      st_thread_cleanup(thread);
      gdk_flush();
      GDK_THREADS_LEAVE();
    }

  if (thread->data_destroy)
    thread->data_destroy(thread->data);

  if (st_handler_event_is_bound(thread->handler, ST_HANDLER_EVENT_THREAD_END))
    st_handler_event_thread_end(thread->handler, thread_data);

  /* free ourselves */

  g_mutex_free(thread->priv->aborted_mutex);
  if (thread->priv->print_timer)
    g_timer_destroy(thread->priv->print_timer);
  if (thread->priv->set_progress_timer)
    g_timer_destroy(thread->priv->set_progress_timer);
  g_free(thread->priv);
  g_free(thread);

  return NULL;
}

static gboolean
st_thread_time (STThread *thread, GTimer **timer, gboolean force)
{
  gboolean go_on = force;

  g_return_val_if_fail(thread != NULL, FALSE);
  g_return_val_if_fail(timer != NULL, FALSE);

  if (! *timer)
    {
      *timer = g_timer_new();
      go_on = TRUE;
    }

  if (! go_on)
    go_on = g_timer_elapsed(*timer, 0) > 0.1;

  if (go_on)
    g_timer_start(*timer);

  return go_on;
}

STThread *
st_thread_get (void)
{
  GThread *g_thread;

  g_thread = g_thread_self();
  return g_thread ? g_thread->data : NULL;
}

void
st_thread_printf (STThread *thread, gboolean force, const char *format, ...)
{
  g_return_if_fail(thread != NULL);
  g_return_if_fail(format != NULL);

  if (thread->print && st_thread_time(thread, &thread->priv->print_timer, force))
    {
      va_list args;
      char *str;

      va_start(args, format);
      str = g_strdup_vprintf(format, args);
      va_end(args);

      thread->print(str, thread->data);
      g_free(str);
    }
}

void
st_thread_set_progress (STThread *thread, gboolean force, double progress)
{
  g_return_if_fail(thread != NULL);

  if (thread->set_progress && st_thread_time(thread, &thread->priv->set_progress_timer, force))
    thread->set_progress(progress, thread->data);
}

void
st_thread_forget (STThread *thread)
{
  g_return_if_fail(thread != NULL);

  g_mutex_lock(thread->priv->aborted_mutex);
  thread->priv->aborted = TRUE;
  g_mutex_unlock(thread->priv->aborted_mutex);
}

void
st_thread_abort (STThread *thread)
{
  g_return_if_fail(thread != NULL);

  st_thread_forget(thread);
  st_thread_cleanup(thread);
}

gboolean
st_thread_is_aborted (STThread *thread)
{
  gboolean aborted;

  g_return_val_if_fail(thread != NULL, FALSE);
  
  g_mutex_lock(thread->priv->aborted_mutex);
  aborted = thread->priv->aborted;
  g_mutex_unlock(thread->priv->aborted_mutex);

  return aborted;
}

void
st_thread_cleanup (STThread *thread)
{
  g_return_if_fail(thread != NULL);

  if (! thread->priv->cleaned_up)
    {
      if (thread->cleanup)
	thread->cleanup(thread->data);

      thread->priv->cleaned_up = TRUE;
    }
}


void
st_thread_validate_callback_return (STThread *thread,
				    gboolean status,
				    GError *err)
{
  g_return_if_fail(thread != NULL);

  if (st_thread_is_aborted(thread))
    {
      if (err)
	st_error_dialog(_("A callback error has occurred"),
			_("The task was aborted but the callback reported that there was an error."));
    }
  else
    {
      if (! status && ! err)
	{
	  /* do what was supposed to have been done */
	  GDK_THREADS_ENTER();
	  st_thread_abort(thread);
	  gdk_flush();
	  GDK_THREADS_LEAVE();
	  
	  st_error_dialog(_("A callback error has occurred"),
			  _("The callback reported that the task was aborted but it was not."));
	}
    }
}
