/*
 * 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 <string.h>
#include <glib/gi18n.h>
#include <Python.h>
#include "streamtuner.h"
#include "pst-helpers.h"
#include "pst-transfer-session.h"

typedef struct
{
  PyThreadState	**thread_state;
  PyObject	*cb;
  PyObject	*args;
} CallbackInfo;

typedef struct
{
  PyObject_HEAD
  
  STTransferSession *session;
} PSTTransferSession;

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

static PyObject *pst_transfer_session_get (PSTTransferSession *self,
					   PyObject *args,
					   PyObject *keywords);
static PyObject *pst_transfer_session_get_binary (PSTTransferSession *self,
						  PyObject *args,
						  PyObject *keywords);
static PyObject *pst_transfer_session_get_by_line (PSTTransferSession *self,
						   PyObject *args,
						   PyObject *keywords);

static PyMethodDef methods[] = {
  { "get", (PyCFunction) pst_transfer_session_get, METH_VARARGS | METH_KEYWORDS },
  { "get_binary", (PyCFunction) pst_transfer_session_get_binary, METH_VARARGS | METH_KEYWORDS },
  { "get_by_line", (PyCFunction) pst_transfer_session_get_by_line, METH_VARARGS | METH_KEYWORDS },
  
  { NULL }
};

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

static PyObject *pst_transfer_session_new (PyTypeObject *type, PyObject *args, PyObject *keywords);
static void pst_transfer_session_dealloc (PSTTransferSession *self);

static PyTypeObject PSTTransferSession_Type = {
  PyObject_HEAD_INIT(NULL)
  0,				/* ob_size */
  "ST.TransferSession",		/* tp_name */
  sizeof(PSTTransferSession),	/* tp_basicsize */
  0,				/* tp_itemsize */
  (destructor) pst_transfer_session_dealloc, /* 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 */
  NULL,				/* tp_members */
  NULL,				/* 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_transfer_session_new	/* tp_new */
};

PyObject *PSTExc_AbortError = NULL;

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

static void pst_transfer_session_get_by_line_cb (const char *line, gpointer data);

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

static PyObject *
pst_transfer_session_new (PyTypeObject *type, PyObject *args, PyObject *keywords)
{
  static char *keyword_list[] = { NULL };
  PSTTransferSession *self;

  if (! PyArg_ParseTupleAndKeywords(args, keywords, "", keyword_list))
    return NULL;

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

  self->session = st_transfer_session_new();

  return (PyObject *) self;
}

static void
pst_transfer_session_dealloc (PSTTransferSession *self)
{
  st_transfer_session_free(self->session);
  self->ob_type->tp_free((PyObject *) self);
}

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

static PyObject *
pst_transfer_session_get (PSTTransferSession *self,
			  PyObject *args,
			  PyObject *keywords)
{
  static char *keyword_list[] = { "url", "flags", "fetch_headers", "fetch_body", NULL };
  const char *url;
  unsigned int flags = 0;
  gboolean fetch_headers = FALSE;
  gboolean fetch_body = TRUE;
  
  gboolean status;
  char *headers = NULL;
  char *body = NULL;
  GError *err = NULL;
  PyObject *result;

  if (! PyArg_ParseTupleAndKeywords(args, keywords, "s|iii", keyword_list,
				    &url, &flags, &fetch_headers, &fetch_body))
    return NULL;

  Py_BEGIN_ALLOW_THREADS
  status = st_transfer_session_get(self->session,
				   url,
				   flags,
				   fetch_headers ? &headers : NULL,
				   fetch_body ? &body : NULL,
				   &err);
  Py_END_ALLOW_THREADS

    if (! status)
    {
      if (err)
	{
	  PyErr_SetString(PyExc_RuntimeError, err->message);
	  g_error_free(err);
	}
      else
	PyErr_SetString(PSTExc_AbortError, _("aborted by the user"));
      
      return NULL;
    }
  
  if (fetch_headers && fetch_body)
    result = Py_BuildValue("(ss)", headers, body);
  else if (fetch_headers)
    result = PyString_FromString(headers);
  else if (fetch_body)
    result = PyString_FromString(body);
  else
    result = pst_none();
  
  g_free(headers);
  g_free(body);

  return result;
}

static PyObject *
pst_transfer_session_get_binary (PSTTransferSession *self,
				 PyObject *args,
				 PyObject *keywords)
{
  static char *keyword_list[] = { "url", "flags", "fetch_headers", "fetch_body", NULL };
  const char *url;
  unsigned int flags = 0;
  gboolean fetch_headers = FALSE;
  gboolean fetch_body = TRUE;
  
  gboolean status;
  char *headers = NULL;
  guint8 *body = NULL;
  unsigned int body_len = 0;
  GError *err = NULL;
  PyObject *buffer;
  int pstatus;
  PyObject *result = NULL;
  
  if (! PyArg_ParseTupleAndKeywords(args, keywords, "s|iii", keyword_list,
				    &url, &flags, &fetch_headers, &fetch_body))
    return NULL;

  Py_BEGIN_ALLOW_THREADS
  status = st_transfer_session_get_binary(self->session,
					  url,
					  flags,
					  fetch_headers ? &headers : NULL,
					  fetch_body ? &body : NULL,
					  fetch_body ? &body_len : NULL,
					  &err);
  Py_END_ALLOW_THREADS

  if (! status)
    {
      if (err)
	{
	  PyErr_SetString(PyExc_RuntimeError, err->message);
	  g_error_free(err);
	}
      else
	PyErr_SetString(PSTExc_AbortError, _("aborted by the user"));
      
      return NULL;
    }
  
  if (fetch_body)
    {
      gpointer ptr;
      int len;

      buffer = PyBuffer_New(body_len);
      if (! buffer)
	goto end;

      pstatus = PyObject_AsWriteBuffer(buffer, &ptr, &len);
      g_return_val_if_fail(pstatus == 0, NULL);
      g_return_val_if_fail(len == body_len, NULL);
  
      memcpy(ptr, body, len);
    }
  if (fetch_headers && fetch_body)
    result = Py_BuildValue("(sN)", headers, buffer);
  else if (fetch_headers)
    result = PyString_FromString(headers);
  else if (fetch_body)
    result = buffer;
  else
    result = pst_none();

 end:
  g_free(headers);
  g_free(body);
  
  return result;
}

static PyObject *
pst_transfer_session_get_by_line (PSTTransferSession *self,
				  PyObject *args,
				  PyObject *keywords)
{
  static char *keyword_list[] = {
    "url",
    "flags",
    "header_cb",
    "header_args",
    "body_cb",
    "body_args",
    NULL
  };
  const char *url;
  unsigned int flags = 0;

  PyThreadState *thread_state;
  CallbackInfo header_info = { &thread_state, NULL, NULL };
  CallbackInfo body_info = { &thread_state, NULL, NULL };
  GError *err = NULL;
  gboolean status;

  if (! PyArg_ParseTupleAndKeywords(args, keywords, "s|iOO!OO!", keyword_list,
				    &url,
				    &flags,
				    &header_info.cb,
				    &PyTuple_Type, &header_info.args,
				    &body_info.cb,
				    &PyTuple_Type, &body_info.args))
    return NULL;
  
  thread_state = PyEval_SaveThread();
  status = st_transfer_session_get_by_line(self->session,
					   url,
					   flags,
					   header_info.cb ? pst_transfer_session_get_by_line_cb : NULL,
					   header_info.cb ? &header_info : NULL,
					   body_info.cb ? pst_transfer_session_get_by_line_cb : NULL,
					   body_info.cb ? &body_info: NULL,
					   &err);
  PyEval_RestoreThread(thread_state);

  if (! status)
    {
      if (err)
	{
	  PyErr_SetString(PyExc_RuntimeError, err->message);
	  g_error_free(err);
	}
      else
	PyErr_SetString(PSTExc_AbortError, _("aborted by the user"));

      return NULL;
    }
  
  return pst_none();
}

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

static void
pst_transfer_session_get_by_line_cb (const char *line, gpointer data)
{
  CallbackInfo *info = data;
  PyObject *pline;
  PyObject *pargs;
  PyObject *presult;

  PyEval_RestoreThread(*info->thread_state);

  pline = Py_BuildValue("(s)", line);
  if (! pline)
    {
      PyErr_Print();
      goto end;
    }
  
  if (info->args)
    {
      pargs = PySequence_Concat(pline, info->args);
      Py_DECREF(pline);
      
      if (! pargs)
	{
	  PyErr_Print();
	  goto end;
	}
    }
  else
    pargs = pline;

  presult = PyObject_CallObject(info->cb, pargs);
  Py_DECREF(pargs);

  if (presult)
    Py_DECREF(presult);
  else
    PyErr_Print();

  end:
  *info->thread_state = PyEval_SaveThread();
}

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

gboolean
pst_transfer_session_register (PyObject *module)
{
  PyTypeObject *ptr = &PSTTransferSession_Type;

  g_return_val_if_fail(module != NULL, FALSE);

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

  Py_INCREF(&PSTTransferSession_Type);
  PyModule_AddObject(module, "TransferSession", (PyObject *) ptr);

  PyModule_AddIntConstant(module, "TRANSFER_PASS_NEWLINE", ST_TRANSFER_PASS_NEWLINE);
  PyModule_AddIntConstant(module, "TRANSFER_UTF8", ST_TRANSFER_UTF8);
  PyModule_AddIntConstant(module, "TRANSFER_PARSE_HTTP_CHARSET", ST_TRANSFER_PARSE_HTTP_CHARSET);
  PyModule_AddIntConstant(module, "TRANSFER_PARSE_HTML_CHARSET", ST_TRANSFER_PARSE_HTML_CHARSET);

  if (! PSTExc_AbortError)
    PSTExc_AbortError = PyErr_NewException("ST.AbortError", NULL, NULL);
  PyModule_AddObject(module, "AbortError", PSTExc_AbortError);

  return TRUE;
}
