#include "rpmsystem-py.h"

#include <rpm/rpmtypes.h>
#include <rpm/rpmfi.h>

#include "header-py.h"
#include "rpmfd-py.h"
#include "rpmfiles-py.h"
#include "rpmarchive-py.h"
#include "rpmstrpool-py.h"

struct rpmarchiveObject_s {
    PyObject_HEAD
    PyObject *md_dict;
    rpmfi archive;
    rpmfiles files;
};

static void rpmarchive_dealloc(rpmarchiveObject * s)
{
    rpmfilesFree(s->files);
    rpmfiArchiveClose(s->archive);
    rpmfiFree(s->archive);
    Py_TYPE(s)->tp_free((PyObject *)s);
}

static PyObject *rpmarchive_error(int rc)
{
    PyErr_SetObject(PyExc_IOError,
		    Py_BuildValue("(is)", rc, rpmfileStrerror(rc)));
    return NULL;
}

static PyObject *rpmarchive_closed(void)
{
    PyErr_SetString(PyExc_IOError, "I/O operation on closed archive");
    return NULL;
}

static PyObject *rpmarchive_tell(rpmarchiveObject *s)
{
    return PyLong_FromLongLong(rpmfiArchiveTell(s->archive));
}

static PyObject *rpmarchive_close(rpmarchiveObject *s)
{
    if (s->archive) {
	int rc = rpmfiArchiveClose(s->archive);
	s->archive = rpmfiFree(s->archive);
	if (rc)
	    return rpmarchive_error(rc);
    }
    Py_RETURN_NONE;
}

static PyObject *rpmarchive_has_content(rpmarchiveObject *s)
{
    return PyLong_FromLong(rpmfiArchiveHasContent(s->archive));
}

static PyObject *rpmarchive_read(rpmarchiveObject *s,
				 PyObject *args, PyObject *kwds)
{
    char *kwlist[] = { "size", NULL };
    char buf[BUFSIZ];
    ssize_t chunksize = sizeof(buf);
    ssize_t left = -1;
    ssize_t nb = 0;
    PyObject *res = NULL;
    
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|l", kwlist, &left))
	return NULL;

    if (s->archive == NULL)
	return rpmarchive_closed();

    /* ConcatAndDel() doesn't work on NULL string, meh */
    res = PyBytes_FromStringAndSize(NULL, 0);
    do {
	if (left >= 0 && left < chunksize)
	    chunksize = left;

	Py_BEGIN_ALLOW_THREADS 
	nb = rpmfiArchiveRead(s->archive, buf, chunksize);
	Py_END_ALLOW_THREADS 

	if (nb > 0) {
	    PyObject *tmp = PyBytes_FromStringAndSize(buf, nb);
	    PyBytes_ConcatAndDel(&res, tmp);
	    left -= nb;
	}
    } while (nb > 0);

    if (nb < 0) {
	Py_XDECREF(res);
	return rpmarchive_error(nb);
    } else {
	return res;
    }
}

static PyObject *rpmarchive_write(rpmarchiveObject *s,
				 PyObject *args, PyObject *kwds)
{
    const char *buf = NULL;
    ssize_t size = 0;
    char *kwlist[] = { "buffer", NULL };
    ssize_t rc = 0;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "s#", kwlist, &buf, &size)) {
	return NULL;
    }

    if (s->archive == NULL)
	return rpmarchive_closed();

    Py_BEGIN_ALLOW_THREADS 
    rc = rpmfiArchiveWrite(s->archive, buf, size);
    Py_END_ALLOW_THREADS 

    if (rc < 0)
	return rpmarchive_error(rc);
    else
	return Py_BuildValue("n", rc);
}

static PyObject *rpmarchive_readto(rpmarchiveObject *s,
				 PyObject *args, PyObject *kwds)
{
    rpmfdObject *fdo = NULL;
    int nodigest = 0;
    int rc;
    char *kwlist[] = { "fd", "nodigest", NULL };

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|i", kwlist,
				 rpmfdFromPyObject, &fdo, &nodigest)) {
	return NULL;
    }

    if (s->archive == NULL)
	return rpmarchive_closed();

    Py_BEGIN_ALLOW_THREADS
    rc = rpmfiArchiveReadToFile(s->archive, rpmfdGetFd(fdo), nodigest);
    Py_END_ALLOW_THREADS

    if (rc)
	return rpmarchive_error(rc);
    
    Py_RETURN_NONE;
}

static PyObject *rpmarchive_writeto(rpmarchiveObject *s,
				 PyObject *args, PyObject *kwds)
{
    rpmfdObject *fdo = NULL;
    int rc;
    char *kwlist[] = { "fd", NULL };

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&", kwlist,
				 rpmfdFromPyObject, &fdo)) {
	return NULL;
    }

    if (s->archive == NULL)
	return rpmarchive_closed();

    Py_BEGIN_ALLOW_THREADS
    rc = rpmfiArchiveWriteFile(s->archive, rpmfdGetFd(fdo));
    Py_END_ALLOW_THREADS

    if (rc)
	return rpmarchive_error(rc);

    Py_RETURN_NONE;
}

static struct PyMethodDef rpmarchive_methods[] = {
    { "tell",	(PyCFunction)rpmarchive_tell,		METH_NOARGS,
      "archive.tell() -- Return current position in archive." },
    { "close",	(PyCFunction)rpmarchive_close,		METH_NOARGS,
      "archive.close() -- Close archive and do final consistency checks."},
    { "read",	(PyCFunction)rpmarchive_read,	METH_VARARGS|METH_KEYWORDS,
      "archive.read(size=None) -- Read next size bytes from current file.\n\n"
      "Returns bytes\n"},
    { "write",	(PyCFunction)rpmarchive_write,	METH_VARARGS|METH_KEYWORDS,
      "archive.write(buffer) -- Write buffer to current file." },
    { "readto",	(PyCFunction)rpmarchive_readto,	METH_VARARGS|METH_KEYWORDS,
      "archive.readto(fd, nodigest=None) -- Read content of fd\n"
      "and write as content of the current file to archive." },
    { "writeto", (PyCFunction)rpmarchive_writeto,METH_VARARGS|METH_KEYWORDS,
      "archive.writeto(fd) -- Write content of current file in archive\n to fd." },
    { "hascontent", (PyCFunction)rpmarchive_has_content, METH_NOARGS,
      "archive.hascontent() -- Return if current file has a content.\n\n"
      "Returns false for non regular and all but one of hardlinked files."},
    { NULL, NULL, 0, NULL }
};

static char rpmarchive_doc[] =
"Gives access to the payload of an rpm package.\n\n"
"Is returned by .archive() method of an rpm.files instance.\n"
"All methods can raise an IOError exception.";

static PyObject *rpmarchive_iternext(rpmarchiveObject *s)
{
    PyObject *next = NULL;
    int fx = rpmfiNext(s->archive);

    if (fx >= 0) {
	next = rpmfile_Wrap(s->files, fx);
    } else if (fx < -1) {
	next = rpmarchive_error(fx);
    } else {
	/* end of iteration, nothing to do */
    }
    
    return next;
}

PyTypeObject rpmarchive_Type = {
	PyVarObject_HEAD_INIT(&PyType_Type, 0)
	"rpm.archive",			/* tp_name */
	sizeof(rpmarchiveObject),		/* tp_basicsize */
	0,				/* tp_itemsize */
	(destructor) rpmarchive_dealloc,	/* tp_dealloc */
	0,				/* tp_print */
	0,				/* tp_getattr */
	0,				/* tp_setattr */
	0,				/* tp_compare */
	0,				/* tp_repr */
	0,				/* tp_as_number */
	0,		/* tp_as_sequence */
	0,		/* tp_as_mapping */
	0,				/* tp_hash */
	0,				/* tp_call */
	0,				/* tp_str */
	PyObject_GenericGetAttr,	/* tp_getattro */
	PyObject_GenericSetAttr,	/* tp_setattro */
	0,				/* tp_as_buffer */
	Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,	/* tp_flags */
	rpmarchive_doc,			/* tp_doc */
	0,				/* tp_traverse */
	0,				/* tp_clear */
	0,				/* tp_richcompare */
	0,				/* tp_weaklistoffset */
	PyObject_SelfIter,		/* tp_iter */
	(iternextfunc) rpmarchive_iternext,		/* tp_iternext */
	rpmarchive_methods,		/* tp_methods */
	0,				/* tp_members */
	0,				/* tp_getset */
	0,				/* 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 */
	0,				/* tp_free */
	0,				/* tp_is_gc */
};

PyObject * rpmarchive_Wrap(PyTypeObject *subtype,
			   rpmfiles files, rpmfi archive)
{
    rpmarchiveObject *s = (rpmarchiveObject *)subtype->tp_alloc(subtype, 0);
    if (s == NULL) return NULL;

    s->files = rpmfilesLink(files);
    s->archive = archive;

    return (PyObject *) s;
}

