#include "Python.h"

#define MAXSIZE 24
#define HEADSIZE 12

typedef struct {
    PyObject_HEAD
    char *buffer;
    char preview[MAXSIZE];
    Py_ssize_t buffer_size;
    Py_ssize_t string_size;
    Py_ssize_t counter;
} SpamObject;

static PyObject *
spam_dump(SpamObject *self, PyObject *args)
{
    FILE *file;
    const char *filename;

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

    file = fopen(filename, "w");
    if (file == NULL)
        return PyErr_SetFromErrno(PyExc_IOError);

    fwrite(self, sizeof(SpamObject), 1, file);
    if (fclose(file))
        return PyErr_SetFromErrno(PyExc_IOError);
    if (ferror(file)) {
        PyErr_SetString(PyExc_IOError, "unknown write failure occured");
        return NULL;
    }

    Py_RETURN_NONE;
}

static PyObject *
spam_put(SpamObject *self, PyObject *str)
{
    char *raw_str;
    Py_ssize_t size;

    if (!PyString_Check(str)) {
        PyErr_SetString(PyExc_ValueError, "need a string");
        return NULL;
    }
    if (PyString_AsStringAndSize(str, &raw_str, &size) < 0)
        return NULL;

    if (size > self->buffer_size) {
        PyMem_Resize(self->buffer, char, size);
        self->buffer_size = size;
    }
    memcpy(self->buffer, raw_str, size);
    self->string_size = size;
    /* set the preview */
    strncpy(self->preview, raw_str, MAXSIZE);

    return PyInt_FromSsize_t(size);
}

static PyObject *
spam_get(SpamObject *self)
{
    return PyString_FromStringAndSize(self->buffer, self->string_size);
}

static PyObject *
spam_incr(SpamObject *self)
{
    self->counter += 1;
    return PyInt_FromLong((long) self->counter);
}

static PyMethodDef spam_methods[] = {
    {"dump",      (PyCFunction) spam_dump, METH_VARARGS, NULL},
    {"put", (PyCFunction) spam_put, METH_O, NULL},
    {"get", (PyCFunction) spam_get, METH_NOARGS, NULL},
    {NULL, NULL, 0, NULL}   /* sentinel */
};

static PyObject *
spam_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    SpamObject *self;

    assert(type != NULL && type->tp_alloc != NULL);
    self = (SpamObject *)type->tp_alloc(type, 0);

    if (self == NULL)
        return NULL;

    self->buffer = PyMem_New(char, 256);
    self->buffer_size = 256;
    self->counter = 0;

    return (PyObject *)self;
}

static void
spam_dealloc(SpamObject *self)
{
    PyMem_Del(self->buffer);
    self->ob_type->tp_free((PyObject *)self);
}

static PyTypeObject Spam_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                              /*ob_size*/
    "spam.Spam",                    /*tp_name*/
    sizeof(SpamObject),             /*tp_basicsize*/
    0,                              /*tp_itemsize*/
    (destructor)spam_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*/
    0,                              /*tp_getattro*/
    0,                              /*tp_setattro*/
    0,                              /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,             /*tp_flags*/
    0,                              /*tp_doc*/
    0,                              /*tp_traverse*/
    0,                              /*tp_clear*/
    0,                              /*tp_richcompare*/
    0,                              /*tp_weaklistoffset*/
    0,                              /*tp_iter*/
    0,                              /*tp_iternext*/
    spam_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*/
    spam_new,                       /*tp_new*/
    0,                              /*tp_free*/
    0,                              /*tp_is_gc*/
};

PyMODINIT_FUNC
initspam(void)
{
    PyObject *m;

    if (PyType_Ready(&Spam_Type) < 0)
        return;

    m = Py_InitModule("spam", NULL);
    if (m == NULL)
        return;
    Py_INCREF(&Spam_Type);
    PyModule_AddObject(m, "Spam", (PyObject *)&Spam_Type);
}
