/* Gnome-Streamer
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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.
 */

#include <gst/gstthread.h>

GstElementDetails gst_thread_details = {
  "Threaded container",
  "Bin",
  "Container that creates/manages a thread",
  VERSION,
  "Erik Walthinsen <omega@cse.ogi.edu>",
  "(C) 1999",
};


/* Thread signals and args */
enum {
  /* FILL ME */
  LAST_SIGNAL
};

enum {
  ARG_0,
  ARG_CREATE_THREAD,
};


static void gst_thread_class_init(GstThreadClass *klass);
static void gst_thread_init(GstThread *thread);

static void gst_thread_set_arg(GtkObject *object,GtkArg *arg,guint id);
static void gst_thread_get_arg(GtkObject *object,GtkArg *arg,guint id);
static gboolean gst_thread_change_state(GstElement *element,
                                        GstElementState state);

static xmlNodePtr gst_thread_save_thyself(GstElement *element,xmlNodePtr parent);

static void gst_thread_prepare(GstThread *thread);
static void gst_thread_signal_thread(GstThread *thread);


static GstBin *parent_class = NULL;
//static guint gst_thread_signals[LAST_SIGNAL] = { 0 };

GtkType
gst_thread_get_type(void) {
  static GtkType thread_type = 0;

  if (!thread_type) {
    static const GtkTypeInfo thread_info = {
      "GstThread",
      sizeof(GstThread),
      sizeof(GstThreadClass),
      (GtkClassInitFunc)gst_thread_class_init,
      (GtkObjectInitFunc)gst_thread_init,
      (GtkArgSetFunc)NULL,
      (GtkArgGetFunc)NULL,
      (GtkClassInitFunc)NULL,
    };
    thread_type = gtk_type_unique(gst_bin_get_type(),&thread_info);
  }
  return thread_type;
}

static void
gst_thread_class_init(GstThreadClass *klass) {
  GtkObjectClass *gtkobject_class;
  GstObjectClass *gstobject_class;
  GstElementClass *gstelement_class;

  gtkobject_class = (GtkObjectClass*)klass;
  gstobject_class = (GstObjectClass*)klass;
  gstelement_class = (GstElementClass*)klass;

  parent_class = gtk_type_class(gst_bin_get_type());

  gtk_object_add_arg_type("GstThread::create_thread", GTK_TYPE_BOOL,
                          GTK_ARG_READWRITE, ARG_CREATE_THREAD);

  gstelement_class->change_state = gst_thread_change_state;
  gstelement_class->save_thyself = gst_thread_save_thyself;

  gtkobject_class->set_arg = gst_thread_set_arg;
  gtkobject_class->get_arg = gst_thread_get_arg;
}

static void gst_thread_init(GstThread *thread) {
  GST_FLAG_SET(thread,GST_THREAD_CREATE);

  thread->entries = NULL;
  thread->numentries = 0;

  thread->lock = g_mutex_new();
  thread->cond = g_cond_new();
}

static void gst_thread_set_arg(GtkObject *object,GtkArg *arg,guint id) {
  /* it's not null if we got it, but it might not be ours */
  g_return_if_fail(GST_IS_THREAD(object));

  switch(id) {
    case ARG_CREATE_THREAD:
      if (GTK_VALUE_BOOL(*arg)) {
        gst_info("turning ON the creation of the thread\n");
        GST_FLAG_SET(object,GST_THREAD_CREATE);
        gst_info("flags are 0x%08x\n",GST_FLAGS(object));
      } else {
        gst_info("turning OFF the creation of the thread\n");
        GST_FLAG_UNSET(object,GST_THREAD_CREATE);
        gst_info("flags are 0x%08x\n",GST_FLAGS(object));
      }
      break;
    default:
      break;
  }
}

static void gst_thread_get_arg(GtkObject *object,GtkArg *arg,guint id) {
  /* it's not null if we got it, but it might not be ours */
  g_return_if_fail(GST_IS_THREAD(object));

  switch(id) {
    case ARG_CREATE_THREAD:
      GTK_VALUE_BOOL(*arg) = GST_FLAG_IS_SET(object,GST_THREAD_CREATE);
      break;
    default:
      break;
  }
}


GstElement *gst_thread_new(guchar *name) {
  GstThread *thread;

  thread = gtk_type_new(gst_thread_get_type());
  gst_element_set_name(GST_ELEMENT(thread),name);
  return GST_ELEMENT(thread);
}


static void gst_thread_prepare(GstThread *thread) {
  GList *elements;
  GstElement *element;
  GList *pads;
  GstPad *pad, *peer;
  GstElement *outside;

  thread->numentries = 0;

  /* first we need to find all the entry points into the thread */
  elements = GST_BIN(thread)->children;
  while (elements) {
    element = GST_ELEMENT(elements->data);
    if (GST_IS_SRC(element)) {
      gst_info("element \"%s\" is a source entry point for the thread\n",
               gst_element_get_name(GST_ELEMENT(element)));
      thread->entries = g_list_prepend(thread->entries,element);
      thread->numentries++;
    } else {
      /* go through the list of pads to see if there's a Connection */
      pads = gst_element_get_pad_list(element);
      while (pads) {
        pad = GST_PAD(pads->data);
        /* we only worry about sink pads */
        if (gst_pad_get_direction(pad) == GST_PAD_SINK) {
          /* get the pad's peer */
          peer = gst_pad_get_peer(pad);
          if (!peer) break;
          /* get the parent of the peer of the pad */
          outside = GST_ELEMENT(gst_pad_get_parent(peer));
          if (!outside) break;
          /* if it's a connection and it's not ours... */
          if (GST_IS_CONNECTION(outside) &&
              (gst_object_get_parent(GST_OBJECT(outside)) != GST_OBJECT(thread))) {
            gst_info("element \"%s\" is the external source Connection \
for internal element \"%s\"\n",
                    gst_element_get_name(GST_ELEMENT(outside)),
                    gst_element_get_name(GST_ELEMENT(element)));
            thread->entries = g_list_prepend(thread->entries,outside);
            thread->numentries++;
          }
        }
        pads = g_list_next(pads);
      }
    }
    elements = g_list_next(elements);
  }
  gst_info("have %d entries into thread\n",thread->numentries);
}


static gboolean gst_thread_change_state(GstElement *element,
                                        GstElementState state) {
  GstThread *thread;
  gboolean stateset = TRUE;

  g_return_val_if_fail(GST_IS_THREAD(element), FALSE);
  thread = GST_THREAD(element);

  if (GST_ELEMENT_CLASS(parent_class)->change_state)
    stateset = GST_ELEMENT_CLASS(parent_class)->change_state(element,state);

  switch (state) {
    case GST_STATE_RUNNING:
      if (!stateset) return FALSE;
      /* we want to prepare our internal state for doing the iterations */
      gst_info("preparing thread \"%s\" for iterations:\n",
               gst_element_get_name(GST_ELEMENT(element)));
      gst_thread_prepare(thread);
      if (thread->numentries == 0)
        return FALSE;
      /* set the state to idle */
      GST_FLAG_UNSET(thread,GST_THREAD_STATE_SPINNING);
      /* create the thread if that's what we're supposed to do */
      gst_info("flags are 0x%08x\n",GST_FLAGS(thread));
      if (GST_FLAG_IS_SET(thread,GST_THREAD_CREATE)) {
        gst_info("starting thread \"%s\"\n",
                 gst_element_get_name(GST_ELEMENT(element)));
        pthread_create(&thread->thread_id,NULL,
                       gst_thread_main_loop,thread);
      } else {
        gst_info("NOT starting thread \"%s\"\n",
                gst_element_get_name(GST_ELEMENT(element)));
      }
      return TRUE;
      break;
    case ~GST_STATE_RUNNING:
      /* stop, reap, and join the thread */
      GST_FLAG_UNSET(thread,GST_THREAD_STATE_SPINNING);
      GST_FLAG_SET(thread,GST_THREAD_STATE_REAPING);
      gst_thread_signal_thread(thread);
      pthread_join(thread->thread_id,0);
      /* tear down the internal state */
      gst_info("tearing down thread's iteration state\n");
      /* FIXME do stuff */
      break;
    case GST_STATE_PLAYING:
      if (!stateset) return FALSE;
      gst_info("starting thread \"%s\"\n",
              gst_element_get_name(GST_ELEMENT(element)));
      GST_FLAG_SET(thread,GST_THREAD_STATE_SPINNING);
      gst_thread_signal_thread(thread);
      return TRUE;
      break;  
    case ~GST_STATE_PLAYING:
      gst_info("stopping thread \"%s\"\n",
              gst_element_get_name(GST_ELEMENT(element)));
      GST_FLAG_UNSET(thread,GST_THREAD_STATE_SPINNING);
      gst_thread_signal_thread(thread);
      break;
    default:
      break;
  }

  return stateset;
}


void *gst_thread_main_loop(void *arg) {
  GstThread *thread = GST_THREAD(arg);

  gst_info("HI, IN MAIN THREAD LOOP!\n");

  while(!GST_FLAG_IS_SET(thread,GST_THREAD_STATE_REAPING)) {
    if (GST_FLAG_IS_SET(thread,GST_THREAD_STATE_SPINNING))
      gst_thread_iterate(thread);
    else {
      g_mutex_lock(thread->lock);
      g_cond_wait(thread->cond,thread->lock);
      g_mutex_unlock(thread->lock);
    }
  }

  GST_FLAG_UNSET(thread,GST_THREAD_STATE_REAPING);

  gst_info("GOODBYE, LEAVING MAIN THREAD LOOP!\n");
  return NULL;
}

void gst_thread_iterate(GstThread *thread) {
  GList *entries;
  GstElement *entry;

  g_return_if_fail(thread != NULL);
  g_return_if_fail(GST_IS_THREAD(thread));
//  g_return_if_fail(GST_FLAG_IS_SET(thread,GST_STATE_RUNNING));
  g_return_if_fail(thread->numentries > 0);

  entries = thread->entries;

  while (entries) {
    entry = GST_ELEMENT(entries->data);
    if (GST_IS_SRC(entry))
      gst_src_push(GST_SRC(entry));
    else if (GST_IS_CONNECTION(entry))
      gst_connection_push(GST_CONNECTION(entry));
    else
      g_assert_not_reached();
    entries = g_list_next(entries);
  }
  //g_print(",");
}

static void gst_thread_signal_thread(GstThread *thread) {
  g_mutex_lock(thread->lock);
  g_cond_signal(thread->cond);
  g_mutex_unlock(thread->lock);
}

static xmlNodePtr gst_thread_save_thyself(GstElement *element,xmlNodePtr parent) {
  xmlNewChild(parent,NULL,"type","thread");

  if (GST_ELEMENT_CLASS(parent_class)->save_thyself)
    GST_ELEMENT_CLASS(parent_class)->save_thyself(element,parent);
	return NULL;
}