/*
 * 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>
#include <structmember.h>
#define NO_IMPORT_PYGOBJECT
#include <pygobject.h>
#include <glib/gi18n.h>
#include "streamtuner.h"
#include "pst-category.h"
#include "pst-handler.h"
#include "pst-handler-config.h"
#include "pst-handler-field.h"
#include "pst-stream.h"
#include "pst-helpers.h"
#include "pst-transfer-session.h"

/*** attributes **************************************************************/

static int pst_handler_set_label (PSTHandler *self, PyObject *value, void *closure);
static int pst_handler_set_description (PSTHandler *self, PyObject *value, void *closure);
static int pst_handler_set_home (PSTHandler *self, PyObject *value, void *closure);
static int pst_handler_set_icon (PSTHandler *self, PyObject *value, void *closure);
static int pst_handler_set_stock_categories (PSTHandler *self, PyObject *value, void *closure);
static int pst_handler_set_flags (PSTHandler *self, PyObject *value, void *closure);
static int pst_handler_set_stream_version (PSTHandler *self, PyObject *value, void *closure);

static PyGetSetDef getsetters[] = {
  { "label", NULL, (setter) pst_handler_set_label },
  { "description", NULL, (setter) pst_handler_set_description },
  { "home", NULL, (setter) pst_handler_set_home },
  { "icon", NULL, (setter) pst_handler_set_icon },
  { "stock_categories", NULL, (setter) pst_handler_set_stock_categories },
  { "flags", NULL, (setter) pst_handler_set_flags },
  { "stream_version", NULL, (setter) pst_handler_set_stream_version },
  
  { NULL }
};

/*** methods *****************************************************************/

static PyObject *pst_handler_add_field (PSTHandler *self, PyObject *args);
static PyObject *pst_handler_notice (PSTHandler *self, PyObject *args);

static PyMethodDef methods[] = {
  { "add_field", (PyCFunction) pst_handler_add_field, METH_VARARGS },
  { "notice", (PyCFunction) pst_handler_notice, METH_VARARGS },
  
  { NULL }
};

/*** members *****************************************************************/

static PyMemberDef members[] = {
  { "config", T_OBJECT, G_STRUCT_OFFSET(PSTHandler, config), READONLY },

  { NULL }
};

/*** type object *************************************************************/

static PyObject *pst_handler_new (PyTypeObject *type, PyObject *args, PyObject *keywords);

PyTypeObject PSTHandler_Type = {
  PyObject_HEAD_INIT(NULL)
  0,				/* ob_size */
  "ST.Handler",			/* tp_name */
  sizeof(PSTHandler),		/* tp_basicsize */
  0,				/* tp_itemsize */
  NULL,				/* tp_dealloc */
  NULL,				/* tp_print */
  NULL,				/* tp_getattr */
  NULL,				/* tp_setattr */
  NULL,				/* tp_compare */
  NULL,				/* tp_repr */
  NULL,				/* tp_as_number */
  NULL,				/* tp_as_sequence */
  NULL,				/* tp_as_mapping */
  NULL,				/* tp_hash */
  NULL,				/* tp_call */
  NULL,				/* tp_str */
  NULL,				/* tp_getattro */
  NULL,				/* tp_setattro */
  NULL,				/* tp_as_buffer */
  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
  NULL,				/* tp_doc */
  NULL,				/* tp_traverse */
  NULL,				/* tp_clear */
  NULL,				/* tp_richcompare */
  0,				/* tp_weaklistoffset */
  NULL,				/* tp_iter */
  NULL,				/* tp_iternext */
  methods,			/* tp_methods */
  members,			/* tp_members */
  getsetters,			/* tp_getset */
  NULL,				/* tp_base */
  NULL,				/* tp_dict */
  NULL,				/* tp_descr_get */
  NULL,				/* tp_descr_set */
  0,				/* tp_dictoffset */
  NULL,				/* tp_init */
  NULL,				/* tp_alloc */
  pst_handler_new		/* tp_new */
};

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

typedef gboolean (* Converter) (PyObject *object, gpointer ptr);

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

static void pst_handler_bind_events (PSTHandler *self);

static int pst_handler_set_string (PSTHandler *self,
				   PyObject *value,
				   void (*func) (STHandler *, const char *));

static gboolean pst_handler_reload_cb (PythonCategory *category,
				       GNode **categories,
				       GList **streams,
				       PSTCallbackInfo *info,
				       GError **err);
static gboolean pst_handler_reload_multiple_cb (GNode **categories,
						GHashTable **streams,
						PSTCallbackInfo *info,
						GError **err);

static gboolean pst_handler_reload_parse_result (PyObject *result,
						 Converter converter1,
						 gpointer ptr1,
						 Converter converter2,
						 gpointer ptr2);
static gboolean pst_handler_reload_convert (PyObject *sequence,
					    int n,
					    Converter converter,
					    gpointer ptr);

static GtkWidget *pst_handler_preferences_widget_new_cb (PSTCallbackInfo *info);

/*** type methods ************************************************************/

static PyObject *
pst_handler_new (PyTypeObject *type, PyObject *args, PyObject *keywords)
{
  PSTHandler *self;
  static char *keyword_list[] = { "name", NULL };
  const char *name;
      
  if (! PyArg_ParseTupleAndKeywords(args, keywords, "s", keyword_list,
				    &name))
    return NULL;

  self = (PSTHandler *) type->tp_alloc(type, 0);
  if (! self)
    return NULL;

  self->handler = st_handler_new(name);
  self->config = pst_handler_config_new(self->handler);
  if (! self->config)
    return NULL;

  pst_handler_bind_events(self);
  
  return (PyObject *) self;
}

static void
pst_handler_bind_events (PSTHandler *self)
{
  static const struct
  {
    STHandlerEvent	event;
    gpointer		cb;
  } private_events[] = {
    { ST_HANDLER_EVENT_CATEGORY_NEW, pst_category_new_cb },
    { ST_HANDLER_EVENT_CATEGORY_FREE, pst_category_free_cb },
    { ST_HANDLER_EVENT_STREAM_NEW, pst_stream_new_cb },
    { ST_HANDLER_EVENT_STREAM_FIELD_SET, pst_stream_field_set_cb },
    { ST_HANDLER_EVENT_STREAM_FIELD_GET, pst_stream_field_get_cb },
    { ST_HANDLER_EVENT_STREAM_FREE, pst_stream_free_cb }
  };
  static const struct
  {
    STHandlerEvent	event;
    char		*method_name;
    gpointer		cb;
  } public_events[] = {
    { ST_HANDLER_EVENT_RELOAD, "reload", pst_handler_reload_cb },
    { ST_HANDLER_EVENT_RELOAD_MULTIPLE, "reload_multiple", pst_handler_reload_multiple_cb },
    { ST_HANDLER_EVENT_STREAM_RESOLVE, "stream_resolve", pst_stream_cb },
    { ST_HANDLER_EVENT_STREAM_TUNE_IN, "stream_tune_in", pst_stream_cb },
    { ST_HANDLER_EVENT_STREAM_RECORD, "stream_record", pst_stream_cb },
    { ST_HANDLER_EVENT_STREAM_BROWSE, "stream_browse", pst_stream_cb },
    { ST_HANDLER_EVENT_STREAM_DELETE, "stream_delete", pst_stream_cb },
    { ST_HANDLER_EVENT_STREAM_TUNE_IN_MULTIPLE, "stream_tune_in_multiple", pst_stream_tune_in_multiple_cb },
    { ST_HANDLER_EVENT_STREAM_STOCK_FIELD_GET, "stream_get_stock_field", pst_stream_stock_field_get_cb },
    { ST_HANDLER_EVENT_STREAM_MODIFY, "stream_modify", pst_stream_modify_cb },
    { ST_HANDLER_EVENT_PREFERENCES_WIDGET_NEW, "preferences_widget_new", pst_handler_preferences_widget_new_cb }
  };
  int i;

  /* private events (not exposed to the Python layer) */

  for (i = 0; i < G_N_ELEMENTS(private_events); i++)
    st_handler_bind(self->handler, private_events[i].event, private_events[i].cb, NULL);

  /* public events (exposed to the Python layer) */

  for (i = 0; i < G_N_ELEMENTS(public_events); i++)
    if (PyObject_HasAttrString((PyObject *) self, public_events[i].method_name))
      {
	PyObject *pattr;
	
	pattr = PyObject_GetAttrString((PyObject *) self, public_events[i].method_name);
	if (PyMethod_Check(pattr))
	  {
	    PSTCallbackInfo *info = g_new(PSTCallbackInfo, 1);
	    
	    Py_INCREF(self);
	    info->object = (PyObject *) self;
	    info->method = public_events[i].method_name;
	    
	    st_handler_bind(self->handler,
			    public_events[i].event,
			    public_events[i].cb,
			    info);
	  }
	Py_DECREF(pattr);
      }
}

static int
pst_handler_set_string (PSTHandler *self,
			PyObject *value,
			void (*func) (STHandler *, const char *))
{
  const char *str;

  g_return_val_if_fail(self != NULL, -1);

  if (! value)
    {
      PyErr_SetString(PyExc_TypeError, _("cannot unset member"));
      return -1;
    }

  str = PyString_AsString(value);
  if (! str)
    return -1;

  func(self->handler, str);

  return 0;
}

static int
pst_handler_set_label (PSTHandler *self, PyObject *value, void *closure)
{
  return pst_handler_set_string(self, value, st_handler_set_label);
}

static int
pst_handler_set_description (PSTHandler *self, PyObject *value, void *closure)
{
  return pst_handler_set_string(self, value, st_handler_set_description);
}

static int
pst_handler_set_home (PSTHandler *self, PyObject *value, void *closure)
{
  return pst_handler_set_string(self, value, st_handler_set_home);
}

static int
pst_handler_set_icon (PSTHandler *self, PyObject *value, void *closure)
{
  GObject *pixbuf;

  if (! value)
    {
      PyErr_SetString(PyExc_TypeError, _("cannot unset icon"));
      return -1;
    }
  
  pixbuf = pst_pygobject_get(value, GDK_TYPE_PIXBUF);
  if (! pixbuf)
    return -1;

  st_handler_set_icon_from_pixbuf(self->handler, GDK_PIXBUF(pixbuf));

  return 0;
}

static int
pst_handler_set_stock_categories (PSTHandler *self, PyObject *value, void *closure)
{
  if (value)
    {
      GNode *categories;

      if (! pst_categories_sequence_as_gnode(value, &categories))
	return -1;

      st_handler_set_stock_categories(self->handler, categories);
    }
  else
    st_handler_set_stock_categories(self->handler, NULL);

  return 0;
}

static int
pst_handler_set_flags (PSTHandler *self, PyObject *value, void *closure)
{
  if (value)
    {
      int flags;

      flags = PyInt_AsLong(value);
      if (flags == -1)
	return -1;

      st_handler_set_flags(self->handler, flags);
    }
  else
    st_handler_set_flags(self->handler, 0);

  return 0;
}

static int
pst_handler_set_stream_version (PSTHandler *self, PyObject *value, void *closure)
{
  if (value)
    {
      int version;

      version = PyInt_AsLong(value);
      if (version == -1)
	return -1;

      st_handler_set_stream_version(self->handler, version);
    }
  else
    st_handler_set_stream_version(self->handler, 0);

  return 0;
}

/*** object methods **********************************************************/

static PyObject *
pst_handler_add_field (PSTHandler *self, PyObject *args)
{
  PSTHandlerField *pfield;

  if (! PyArg_ParseTuple(args, "O!", &PSTHandlerField_Type, &pfield))
    return NULL;

  st_handler_add_field(self->handler, pfield->field);

  return pst_none();
}

static PyObject *
pst_handler_notice (PSTHandler *self, PyObject *args)
{
  const char *message;

  if (! PyArg_ParseTuple(args, "s", &message))
    return NULL;

  st_handler_notice(self->handler, "%s", message);

  return pst_none();
}

/*** streamtuner callbacks ***************************************************/

static gboolean
pst_handler_reload_cb (PythonCategory *category,
		       GNode **categories,
		       GList **streams,
		       PSTCallbackInfo *info,
		       GError **err)
{
  PyObject *result;
  gboolean status;

  PST_THREADS_ENTER

  result = PyObject_CallMethod(info->object, info->method, "O", category->p);

  status = pst_handler_reload_parse_result(result,
					   (Converter) pst_categories_sequence_as_gnode, categories,
					   (Converter) pst_streams_sequence_as_glist, streams);
  if (! status)
    pst_set_error(err);
      
  Py_XDECREF(result);

  PST_THREADS_LEAVE

  return status;
}

static gboolean
pst_handler_reload_multiple_cb (GNode **categories,
				GHashTable **streams,
				PSTCallbackInfo *info,
				GError **err)
{
  PyObject *result;
  gboolean status;

  PST_THREADS_ENTER

  result = PyObject_CallMethod(info->object, info->method, NULL);

  status = pst_handler_reload_parse_result(result,
					   (Converter) pst_categories_sequence_as_gnode, categories,
					   (Converter) pst_streams_mapping_as_ghashtable, streams);
  if (! status)
    pst_set_error(err);
      
  Py_XDECREF(result);

  PST_THREADS_LEAVE

  return status;
}

static gboolean
pst_handler_reload_parse_result (PyObject *result,
				 Converter converter1,
				 gpointer ptr1,
				 Converter converter2,
				 gpointer ptr2)
{
  int len;

  g_return_val_if_fail(converter1 != NULL, FALSE);
  g_return_val_if_fail(ptr1 != NULL, FALSE);
  g_return_val_if_fail(converter2 != NULL, FALSE);
  g_return_val_if_fail(ptr2 != NULL, FALSE);

  if (! result)
    return FALSE;

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

  if (len != 2)
    {
      PyErr_SetString(PyExc_TypeError, _("sequence length must be 2"));
      return FALSE;
    }

  if (! pst_handler_reload_convert(result, 0, converter1, ptr1))
    return FALSE;
  if (! pst_handler_reload_convert(result, 1, converter2, ptr2))
    return FALSE;

  return TRUE;
}

static gboolean
pst_handler_reload_convert (PyObject *sequence,
			    int n,
			    Converter converter,
			    gpointer ptr)
{
  PyObject *object;
  gboolean status;

  g_return_val_if_fail(sequence != NULL, FALSE);
  g_return_val_if_fail(converter != NULL, FALSE);
  g_return_val_if_fail(ptr != NULL, FALSE);

  object = PySequence_ITEM(sequence, n);
  if (! object)
    return FALSE;

  status = converter(object, ptr);
  Py_DECREF(object);

  return status;
}

static GtkWidget *
pst_handler_preferences_widget_new_cb (PSTCallbackInfo *info)
{
  PyObject *result;
  GtkWidget *widget = NULL;

  PST_THREADS_ENTER

  result = PyObject_CallMethod(info->object, info->method, NULL);
  if (result)
    {
      widget = (GtkWidget *) pst_pygobject_get(result, GTK_TYPE_WIDGET);
      if (! widget)
	PyErr_Print();

      Py_DECREF(result);
    }
  else
    PyErr_Print();

  PST_THREADS_LEAVE

  return widget;
}

/*** C API *******************************************************************/

gboolean
pst_handler_register (PyObject *module)
{
  PyTypeObject *ptr = &PSTHandler_Type;

  g_return_val_if_fail(module != NULL, FALSE);

  if (PyType_Ready(&PSTHandler_Type) < 0)
    return FALSE;

  Py_INCREF(&PSTHandler_Type);
  PyModule_AddObject(module, "Handler", (PyObject *) ptr);

  PyModule_AddIntConstant(module, "HANDLER_NO_CATEGORIES", ST_HANDLER_NO_CATEGORIES);
  PyModule_AddIntConstant(module, "HANDLER_CONFIRM_DELETION", ST_HANDLER_CONFIRM_DELETION);

  PyModule_AddIntConstant(module, "HANDLER_STOCK_FIELD_NAME", ST_HANDLER_STOCK_FIELD_NAME);
  PyModule_AddIntConstant(module, "HANDLER_STOCK_FIELD_GENRE", ST_HANDLER_STOCK_FIELD_GENRE);
  PyModule_AddIntConstant(module, "HANDLER_STOCK_FIELD_DESCRIPTION", ST_HANDLER_STOCK_FIELD_DESCRIPTION);
  PyModule_AddIntConstant(module, "HANDLER_STOCK_FIELD_HOMEPAGE", ST_HANDLER_STOCK_FIELD_HOMEPAGE);
  PyModule_AddIntConstant(module, "HANDLER_STOCK_FIELD_URI_LIST", ST_HANDLER_STOCK_FIELD_URI_LIST);

  return TRUE;
}
