/*
 * Definition of a `Connection` type.  
 * Used by `socket_connection.c` and `pipe_connection.c`.
 *
 * connection.h
 *
 * Copyright (c) 2006-2008, R Oudkerk --- see COPYING.txt
 */

#ifndef CONNECTION_H
#define CONNECTION_H

/*
 * Read/write flags
 */

#define READABLE 1
#define WRITABLE 2

#define CHECK_READABLE(self) \
    if (!(self->flags & READABLE)) { \
        PyErr_SetString(PyExc_IOError, "connection is write-only"); \
        return NULL; \
    }

#define CHECK_WRITABLE(self) \
    if (!(self->flags & WRITABLE)) { \
        PyErr_SetString(PyExc_IOError, "connection is read-only"); \
        return NULL; \
    }

/*
 * Allocation and deallocation
 */

static PyObject *
connection_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
	ConnectionObject *self;
	HANDLE handle;
	BOOL readable = TRUE, writable = TRUE;

	static char *kwlist[] = {"handle", "readable", "writable", NULL};

	if (!PyArg_ParseTupleAndKeywords(args, kwds, F_HANDLE "|ii", kwlist, 
					 &handle, &readable, &writable))
		return NULL;

	if (handle == INVALID_HANDLE_VALUE || (Py_ssize_t)handle < 0) {
		PyErr_Format(PyExc_IOError, "invalid handle %zd",
			     (Py_ssize_t)handle);
		return NULL;
	}

	if (!readable && !writable) {
		PyErr_SetString(PyExc_ValueError, 
				"either readable or writable must be true");
		return NULL;
	}

	self = PyObject_New(ConnectionObject, type);
	if (self == NULL)
		return NULL;

	self->weakreflist = NULL;
	self->handle = handle;
	self->flags = 0;

	if (readable)
		self->flags |= READABLE;
	if (writable)
		self->flags |= WRITABLE;
	assert(self->flags >= 1 && self->flags <= 3);

	return (PyObject*)self;
}

static void
connection_dealloc(ConnectionObject* self)
{
	if (self->weakreflist != NULL)
		PyObject_ClearWeakRefs((PyObject*)self);

	if (self->handle != INVALID_HANDLE_VALUE) {
		Py_BEGIN_ALLOW_THREADS
		CLOSE(self->handle);
		Py_END_ALLOW_THREADS
	}
	PyObject_Del(self);
}

/*
 * Functions for transferring buffers
 */

static PyObject *
connection_sendbytes(ConnectionObject *self, PyObject *args)
{
	char *buffer;
	Py_ssize_t length, offset=0, size=PY_SSIZE_T_MIN;
	int res;

	if (!PyArg_ParseTuple(args, F_RBUFFER "#|" F_PY_SSIZE_T F_PY_SSIZE_T,
			      &buffer, &length, &offset, &size))
		return NULL;

	CHECK_WRITABLE(self);

	if (offset < 0) {
		PyErr_SetString(PyExc_ValueError, "offset is negative");
		return NULL;
	}
	if (length < offset) {
		PyErr_SetString(PyExc_ValueError, "buffer length < offset");
		return NULL;
	}

	if (size == PY_SSIZE_T_MIN) {
		size = length - offset;
	} else {
		if (size < 0) {
			PyErr_SetString(PyExc_ValueError, "size is negative");
			return NULL;		
		}
		if (offset + size > length) {
			PyErr_SetString(PyExc_ValueError, 
					"buffer length < offset + size");
			return NULL;
		}
	}

	res = conn_send_string(self, buffer + offset, size);

	if (res < 0)
		return mp_SetError(PyExc_IOError, res);

	Py_RETURN_NONE;
}

static PyObject *
connection_recvbytes(ConnectionObject *self, PyObject *args) 
{
	char *freeme = NULL;
	Py_ssize_t res, maxlength = PY_SSIZE_T_MAX;
	PyObject *result = NULL;

	if (!PyArg_ParseTuple(args, "|" F_PY_SSIZE_T, &maxlength))
		return NULL;

	CHECK_READABLE(self);
	
	if (maxlength < 0) {
		PyErr_SetString(PyExc_ValueError, "maxlength < 0");
		return NULL;
	}
	
	res = conn_recv_string(self, self->buffer, CONNECTION_BUFFER_SIZE, 
			       &freeme, maxlength);
	
	if (res < 0) {
		if (res == MP_BAD_MESSAGE_LENGTH) {
			if ((self->flags & WRITABLE) == 0) {
				Py_BEGIN_ALLOW_THREADS
				CLOSE(self->handle);
				Py_END_ALLOW_THREADS
				self->handle = INVALID_HANDLE_VALUE;
			} else {
				self->flags = WRITABLE;
			}
		}
		mp_SetError(PyExc_IOError, res);
	} else {    
		if (freeme == NULL) {
			result = PyString_FromStringAndSize(self->buffer, res);
		} else {
			result = PyString_FromStringAndSize(freeme, res);
			PyMem_Free(freeme);
		}
	}
	
	return result;
}

static PyObject *
connection_recvbytes_into(ConnectionObject *self, PyObject *args) 
{
	char *freeme = NULL, *buffer = NULL;
	Py_ssize_t res, length, offset = 0;
	PyObject *result = NULL;
	Py_buffer pbuf;

	CHECK_READABLE(self);
	
	if (!PyArg_ParseTuple(args, "w*|" F_PY_SSIZE_T, 
			      &pbuf, &offset))
		return NULL;

	buffer = pbuf.buf;
	length = pbuf.len;

	if (offset < 0) {
		PyErr_SetString(PyExc_ValueError, "negative offset");
		goto _error;
	}   

	if (offset > length) {
		PyErr_SetString(PyExc_ValueError, "offset too large");
		goto _error;
	}

	res = conn_recv_string(self, buffer+offset, length-offset, 
			       &freeme, PY_SSIZE_T_MAX);

	if (res < 0) {
		if (res == MP_BAD_MESSAGE_LENGTH) {
			if ((self->flags & WRITABLE) == 0) {
				Py_BEGIN_ALLOW_THREADS
				CLOSE(self->handle);
				Py_END_ALLOW_THREADS
				self->handle = INVALID_HANDLE_VALUE;
			} else {
				self->flags = WRITABLE;
			}
		}
		mp_SetError(PyExc_IOError, res);
	} else {
		if (freeme == NULL) {
			result = PyInt_FromSsize_t(res);
		} else {
			result = PyObject_CallFunction(BufferTooShort, 
						       F_RBUFFER "#", 
						       freeme, res);
			PyMem_Free(freeme);
			if (result) {
				PyErr_SetObject(BufferTooShort, result);
				Py_DECREF(result);
			}
			goto _error;
		}
	}

_cleanup:
	PyBuffer_Release(&pbuf);
	return result;

_error:
	result = NULL;
	goto _cleanup;
}

/*
 * Functions for transferring objects
 */

static PyObject *
connection_send_obj(ConnectionObject *self, PyObject *obj)
{
	char *buffer;
	int res;
	Py_ssize_t length;
	PyObject *pickled_string = NULL;

	CHECK_WRITABLE(self);

	pickled_string = PyObject_CallFunctionObjArgs(pickle_dumps, obj, 
						      pickle_protocol, NULL);
	if (!pickled_string)
		goto failure;

	if (PyString_AsStringAndSize(pickled_string, &buffer, &length) < 0)
		goto failure;

	res = conn_send_string(self, buffer, (int)length);

	if (res < 0) {
		mp_SetError(PyExc_IOError, res);
		goto failure;
	}

	Py_XDECREF(pickled_string);
	Py_RETURN_NONE;

  failure:
	Py_XDECREF(pickled_string);
	return NULL;
}

static PyObject *
connection_recv_obj(ConnectionObject *self)
{
	char *freeme = NULL;
	Py_ssize_t res;
	PyObject *temp = NULL, *result = NULL;

	CHECK_READABLE(self);

	res = conn_recv_string(self, self->buffer, CONNECTION_BUFFER_SIZE, 
			       &freeme, PY_SSIZE_T_MAX);

	if (res < 0) {
		if (res == MP_BAD_MESSAGE_LENGTH) {
			if ((self->flags & WRITABLE) == 0) {
				Py_BEGIN_ALLOW_THREADS
				CLOSE(self->handle);
				Py_END_ALLOW_THREADS
				self->handle = INVALID_HANDLE_VALUE;
			} else {
				self->flags = WRITABLE;
			}
		}
		mp_SetError(PyExc_IOError, res);
	} else {    
		if (freeme == NULL) {
			temp = PyString_FromStringAndSize(self->buffer, res);
		} else {
			temp = PyString_FromStringAndSize(freeme, res);
			PyMem_Free(freeme);
		}
	}

	if (temp)
		result = PyObject_CallFunctionObjArgs(pickle_loads, 
						      temp, NULL);
	Py_XDECREF(temp);
	return result;
}

/*
 * Other functions
 */

static PyObject *
connection_poll(ConnectionObject *self, PyObject *args)
{
	PyObject *timeout_obj = NULL;
	double timeout = 0.0;
	int res;

	CHECK_READABLE(self);

	if (!PyArg_ParseTuple(args, "|O", &timeout_obj))
		return NULL;

	if (timeout_obj == NULL) {
		timeout = 0.0;
	} else if (timeout_obj == Py_None) {
		timeout = -1.0;				/* block forever */
	} else {
		timeout = PyFloat_AsDouble(timeout_obj);
		if (PyErr_Occurred())
			return NULL;
		if (timeout < 0.0)
			timeout = 0.0;
	}

	Py_BEGIN_ALLOW_THREADS
	res = conn_poll(self, timeout);
	Py_END_ALLOW_THREADS

	switch (res) {
	case TRUE:
		Py_RETURN_TRUE;
	case FALSE:
		Py_RETURN_FALSE;
	default:
		return mp_SetError(PyExc_IOError, res);
	}
}

static PyObject *
connection_fileno(ConnectionObject* self)
{
	if (self->handle == INVALID_HANDLE_VALUE) {
		PyErr_SetString(PyExc_IOError, "handle is invalid");
		return NULL;
	}
	return PyInt_FromLong((long)self->handle);
}

static PyObject *
connection_close(ConnectionObject *self)
{
	if (self->handle != INVALID_HANDLE_VALUE) {
		Py_BEGIN_ALLOW_THREADS
		CLOSE(self->handle);
		Py_END_ALLOW_THREADS
		self->handle = INVALID_HANDLE_VALUE;
	}

	Py_RETURN_NONE;
}

static PyObject *
connection_repr(ConnectionObject *self)
{
	static char *conn_type[] = {"read-only", "write-only", "read-write"};

	assert(self->flags >= 1 && self->flags <= 3);
	return FROM_FORMAT("<%s %s, handle %zd>", 
			   conn_type[self->flags - 1],
			   CONNECTION_NAME, (Py_ssize_t)self->handle);
}

/*
 * Getters and setters
 */

static PyObject *
connection_closed(ConnectionObject *self, void *closure)
{
	return PyBool_FromLong((long)(self->handle == INVALID_HANDLE_VALUE));
}

static PyObject *
connection_readable(ConnectionObject *self, void *closure)
{
	return PyBool_FromLong((long)(self->flags & READABLE));
}

static PyObject *
connection_writable(ConnectionObject *self, void *closure)
{
	return PyBool_FromLong((long)(self->flags & WRITABLE));
}

/*
 * Tables
 */

static PyMethodDef connection_methods[] = {
	{"send_bytes", (PyCFunction)connection_sendbytes, METH_VARARGS, 
	 "send the byte data from a readable buffer-like object"},
	{"recv_bytes", (PyCFunction)connection_recvbytes, METH_VARARGS, 
	 "receive byte data as a string"},
	{"recv_bytes_into",(PyCFunction)connection_recvbytes_into,METH_VARARGS,
	 "receive byte data into a writeable buffer-like object\n"
	 "returns the number of bytes read"},

	{"send", (PyCFunction)connection_send_obj, METH_O, 
	 "send a (picklable) object"},
	{"recv", (PyCFunction)connection_recv_obj, METH_NOARGS, 
	 "receive a (picklable) object"},

	{"poll", (PyCFunction)connection_poll, METH_VARARGS, 
	 "whether there is any input available to be read"},
	{"fileno", (PyCFunction)connection_fileno, METH_NOARGS,
	 "file descriptor or handle of the connection"},
	{"close", (PyCFunction)connection_close, METH_NOARGS,
	 "close the connection"},

	{NULL}  /* Sentinel */
};

static PyGetSetDef connection_getset[] = {
	{"closed", (getter)connection_closed, NULL, 
	 "True if the connection is closed", NULL},
	{"readable", (getter)connection_readable, NULL, 
	 "True if the connection is readable", NULL},
	{"writable", (getter)connection_writable, NULL, 
	 "True if the connection is writable", NULL},
	{NULL}
};

/*
 * Connection type
 */

PyDoc_STRVAR(connection_doc,
	     "Connection type whose constructor signature is\n\n"
	     "    Connection(handle, readable=True, writable=True).\n\n"
	     "The constructor does *not* duplicate the handle.");

PyTypeObject CONNECTION_TYPE = {
	PyVarObject_HEAD_INIT(NULL, 0)
	/* tp_name           */ "_multiprocessing." CONNECTION_NAME,
	/* tp_basicsize      */ sizeof(ConnectionObject),
	/* tp_itemsize       */ 0,
	/* tp_dealloc        */ (destructor)connection_dealloc,
	/* tp_print          */ 0,
	/* tp_getattr        */ 0,
	/* tp_setattr        */ 0,
	/* tp_compare        */ 0,
	/* tp_repr           */ (reprfunc)connection_repr,
	/* tp_as_number      */ 0,
	/* tp_as_sequence    */ 0,
	/* tp_as_mapping     */ 0,
	/* tp_hash           */ 0,
	/* tp_call           */ 0,
	/* tp_str            */ 0,
	/* tp_getattro       */ 0,
	/* tp_setattro       */ 0,
	/* tp_as_buffer      */ 0,
	/* tp_flags          */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | 
				Py_TPFLAGS_HAVE_WEAKREFS,
	/* tp_doc            */ connection_doc,
	/* tp_traverse       */ 0,
	/* tp_clear          */ 0,
	/* tp_richcompare    */ 0,
	/* tp_weaklistoffset */ offsetof(ConnectionObject, weakreflist),
	/* tp_iter           */ 0,
	/* tp_iternext       */ 0,
	/* tp_methods        */ connection_methods,
	/* tp_members        */ 0,
	/* tp_getset         */ connection_getset,
	/* tp_base           */ 0,
	/* tp_dict           */ 0,
	/* tp_descr_get      */ 0,
	/* tp_descr_set      */ 0,
	/* tp_dictoffset     */ 0,
	/* tp_init           */ 0,
	/* tp_alloc          */ 0,
	/* tp_new            */ connection_new,
};

#endif /* CONNECTION_H */
