/***************************************************************************
 *
 * Copyright (c) 2000, 2001 BalaBit IT Ltd, Budapest, Hungary
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: pyconnect.c,v 1.36.2.1.2.4 2001/09/19 19:22:17 bazsi Exp $
 *
 * Author  : Bazsi
 * Auditor : kisza
 * Last version: 1.16
 * Notes:
 *
 ***************************************************************************/

#include <zorp/pyconnect.h>
#include <zorp/io.h>
#include <zorp/log.h>
#include <zorp/policy.h>
#include <zorp/pysockaddr.h>
#include <zorp/freeq.h>

/*
 * struct ZorpConnect
 *
 * Author:  Bazsi, 2000/03/27
 * Purpose: this class encapsulates a connector
 *
 */

struct _ZorpConnect
{
  PyObject_HEAD
  ZIOConnect *connect;
  ZSockAddr *local;
  PyObject *handler;
  int fd;
  GMutex *lock;
  GCond *connected;
  gboolean dont_lock;
};

static PyObject *z_py_zorp_connect_new_instance(PyObject *s, PyObject *args);
static void z_py_zorp_connect_destroy(ZorpConnect *self);
static void z_py_zorp_connect_free(ZorpConnect *self);
static PyObject *z_py_zorp_connect_getattr(PyObject *o, char *name);

static PyObject *z_py_zorp_connect_start_method(ZorpConnect *self, PyObject *args);
static PyObject *z_py_zorp_connect_block_method(ZorpConnect *self, PyObject *args);
static PyObject *z_py_zorp_connect_destroy_method(ZorpConnect *self, PyObject *args);


PyMethodDef z_py_zorp_connect_funcs[] =
{
  { "Connect",  z_py_zorp_connect_new_instance, METH_VARARGS },
  { NULL,      NULL }   /* sentinel*/
};

static PyMethodDef py_zorp_connect_methods[] =
{
  { "start",       (PyCFunction) z_py_zorp_connect_start_method, 0 },
  { "block",       (PyCFunction) z_py_zorp_connect_block_method, 0 },
  { "destroy",     (PyCFunction) z_py_zorp_connect_destroy_method, 0 },
  { NULL,          NULL }   /* sentinel*/
};

PyTypeObject z_py_zorp_connect_type = 
{
  PyObject_HEAD_INIT(&z_py_zorp_connect_type)
  0,
  "ZorpConnect",
  sizeof(ZorpConnect),
  0,
  (destructor) z_py_zorp_connect_free,
  0,
  (getattrfunc) z_py_zorp_connect_getattr,
  0,
  0,
  0, /*(reprfunc) z_py_zorp_connect_repr,*/
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  "ZorpConnect class for Zorp",
};

/* called by the low level connector, under the main thread */
static void
z_py_zorp_connect_connected(int fd, gpointer user_data)
{
  ZorpConnect *self = (ZorpConnect *) user_data;
  ZIOConnect *connect;
  PyObject *res;

  z_log(NULL, CORE_DEBUG, 7, "z_py_zorp_connect_connected(%p, %p)", self, self->connect);

  if (!self->connect)
    {
      /* we have already been called */
      return;
    }
  connect = self->connect;
  self->connect = NULL;
  z_io_connect_unref(connect);
    
  if (self->handler != Py_None)
    {
      if (!self->dont_lock)
        {
          z_policy_lock(z_python_global_state);
          if (self->handler == NULL)
            {
              z_policy_unlock(z_python_global_state);
              return;
            }
          res = PyEval_CallFunction(self->handler, "(i)", fd);
          if (!res || PyErr_Occurred())
    	    {
    	      PyErr_Print();
    	      close(fd);
    	    }
          z_py_zorp_connect_destroy(self);
          z_policy_unlock(z_python_global_state);
        }
      else
        {
          if (self->handler == NULL)
            return;
          res = PyEval_CallFunction(self->handler, "(i)", fd);
          if (!res || PyErr_Occurred())
    	    {
              PyErr_Print();
              close(fd);
    	    }
          z_py_zorp_connect_destroy(self);
        }
    }
  else
    {
      g_mutex_lock(self->lock);
      self->fd = fd;
      g_cond_signal(self->connected);
      g_mutex_unlock(self->lock);
    }
}

static PyObject *
z_py_zorp_connect_new_instance(PyObject *s, PyObject *args)
{
  ZorpSockAddr *local, *remote;
  ZorpConnect *self;
  PyObject *handler;
  char buf1[128], buf2[128];

  z_enter();
  /* called by python, no need to lock the interpreter */
   
  if (!PyArg_ParseTuple(args, "OOO", &local, &remote, &handler))
    return NULL;
  if (handler != Py_None && !PyCallable_Check(handler))
    {
      PyErr_SetString(PyExc_TypeError, "Handler parameter must be callable");
      z_leave();
      return NULL;
    }
  if ((((PyObject *) local != Py_None) && !z_py_zorp_sockaddr_check(local)) ||
      (!z_py_zorp_sockaddr_check(remote)))
    {
      PyErr_SetString(PyExc_TypeError, "Local and remote arguments must be ZorpSockAddr or None");
      z_leave();
      return NULL;
    }
  /*LOG
    This message indicates that Zorp began establishing connection
    with the indicated remote host.
   */
  z_log(NULL, CORE_DEBUG, 7, "Connecting to remote host; local='%s', remote='%s'", 
	  (PyObject *) local != Py_None ? z_sockaddr_format(local->sa, buf1, sizeof(buf1)) : "NULL",
	  z_sockaddr_format(remote->sa, buf2, sizeof(buf2)));

  self = PyObject_NEW(ZorpConnect, &z_py_zorp_connect_type);
  if (!self)
    {
      z_leave();
      return NULL;
    }
  
  self->local = NULL;
  self->lock = g_mutex_new();
  self->dont_lock = FALSE;
  if (handler == Py_None)  
    {
      self->connected = g_cond_new();
    }
  else
    {
      self->connected = NULL;
    }

  Py_XINCREF(handler);
  self->handler = handler;

  self->fd = -2;
  self->connect = z_io_connect_new((PyObject *) local == Py_None ? NULL : local->sa, remote->sa, z_py_zorp_connect_connected, self);
  if (!self->connect)
    {
      PyErr_SetString(PyExc_IOError, "Error during connect");
      
      /* the interpreter is locked */
      z_py_zorp_connect_destroy(self);
      z_leave();
      return NULL;
    }

  Py_XINCREF((PyObject *) self); /* returned reference */
  z_leave();
  return (PyObject *) self;
}

static PyObject *
z_py_zorp_connect_start_method(ZorpConnect *self, PyObject *args)
{
  z_enter();

  if (self->connect)
    {
      ZSockAddr *local;
      
      local = z_io_connect_start(self->connect);
      self->local = local;
      if (local == NULL)
        {
          self->dont_lock = TRUE;
          z_py_zorp_connect_connected(-1, self);
        }
    }
  else
    z_trace(NULL, "py_connect_start: self=%p, self->connect = NULL", self);

  Py_XINCREF(Py_None);
  z_leave();
  return Py_None;
}

static PyObject *
z_py_zorp_connect_block_method(ZorpConnect *self, PyObject *args)
{
  z_enter();
  if (self->handler == Py_None)
    {
      PyObject *res;
          
      g_mutex_lock(self->lock);
      if (self->fd == -2)
        {
          Py_BEGIN_ALLOW_THREADS
          g_cond_wait(self->connected, self->lock);
          Py_END_ALLOW_THREADS
        }
      
      g_mutex_unlock(self->lock);

      res = Py_BuildValue("i", self->fd);
      z_py_zorp_connect_destroy(self);
      z_leave();
      return res;
    }
  PyErr_SetString(PyExc_ValueError, "z_py_zorp_connect, block called with handler != None");
  z_leave();
  return NULL;
}

/* called automatically to free up this instance */
static void
z_py_zorp_connect_free(ZorpConnect *self)
{
  z_enter();
  if (self->handler)
    {
      PyObject *handler = self->handler;
      self->handler = NULL;
      Py_XDECREF(handler);
    }
  if (self->connect)
    {
      ZIOConnect *connect = self->connect;
      self->connect = NULL;
      z_io_connect_unref(connect);
    }
  if (self->local)
    {
      ZSockAddr *local = self->local;
      self->local = NULL;
      z_sockaddr_unref(local);
    }
  if (self->lock)
    {
      g_mutex_free(self->lock);
    }
  if (self->connected)
    {
      g_cond_free(self->connected);
    }
  PyMem_DEL(self);
  z_leave();
}

void
z_py_zorp_connect_freer(gpointer p)
{
  ZorpConnect *self = (ZorpConnect *) p;

  /* we are after callbacks were called, thus _connected was either called
     or the connection is not yet established. If it was called self->connect
     is NULL, and the self-reference is dropped, if it isn't self->connect is
     not NULL, and we deref ourselves here */
  z_policy_lock(z_python_global_state);
  if (self->connect)
    {
      Py_XDECREF(self);
    }
  Py_XDECREF(self);
  z_policy_unlock(z_python_global_state);
}

/* called from C to destroy this instance, Python interpreter must be locked */
static void
z_py_zorp_connect_destroy(ZorpConnect *self)
{
  PyObject *handler;

  z_enter();
  handler = self->handler;
  self->handler = NULL;
  Py_XDECREF(handler); /* avoid circular references */

  Py_XDECREF((PyObject *) self);
  z_leave();
}

/* called from Python to destroy this instance */
static PyObject *
z_py_zorp_connect_destroy_method(ZorpConnect *self, PyObject *args)
{
  PyObject *handler;
  
  z_enter();

  /* this would race with _connected, but we are protected by the interpreter lock */

  Py_XINCREF(self);
  
  handler = self->handler;
  self->handler = NULL;
  Py_XDECREF(handler); /* avoid circular references */

  z_free_queue_add(self, z_py_zorp_connect_freer);
  
  Py_XINCREF(Py_None);
  z_leave();
  return Py_None;
}

static PyObject *
z_py_zorp_connect_getattr(PyObject *o, char *name)
{
  ZorpConnect *self = (ZorpConnect *) o;
  PyObject * back;
  z_enter();
  
  if (strcmp(name, "local") == 0)
    {
      if (self->local)
        {
          back = z_py_zorp_sockaddr_new(self->local);
          z_leave();
          return back;
        }
      else
        {
          Py_XINCREF(Py_None);
          z_leave();
          return Py_None;
        }
    }
  else
    {
      back = Py_FindMethod(py_zorp_connect_methods, o, name);
      z_leave();
      return back;
    }
}

void
z_py_zorp_connect_init(void)
{
  /* PyImport_AddModule("Zorp.Zorp"); */
  Py_InitModule("Zorp.Zorp", z_py_zorp_connect_funcs);
}
