/* Octree object
 */

#include "Python.h"

typedef struct {
    PyObject_HEAD
    double x, y, z;
    PyObject *object;
    PyObject *render;
    int rendered;   // indicate the object has been rendered in a given pass
} RenderableObject;

typedef struct _octree {
	PyObject_HEAD
	struct _octree *nodes[8];
    PyObject *objects;
    double x, y, z;
    double size, half_size;
    int MAX_OBJECTS_PER_BUCKET;
} OctreeObject;

static int _insertObject(OctreeObject *self, double x, double y, double z,
        PyObject *object);
static int _insertRenderable(OctreeObject *self, double x, double y,
        double z, RenderableObject *renderable);

/*
 * Octree renderable objects
 */

staticforward PyTypeObject Renderable_Type;

#define RenderableObject_Check(v)	((v)->ob_type == &Renderable_Type)

static RenderableObject *_newRenderableObject(double x, double y, double z,
        PyObject *object) {
    RenderableObject *self;
    PyObject *render;

    if (!(render = PyObject_GetAttrString(object, "render")))
        return NULL;
	if (!(self = PyObject_New(RenderableObject, &Renderable_Type)))
        return NULL;
    self->x = x;
    self->y = y;
    self->z = z;
    self->object = object;
    self->render = render;
    self->rendered = 0;

    // we've got a ref to these now
    Py_INCREF(object);
    Py_INCREF(render);

    return self;
}

/* Renderable methods */
static void Renderable_dealloc(RenderableObject *self) {
    Py_DECREF(self->render);
    Py_DECREF(self->object);
	PyObject_Del(self);
}

statichere PyTypeObject Renderable_Type = {
	PyObject_HEAD_INIT(NULL)
	0,			/*ob_size*/
	"Renderable",			/*tp_name*/
	sizeof(RenderableObject),	/*tp_basicsize*/
	0,			/*tp_itemsize*/
	/* methods */
	(destructor)Renderable_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*/
};



/*
 * Octree storage objects
 */

staticforward PyTypeObject Octree_Type;

#define OctreeObject_Check(v)	((v)->ob_type == &Octree_Type)

static OctreeObject *_newOctreeObject(double x, double y, double z,
        double size) {
	OctreeObject *self;
    PyObject *objects;
    int i;

    if (!(objects = PyList_New(0))) return NULL;
	if (!(self = PyObject_New(OctreeObject, &Octree_Type))) return NULL;
    self->x = x;
    self->y = y;
    self->z = z;
    self->size = size;
    self->half_size = size/2;
    self->MAX_OBJECTS_PER_BUCKET = 64;

    self->objects = objects;
    for (i=0; i<8; i++) {
        self->nodes[i] = NULL;
    }
	return self;
}

static OctreeObject *newOctreeObject(PyObject *args) {
    double x, y, z, size;

	if (!PyArg_ParseTuple(args, "(ddd)d:newOctreeObject", &x, &y, &z, &size))
		return NULL;
    return _newOctreeObject(x, y, z, size);
}

/* Octree methods */
static void Octree_dealloc(OctreeObject *self) {
    int i;
    // it's possible that objects may be invalid when we're doing this
    if (self->objects) {Py_DECREF(self->objects);}
    for (i=0; i<8; i++) {
        if (self->nodes[i] != NULL) {Py_DECREF(self->nodes[i]);}
    }
	PyObject_Del(self);
}

/*
Determine if the given point lies inside this node
*/
static int _isPointInside(OctreeObject *self, double x, double y, double z) {
    double s = self->half_size;

    if (x < self->x - s) return 0;
    if (x > self->x + s) return 0;
    if (y < self->y - s) return 0;
    if (y > self->y + s) return 0;
    if (z < self->z - s) return 0;
    if (z > self->z + s) return 0;
    return 1;
}

/*
Make eight new nodes and divvy up the objects contents to each of
the new nodes.
*/
#define NEWNODE(N, X, Y, Z) { \
    if (!(self->nodes[N] = _newOctreeObject(X, Y, Z, hs))) return 0; \
}
static int _subdivide(OctreeObject *self) {
    RenderableObject *entry;
    double hs=self->half_size, qs=self->half_size/2;
    double cx=self->x, cy=self->y, cz=self->z;
    int i, n, length;

    // make the eight new nodes
    // ownership of the new reference is transferred from this function
    // to the nodes array.
    NEWNODE(0, cx-qs, cy-qs, cz-qs);
    NEWNODE(1, cx-qs, cy-qs, cz+qs);
    NEWNODE(2, cx-qs, cy+qs, cz-qs);
    NEWNODE(3, cx-qs, cy+qs, cz+qs);
    NEWNODE(4, cx+qs, cy-qs, cz-qs);
    NEWNODE(5, cx+qs, cy-qs, cz+qs);
    NEWNODE(6, cx+qs, cy+qs, cz-qs);
    NEWNODE(7, cx+qs, cy+qs, cz+qs);

    // re-insert now that we have sub-nodes
    length = PyList_Size(self->objects);
    for (n=0; n < length; n++) {
        // this is a borrowe ref - don't decref it
        if (!(entry = (RenderableObject *)PyList_GetItem(self->objects, n)))
            return 0;
        // figure which sub-node to put this entry into
        for (i=0; i<8; i++) {
            if (_isPointInside(self->nodes[i], entry->x, entry->y,
                        entry->z)) {
                if (!_insertRenderable(self->nodes[i], entry->x, entry->y,
                    entry->z, entry)) return 0;
                break;
            }
        }
    }

    // and nuke the old objects list
    Py_DECREF(self->objects);
    self->objects = NULL;
    if (!(self->objects = PyList_New(0))) return 0;
    return 1;
}

/*
Figure where the point lies in the octree and insert the object
into the objects there.
*/
static int _insertObject(OctreeObject *self, double x, double y, double z,
        PyObject *object) {
    RenderableObject *renderable;
    int res;

    // make the new object and insert it
    if (!(renderable = _newRenderableObject(x, y, z, object))) return 0;
    res = _insertRenderable(self, x, y, z, renderable);

    // we're done with our reference
    Py_DECREF(renderable);
    return res;
}
static int _insertRenderable(OctreeObject *self, double x, double y,
        double z, RenderableObject *renderable) {
    int i;

    // make sure the octree can take this node
    if (!_isPointInside(self, x, y, z)) {
        PyErr_SetString(PyExc_ValueError, "Point lies outside octree");
    }

    // add to a subnode of this node?
    for (i=0; i<8; i++) {
        if (self->nodes[i] == NULL) continue;
        if (_isPointInside(self->nodes[i], x, y, z)) {
            return _insertRenderable(self->nodes[i], x, y, z, renderable);
        }
    }

    // add to this node's bucket
    if (PyList_Append(self->objects, (PyObject *)renderable) == -1)
        return 0;

    // now check to see if we're full
    if (PyList_Size(self->objects) > self->MAX_OBJECTS_PER_BUCKET) {
        return _subdivide(self);
    }

	return 1;
}
static PyObject *insertObject(OctreeObject *self, PyObject *args) {
    PyObject *object=NULL;
    double x, y, z;

	if (!PyArg_ParseTuple(args, "(ddd)O:insertObject", &x, &y, &z, &object))
		return NULL;

    if (!_insertObject(self, x, y, z, object)) return NULL;

	Py_INCREF(Py_None);
	return Py_None;
}


/*
Insert a polygon - defined by a sequence of vertex 3-tuples. The polygon
object must also have a render method. A reference to this method is
obtained when the object is added to the tree, so changing it on the object
after adding will have no effect.
*/
static PyObject *insertPolygon(OctreeObject *self, PyObject *args) {
    PyObject *poly, *vertexes, *vertex;
    double x, y, z;
    int i, length;

	if (!PyArg_ParseTuple(args, "O:insertPolygon", &poly)) return NULL;

    // get the vertexes list
    if (!(vertexes = PyObject_GetAttrString(poly, "vertexes")))
        return NULL;

    // now add each of the three vertexes
    length = PySequence_Size(vertexes);
    for (i=0; i<length; i++) {
        if (!(vertex = PySequence_GetItem(vertexes, i))) goto iP_error;
        x = PyFloat_AsDouble(PyTuple_GetItem(vertex, 0));
        if (PyErr_Occurred()) goto iP_error;
        y = PyFloat_AsDouble(PyTuple_GetItem(vertex, 1));
        if (PyErr_Occurred()) goto iP_error;
        z = PyFloat_AsDouble(PyTuple_GetItem(vertex, 2));
        if (PyErr_Occurred()) goto iP_error;
        if (!(_insertObject(self, x, y, z, poly))) goto iP_error;
    }

    Py_INCREF(Py_None);
    return Py_None;
iP_error:
    Py_DECREF(vertexes);
    return NULL;
}

/*
Clear the "rendered" flag from all objects
*/
static PyObject *clearRenderFlag(OctreeObject *self, PyObject *args) {
    PyObject *flag;
    RenderableObject *entry;
    int n, length;

    // clear sub-nodes
    for (n=0; n < 8; n++) {
        if (self->nodes[n] == NULL) continue;
        clearRenderFlag(self->nodes[n], NULL);
    }

    // clear renderables
    length = PyList_Size(self->objects);
    for (n=0; n < length; n++) {
        if (!(entry = (RenderableObject *)PyList_GetItem(self->objects, n)))
            return 0;
        flag = PyInt_FromLong(0);
        PyObject_SetAttrString(entry->object, "rendered", flag);
        Py_DECREF(flag);
    }

    Py_INCREF(Py_None);
    return Py_None;
}


/*
Render all nodes, clipped to the given frustum
*/
static PyObject *renderClippedToFrustum(OctreeObject *self, PyObject *args) {
    PyObject *frustum, *res, *flag;
    RenderableObject *entry;
    int n, length, drawn=0, done=0;

	if (!PyArg_ParseTuple(args, "O:renderClippedToFrustum", &frustum))
        return NULL;

    if (!(res = PyObject_CallMethod(frustum, "isCubeInside", "dddd", self->x,
        self->y, self->z, self->half_size))) return NULL;
    if (!PyInt_AsLong(res)) return res;
    Py_DECREF(res);

    // do sub-nodes
    for (n=0; n < 8; n++) {
        if (self->nodes[n] == NULL) continue;
        if (!(res = renderClippedToFrustum(self->nodes[n], args)))
            return NULL;
        drawn = drawn + PyInt_AsLong(res);
        Py_DECREF(res);
        done=1;
    }

    // do renderables
    length = PyList_Size(self->objects);
    for (n=0; n < length; n++) {
        if (!(entry = (RenderableObject *)PyList_GetItem(self->objects, n)))
            return 0;
        // see if this is done already
        flag = PyObject_GetAttrString(entry->object, "rendered");
        done = PyInt_AsLong(flag);
        Py_DECREF(flag);
        if (done) continue;

        // render
        if (!(res = PyObject_CallFunction(entry->render, NULL))) return NULL;
        Py_DECREF(res);

        // indicate that this one's done
        flag = PyInt_FromLong(1);
        PyObject_SetAttrString(entry->object, "rendered", flag);
        Py_DECREF(flag);
        drawn = drawn + 1;
    }

    return PyInt_FromLong(drawn);
}


/*
Figure a summary about this octree structure
*/
static int _countNodes(OctreeObject *self) {
    int n, count=1;

    // do sub-nodes
    for (n=0; n < 8; n++) {
        if (self->nodes[n] == NULL) continue;
        count += _countNodes(self->nodes[n]);
    }
    return count;
}

static int _countEmpties(OctreeObject *self) {
    int n, count=0;
    
    if (PyList_Size(self->objects) == 0) count=1;

    // do sub-nodes
    for (n=0; n < 8; n++) {
        if (self->nodes[n] == NULL) continue;
        count += _countEmpties(self->nodes[n]);
    }
    return count;
}

static int _countObjects(OctreeObject *self) {
    int n, count;
    
    count = PyList_Size(self->objects);

    // do sub-nodes
    for (n=0; n < 8; n++) {
        if (self->nodes[n] == NULL) continue;
        count += _countObjects(self->nodes[n]);
    }
    return count;
}

static PyObject *summary(OctreeObject *self, PyObject *args) {
    printf("Nodes: %d\nObjects: %d\nEmpty nodes: %d\n",
            _countNodes(self), _countObjects(self), _countEmpties(self));
    Py_INCREF(Py_None);
    return Py_None;
}

static PyMethodDef Octree_methods[] = {
	{"insertObject", (PyCFunction)insertObject, METH_VARARGS},
	{"insertPolygon", (PyCFunction)insertPolygon, METH_VARARGS},
	{"clearRenderFlag", (PyCFunction)clearRenderFlag, METH_VARARGS},
	{"renderClippedToFrustum", (PyCFunction)renderClippedToFrustum,
        METH_VARARGS},
	{"summary", (PyCFunction)summary, METH_VARARGS},
	{NULL,		NULL}		/* sentinel */
};

static PyObject *Octree_getattr(OctreeObject *self, char *name) {
	return Py_FindMethod(Octree_methods, (PyObject *)self, name);
}

statichere PyTypeObject Octree_Type = {
	PyObject_HEAD_INIT(NULL)
	0,			/*ob_size*/
	"Octree",			/*tp_name*/
	sizeof(OctreeObject),	/*tp_basicsize*/
	0,			/*tp_itemsize*/
	/* methods */
	(destructor)Octree_dealloc, /*tp_dealloc*/
	0,			/*tp_print*/
	(getattrfunc)Octree_getattr, /*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*/
};
/* --------------------------------------------------------------------- */

/* Function of no arguments returning new Octree object */
static PyObject *octree_new(PyObject *self, PyObject *args) {
	return (PyObject *)newOctreeObject(args);
}


/* List of functions defined in the module */
static PyMethodDef octree_methods[] = {
	{"Octree",		octree_new,		METH_VARARGS},
	{NULL,		NULL}		/* sentinel */
};


DL_EXPORT(void) init_octree(void) {
	Octree_Type.ob_type = &PyType_Type;
	Py_InitModule("_octree", octree_methods);
}
