/*
 * Copyright (c) 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 <Python.h>
#define NO_IMPORT_PYGOBJECT
#include <pygobject.h>
#include <glib/gi18n.h>
#include "streamtuner.h"
#include "pst-helpers.h"
#include "pst-category.h"
#include "pst-stream.h"
#include "pst-transfer-session.h"

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

static gboolean pst_categories_sequence_as_gnode_real (PyObject *categories, GNode *parent);
static gboolean pst_categories_sequence_as_gnode_free_cb (GNode *node, gpointer data);
static gboolean pst_streams_mapping_as_ghashtable_insert (GHashTable *hash, PyObject *pair);

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

int
pst_strings_as_gslist (PyObject *strings, GSList **gslist)
{
  int i;
  int len;

  g_return_val_if_fail(strings != NULL, FALSE);
  g_return_val_if_fail(gslist != NULL, FALSE);

  len = PySequence_Length(strings);
  if (len == -1)
    return 0;

  *gslist = NULL;

  for (i = 0; i < len; i++)
    {
      PyObject *item;
      const char *str;
      
      item = PySequence_ITEM(strings, i);
      if (! item)
	goto exception;

      str = PyString_AsString(item);
      Py_DECREF(item);

      if (! str)		/* exception raised by PyString_AsString() */
	goto exception;

      *gslist = g_slist_append(*gslist, g_strdup(str));
    }
  
  return 1;

 exception:
  g_slist_foreach(*gslist, (GFunc) g_free, NULL);
  g_slist_free(*gslist);

  return 0;
}

PyObject *
pst_strings_from_gslist (GSList *gslist)
{
  PyObject *tuple;
  GSList *l;
  int i = 0;

  tuple = PyTuple_New(g_slist_length(gslist));
  if (! tuple)
    return NULL;

  for (l = gslist; l; l = l->next)
    {
      PyObject *str;

      str = PyString_FromString(l->data);
      if (! str)
	{
	  Py_DECREF(tuple);
	  return NULL;
	}

      PyTuple_SET_ITEM(tuple, i++, str);
    }

  return tuple;
}

PyObject *
pst_none (void)
{
  Py_INCREF(Py_None);
  return Py_None;
}

PyObject *
pst_string_from_string_or_null (const char *str)
{
  return str ? PyString_FromString(str) : pst_none();
}

PyObject *
pst_string_take_string (char *str)
{
  PyObject *pstr;

  g_return_val_if_fail(str != NULL, NULL);

  pstr = PyString_FromString(str);
  g_free(str);

  return pstr;
}

PyObject *
pst_string_take_string_or_null (char *str)
{
  return str ? pst_string_take_string(str) : pst_none();
}

int
pst_string_dup_string_or_null (PyObject *value, char **str)
{
  const char *_str = NULL;

  g_return_val_if_fail(str != NULL, -1);

  if (value)
    {
      _str = PyString_AsString(value);
      if (! _str)
	return -1;
    }

  g_free(*str);
  *str = g_strdup(_str);

  return 0;
}

gboolean
pst_categories_sequence_as_gnode (PyObject *categories, GNode **node)
{
  GNode *_node;

  g_return_val_if_fail(categories != NULL, FALSE);
  g_return_val_if_fail(node != NULL, FALSE);

  _node = g_node_new(NULL);
  if (pst_categories_sequence_as_gnode_real(categories, _node))
    {
      *node = _node;
      return TRUE;
    }
  else
    {
      g_node_traverse(_node,
		      G_PRE_ORDER,
		      G_TRAVERSE_ALL,
		      -1,
		      pst_categories_sequence_as_gnode_free_cb,
		      NULL);
      g_node_destroy(_node);
      return FALSE;
    }
}

static gboolean
pst_categories_sequence_as_gnode_real (PyObject *categories, GNode *parent)
{
  GNode *prev = NULL;
  int len;
  int i;

  g_return_val_if_fail(categories != NULL, FALSE);
  g_return_val_if_fail(parent != NULL, FALSE);

  len = PySequence_Length(categories);
  if (len == -1)
    return FALSE;

  for (i = 0; i < len; i++)
    {
      PyObject *item;
      gboolean status = FALSE;

      item = PySequence_ITEM(categories, i);
      if (! item)
	return FALSE;
      
      if (PySequence_Check(item))
	{
	  if (pst_categories_sequence_as_gnode_real(item, prev ? prev : parent))
	    status = TRUE;
	}
      else if (PyObject_TypeCheck(item, &PSTCategory_Type))
	{
	  PythonCategory *category;

	  category = pst_category_copy(((PSTCategory *) item)->category);
	  if (category)
	    {
	      prev = g_node_append_data(parent, category);
	      status = TRUE;
	    }
	}
      else
	PyErr_Format(PyExc_TypeError, _("a list element is not a sequence or %s object"), PSTCategory_Type.tp_name);

      Py_DECREF(item);
      if (! status)
	return FALSE;
    }

  return TRUE;
}

static gboolean
pst_categories_sequence_as_gnode_free_cb (GNode *node, gpointer data)
{
  if (node->data)
    pst_category_free_cb(node->data, NULL);

  return FALSE;			/* continue */
}

gboolean
pst_streams_sequence_as_glist (PyObject *streams, GList **list)
{
  int i;
  int len;

  g_return_val_if_fail(streams != NULL, FALSE);
  g_return_val_if_fail(list != NULL, FALSE);

  len = PySequence_Length(streams);
  if (len == -1)
    return FALSE;

  *list = NULL;
  for (i = 0; i < len; i++)
    {
      PyObject *item;
      gboolean status;

      item = PySequence_ITEM(streams, i);
      if (! item)
	return FALSE;

      if (PyObject_TypeCheck(item, &PSTStream_Type))
	{
	  *list = g_list_append(*list, pst_stream_copy(((PSTStream *) item)->stream));
	  status = TRUE;
	}
      else
	{
	  PyErr_Format(PyExc_TypeError, _("element %i of the streams sequence is not a %s object"), i, PSTStream_Type.tp_name);
	  status = FALSE;
	}
      
      Py_DECREF(item);

      if (! status)
	return FALSE;
    }

  return TRUE;
}

gboolean
pst_streams_mapping_as_ghashtable (PyObject *streams, GHashTable **hash)
{
  PyObject *items;
  int len;
  int i;
  gboolean status = FALSE;

  g_return_val_if_fail(streams != NULL, FALSE);
  g_return_val_if_fail(hash != NULL, FALSE);

  items = PyMapping_Items(streams);
  if (! items)
    return FALSE;

  len = PySequence_Length(items);
  if (len == -1)
    goto end;

  *hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);

  for (i = 0; i < len; i++)
    {
      PyObject *pair;
      gboolean this_status;

      pair = PySequence_ITEM(items, i);
      if (! pair)
	goto end;
      
      this_status = pst_streams_mapping_as_ghashtable_insert(*hash, pair);
      Py_DECREF(pair);

      if (! this_status)
	goto end;
    }

  status = TRUE;		/* success */
  
 end:
  Py_DECREF(items);

  return status;
}

static gboolean
pst_streams_mapping_as_ghashtable_insert (GHashTable *hash, PyObject *pair)
{
  PyObject *key;
  PyObject *value;
  const char *ckey;
  GList *list;
  gboolean status = FALSE;

  g_return_val_if_fail(hash != NULL, FALSE);
  g_return_val_if_fail(pair != NULL, FALSE);

  key = PySequence_GetItem(pair, 0);
  if (! key)
    return FALSE;

  value = PySequence_GetItem(pair, 1);
  if (! value)
    goto end;

  ckey = PyString_AsString(key);
  if (! ckey)
    goto end;

  if (! pst_streams_sequence_as_glist(value, &list))
    goto end;

  g_hash_table_insert(hash, g_strdup(ckey), list);
  status = TRUE;

 end:
  Py_DECREF(key);
  Py_DECREF(value);

  return status;
}

void
pst_set_error (GError **err)
{
  if (PyErr_ExceptionMatches(PSTExc_AbortError))
    PyErr_Clear();
  else
    {
      PyObject *ptype;
      PyObject *pvalue;
      PyObject *ptraceback;
      const char *value;

      PyErr_Fetch(&ptype, &pvalue, &ptraceback);

      value = PyString_AsString(pvalue);
      if (! value)
	{
	  PyErr_Print();
	  value = _("a Python exception has occurred");
	}
      
      g_set_error(err, 0, 0, "%s", value);

      PyErr_Restore(ptype, pvalue, ptraceback);
      PyErr_Print();
    }
}

/* http://bugzilla.gnome.org/show_bug.cgi?id=160452 */
int
pst_value_from_pyobject (GValue *value, PyObject *obj)
{
  if (PySequence_Check(obj) && G_VALUE_HOLDS(value, G_TYPE_VALUE_ARRAY))
    {
      GValueArray *value_array;
      int len;
      int i;

      len = PySequence_Length(obj);
      if (len == -1)
	{
	  PyErr_Clear();
	  return -1;
	}

      value_array = g_value_array_new(len);

      for (i = 0; i < len; i++)
	{
	  PyObject *item;
	  GType type;
	  GValue item_value = { 0, };

	  item = PySequence_GetItem(obj, i);
	  if (! item)
	    {
	      PyErr_Clear();
	      g_value_array_free(value_array);
	      return -1;
	    }

	  type = pyg_type_from_object((PyObject *) item->ob_type);
	  if (! type)
	    {
	      PyErr_Clear();
	      g_value_array_free(value_array);
	      Py_DECREF(item);
	      return -1;
	    }

	  g_value_init(&item_value, type);
	  if (pst_value_from_pyobject(&item_value, item) == -1)
	    {
	      g_value_array_free(value_array);
	      Py_DECREF(item);
	      g_value_unset(&item_value);
	      return -1;
	    }
	  Py_DECREF(item);

	  g_value_array_append(value_array, &item_value);
	  g_value_unset(&item_value);
	}

      g_value_take_boxed(value, value_array);
      return 0;
    }
  else
    {
      int status;

      status = pyg_value_from_pyobject(value, obj);
      if (PyErr_Occurred()) /* http://bugzilla.gnome.org/show_bug.cgi?id=160595 */
	PyErr_Clear();

      return status;
    }
}

PyTypeObject *
pst_pygobject_lookup_class (GType type)
{
  PyTypeObject *type_object;

  type_object = pygobject_lookup_class(type);
  if (! type_object)
    PyErr_Format(PyExc_SystemError, _("unable to lookup PyGTK object type of %s"), g_type_name(type));

  return type_object;
}

int
pst_convert_widget (PyObject *object, GtkWidget **widget)
{
  *widget = (GtkWidget *) pst_pygobject_get(object, GTK_TYPE_WIDGET);
  return *widget ? 1 : 0;
}

GObject *
pst_pygobject_get (PyObject *object, GType type)
{
  PyTypeObject *ptype;

  ptype = pst_pygobject_lookup_class(type);
  if (! ptype)
    return NULL;

  if (! PyObject_TypeCheck(object, ptype))
    {
      PyErr_Format(PyExc_TypeError, _("not a %s object"), ptype->tp_name);
      return NULL;
    }

  return pygobject_get(object);
}
