/*
 * raytracermodule.cc: Python module
 *
 * This file is part of Pyrit Ray Tracer.
 *
 * Copyright 2006, 2007, 2008  Radek Brich
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <Python.h>

#include <vector>
#include "raytracer.h"
#include "scene.h"
#include "octree.h"

//=========================== Light Source Object ===========================

typedef struct {
	PyObject_HEAD
	Light *light;
} LightObject;

static PyObject *Light_Constructor(PyObject* self, PyObject* args, PyObject *kwd);
static void Light_Destructor(PyObject* self);
static PyObject *Light_Getattr(PyObject *self, char *name);
static PyObject *Light_castshadows(PyObject* self, PyObject* args);

static PyTypeObject LightType = {
	PyObject_HEAD_INIT(NULL)
	0,				/*ob_size*/
	"Light",			/*tp_name*/
	sizeof(LightObject),		/*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	Light_Destructor,		/*tp_dealloc*/
	0,				/*tp_print*/
	Light_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 */
};

static PyMethodDef LightMethods[] = {
	{"castshadows", (PyCFunction)Light_castshadows, METH_VARARGS, "Enable or disable shadows from this light."},
	{NULL, NULL}
};

static PyObject* Light_Constructor(PyObject* self, PyObject* args, PyObject *kwd)
{
	LightObject *v;
	static char *kwdlist[] = {"position", "colour", NULL};
	PyObject *TPos, *TCol = NULL;
	Float px, py, pz;
	Float cr = 1.0, cg = 1.0, cb = 1.0;

	if (!PyArg_ParseTupleAndKeywords(args, kwd, "O!|O!", kwdlist,
		&PyTuple_Type, &TPos, &PyTuple_Type, &TCol))
		return NULL;

	if (!PyArg_ParseTuple(TPos, "fff", &px, &py, &pz))
		return NULL;
	if (TCol && !PyArg_ParseTuple(TCol, "fff", &cr, &cg, &cb))
		return NULL;

	v = PyObject_New(LightObject, &LightType);
	v->light = new Light(Vector3(px, py, pz), Colour(cr, cg, cb));
	return (PyObject*)v;
}

static void Light_Destructor(PyObject* self)
{
	delete ((LightObject *)self)->light;
	PyObject_Del(self);
}

static PyObject *Light_Getattr(PyObject *self, char *name)
{
	return Py_FindMethod(LightMethods, self, name);
}

static PyObject *Light_castshadows(PyObject* self, PyObject* args)
{
	int shadows = 1;

	if (!PyArg_ParseTuple(args, "i", &shadows))
		return NULL;

	((LightObject *)self)->light->castShadows(shadows);

	Py_INCREF(Py_None);
	return Py_None;
}

//=========================== Camera Object ===========================

typedef struct {
	PyObject_HEAD
	Camera *camera;
} CameraObject;

static PyObject *Camera_Constructor(PyObject* self, PyObject* args, PyObject *kwd);
static void Camera_Destructor(PyObject* self);
static PyObject *Camera_Getattr(PyObject *self, char *name);
static PyObject *Camera_setEye(PyObject* self, PyObject* args);
static PyObject *Camera_rotate(PyObject* self, PyObject* args);

static PyTypeObject CameraType = {
	PyObject_HEAD_INIT(NULL)
	0,				/*ob_size*/
	"Camera",			/*tp_name*/
	sizeof(CameraObject),		/*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	Camera_Destructor,		/*tp_dealloc*/
	0,				/*tp_print*/
	Camera_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 */
};

static PyMethodDef CameraMethods[] = {
	{"setEye", (PyCFunction)Camera_setEye, METH_VARARGS, "Set eye of the camera."},
	{"rotate", (PyCFunction)Camera_rotate, METH_VARARGS, "Rotate camera with a quaternion."},
	{NULL, NULL}
};

static PyObject* Camera_Constructor(PyObject* self, PyObject* args, PyObject *kwd)
{
	CameraObject *v;
	static char *kwdlist[] = {"eye", "p", "u", "v", NULL};
	PyObject *TEye = NULL, *Tp = NULL, *Tu = NULL, *Tv = NULL;
	Float ex=0.0,  ey=0.0, ez=10.0;
	Float px=0.0,  py=0.0, pz=-1.0;
	Float ux=-1.0, uy=0.0, uz=0.0;
	Float vx=0.0,  vy=1.0, vz=0.0;

	if (!PyArg_ParseTupleAndKeywords(args, kwd, "|O!O!O!O!", kwdlist,
		&PyTuple_Type, &TEye, &PyTuple_Type, &Tp, &PyTuple_Type, &Tu, &PyTuple_Type, &Tv))
		return NULL;

	if (TEye)
		if (!PyArg_ParseTuple(TEye, "fff", &ex, &ey, &ez))
			return NULL;

	if (Tp)
		if (!PyArg_ParseTuple(Tp, "fff", &px, &py, &pz))
			return NULL;

	if (Tu)
		if (!PyArg_ParseTuple(Tu, "fff", &ux, &uy, &uz))
			return NULL;

	if (Tv)
		if (!PyArg_ParseTuple(Tv, "fff", &vx, &vy, &vz))
			return NULL;

	v = PyObject_New(CameraObject, &CameraType);
	v->camera = new Camera(Vector3(ex, ey, ez),
		Vector3(px, py, pz), Vector3(ux, uy, uz), Vector3(vx, vy, vz));
	return (PyObject*)v;
}

static void Camera_Destructor(PyObject* self)
{
	delete ((CameraObject *)self)->camera;
	PyObject_Del(self);
}

static PyObject *Camera_Getattr(PyObject *self, char *name)
{
	return Py_FindMethod(CameraMethods, self, name);
}


static PyObject *Camera_setEye(PyObject* self, PyObject* args)
{
	PyObject *TEye = NULL;
	Float ex=0.0,  ey=0.0, ez=10.0;

	if (!PyArg_ParseTuple(args, "O!", &PyTuple_Type, &TEye))
		return NULL;

	if (TEye)
		if (!PyArg_ParseTuple(TEye, "fff", &ex, &ey, &ez))
			return NULL;

	((CameraObject *)self)->camera->setEye(Vector3(ex, ey, ez));

	Py_INCREF(Py_None);
	return Py_None;
}

static PyObject *Camera_rotate(PyObject* self, PyObject* args)
{
	PyObject *Tq = NULL;
	Float qa, qb, qc, qd;

	if (!PyArg_ParseTuple(args, "O!", &PyTuple_Type, &Tq))
		return NULL;

	if (!PyArg_ParseTuple(Tq, "ffff", &qa, &qb, &qc, &qd))
		return NULL;

	((CameraObject *)self)->camera->rotate(Quaternion(qa, qb, qc, qd).normalize());

	Py_INCREF(Py_None);
	return Py_None;
}


//=========================== Material Object ===========================

typedef struct {
	PyObject_HEAD
	Material *material;
} MaterialObject;

static PyObject *Material_Constructor(PyObject* self, PyObject* args, PyObject *kwd);
static void Material_Destructor(PyObject* self);
static PyObject *Material_Getattr(PyObject *self, char *name);
static PyObject *Material_setPhong(PyObject* self, PyObject* args);
static PyObject *Material_setReflectivity(PyObject* self, PyObject* args);
static PyObject *Material_setTransmissivity(PyObject* self, PyObject* args);

static PyTypeObject MaterialType = {
	PyObject_HEAD_INIT(NULL)
	0,				/*ob_size*/
	"Material",			/*tp_name*/
	sizeof(MaterialObject),		/*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	Material_Destructor,		/*tp_dealloc*/
	0,				/*tp_print*/
	Material_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 */
};

static PyMethodDef MaterialMethods[] = {
	{"setPhong", (PyCFunction)Material_setPhong, METH_VARARGS, "Set ambient, diffuse, specular and shininess Phong model constants."},
	{"setReflectivity", (PyCFunction)Material_setReflectivity, METH_VARARGS, "Set reflectivity."},
	{"setTransmissivity", (PyCFunction)Material_setTransmissivity, METH_VARARGS, "Set transmissivity and refraction index."},
	{NULL, NULL}
};

static PyObject* Material_Constructor(PyObject* self, PyObject* args, PyObject *kwd)
{
	MaterialObject *v;
	static char *kwdlist[] = {"colour", NULL};
	PyObject *TCol = NULL;
	Float cr=1.0, cg=1.0, cb=1.0;

	if (!PyArg_ParseTupleAndKeywords(args, kwd, "|O!", kwdlist,
		&PyTuple_Type, &TCol))
		return NULL;

	if (TCol)
		if (!PyArg_ParseTuple(TCol, "fff", &cr, &cg, &cb))
			return NULL;

	v = PyObject_New(MaterialObject, &MaterialType);
	v->material = new Material(Colour(cr, cg, cb));
	return (PyObject*)v;
}

static void Material_Destructor(PyObject* self)
{
	delete ((MaterialObject *)self)->material;
	PyObject_Del(self);
}

static PyObject *Material_Getattr(PyObject *self, char *name)
{
	return Py_FindMethod(MaterialMethods, self, name);
}

static PyObject *Material_setPhong(PyObject* self, PyObject* args)
{
	Float amb, dif, spec, shin = 0.5;

	if (!PyArg_ParseTuple(args, "fff|f", &amb, &dif, &spec, &shin))
		return NULL;

	((MaterialObject *)self)->material->setPhong(amb, dif, spec, shin);

	Py_INCREF(Py_None);
	return Py_None;
}

static PyObject *Material_setReflectivity(PyObject* self, PyObject* args)
{
	Float refl;

	if (!PyArg_ParseTuple(args, "f", &refl))
		return NULL;

	((MaterialObject *)self)->material->setReflectivity(refl);

	Py_INCREF(Py_None);
	return Py_None;
}

static PyObject *Material_setTransmissivity(PyObject* self, PyObject* args)
{
	Float trans, rindex = 1.3;

	if (!PyArg_ParseTuple(args, "f|f", &trans, &rindex))
		return NULL;

	((MaterialObject *)self)->material->setTransmissivity(trans, rindex);

	Py_INCREF(Py_None);
	return Py_None;
}

//=========================== NormalVertex Object ===========================

typedef struct {
	PyObject_HEAD
	NormalVertex *nvertex;
} NormalVertexObject;

static PyObject *NormalVertex_Constructor(PyObject* self, PyObject* args, PyObject *kwd);
static void NormalVertex_Destructor(PyObject* self);
static PyObject *NormalVertex_Getattr(PyObject *self, char *name);
static PyObject *NormalVertex_setNormal(PyObject* self, PyObject* args);

static PyTypeObject NormalVertexType = {
	PyObject_HEAD_INIT(NULL)
	0,				/*ob_size*/
	"NormalVertex",			/*tp_name*/
	sizeof(NormalVertexObject),		/*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	NormalVertex_Destructor,		/*tp_dealloc*/
	0,				/*tp_print*/
	NormalVertex_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 */
};

static PyMethodDef NormalVertexMethods[] = {
	{"setNormal", (PyCFunction)NormalVertex_setNormal, METH_VARARGS, "Set normal of this vertex."},
	{NULL, NULL}
};

static PyObject* NormalVertex_Constructor(PyObject* self, PyObject* args, PyObject *kwd)
{
	NormalVertexObject *v;
	static char *kwdlist[] = {"vector", "normal", NULL};
	PyObject *TVer = NULL;
	PyObject *TNor = NULL;
	Float vx, vy, vz, nx=0, ny=0, nz=0;

	if (!PyArg_ParseTupleAndKeywords(args, kwd, "O!|O!", kwdlist,
		&PyTuple_Type, &TVer, &PyTuple_Type, &TNor))
		return NULL;

	if (!PyArg_ParseTuple(TVer, "fff", &vx, &vy, &vz))
		return NULL;

	if (TNor)
		if (!PyArg_ParseTuple(TNor, "fff", &nx, &ny, &nz))
			return NULL;

	v = PyObject_New(NormalVertexObject, &NormalVertexType);
	v->nvertex = new NormalVertex(Vector3(vx, vy, vz), Vector3(nx, ny, nz));
	return (PyObject*)v;
}

static void NormalVertex_Destructor(PyObject* self)
{
	delete ((NormalVertexObject *)self)->nvertex;
	PyObject_Del(self);
}

static PyObject *NormalVertex_Getattr(PyObject *self, char *name)
{
	return Py_FindMethod(NormalVertexMethods, self, name);
}

static PyObject *NormalVertex_setNormal(PyObject* self, PyObject* args)
{
	PyObject *TNor = NULL;
	Float nx, ny, nz;

	if (!PyArg_ParseTuple(args, "O!", &PyTuple_Type, &TNor))
		return NULL;

	if (!PyArg_ParseTuple(TNor, "fff", &nx, &ny, &nz))
		return NULL;

	((NormalVertexObject *)self)->nvertex->setNormal(Vector3(nx,ny,nz).normalize());

	Py_INCREF(Py_None);
	return Py_None;
}

//=========================== Sphere Object ===========================

typedef struct {
	PyObject_HEAD
	Sphere *shape;
} SphereObject;

static PyObject *Sphere_Constructor(PyObject* self, PyObject* args, PyObject *kwd);
static void Sphere_Destructor(PyObject* self);
static PyObject *Sphere_Getattr(PyObject *self, char *name);

static PyTypeObject SphereType = {
	PyObject_HEAD_INIT(NULL)
	0,				/*ob_size*/
	"Sphere",			/*tp_name*/
	sizeof(SphereObject),		/*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	Sphere_Destructor,		/*tp_dealloc*/
	0,				/*tp_print*/
	Sphere_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 */
};

static PyMethodDef SphereMethods[] = {
	{NULL, NULL}
};

static PyObject* Sphere_Constructor(PyObject* self, PyObject* args, PyObject *kwd)
{
	SphereObject *v;
	MaterialObject *material;
	static char *kwdlist[] = {"centre", "radius", "material", NULL};
	PyObject *TCentre = NULL;
	Float cx, cy, cz, radius;

	if (!PyArg_ParseTupleAndKeywords(args, kwd, "O!fO!", kwdlist,
		&PyTuple_Type, &TCentre, &radius, &MaterialType, &material))
		return NULL;

	if (!PyArg_ParseTuple(TCentre, "fff", &cx, &cy, &cz))
		return NULL;

	v = PyObject_New(SphereObject, &SphereType);
	v->shape = new Sphere(Vector3(cx, cy, cz), radius, material->material);
	Py_INCREF(material);
	return (PyObject*)v;
}

static void Sphere_Destructor(PyObject* self)
{
	delete ((SphereObject *)self)->shape;
	PyObject_Del(self);
}

static PyObject *Sphere_Getattr(PyObject *self, char *name)
{
	return Py_FindMethod(SphereMethods, self, name);
}

//=========================== Box Object ===========================

typedef struct {
	PyObject_HEAD
	Box *shape;
} BoxObject;

static PyObject *Box_Constructor(PyObject* self, PyObject* args, PyObject *kwd);
static void Box_Destructor(PyObject* self);
static PyObject *Box_Getattr(PyObject *self, char *name);

static PyTypeObject BoxType = {
	PyObject_HEAD_INIT(NULL)
	0,				/*ob_size*/
	"Box",			/*tp_name*/
	sizeof(BoxObject),		/*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	Box_Destructor,		/*tp_dealloc*/
	0,				/*tp_print*/
	Box_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 */
};

static PyMethodDef BoxMethods[] = {
	{NULL, NULL}
};

static PyObject* Box_Constructor(PyObject* self, PyObject* args, PyObject *kwd)
{
	BoxObject *v;
	MaterialObject *material;
	static char *kwdlist[] = {"L", "H", "material", NULL};
	PyObject *TL = NULL;
	PyObject *TH = NULL;
	Float lx, ly, lz, hx, hy, hz;

	if (!PyArg_ParseTupleAndKeywords(args, kwd, "O!O!O!", kwdlist,
		&PyTuple_Type, &TL, &PyTuple_Type, &TH, &MaterialType, &material))
		return NULL;

	if (!PyArg_ParseTuple(TL, "fff", &lx, &ly, &lz))
		return NULL;

	if (!PyArg_ParseTuple(TH, "fff", &hx, &hy, &hz))
		return NULL;

	v = PyObject_New(BoxObject, &BoxType);
	v->shape = new Box(Vector3(lx, ly, lz), Vector3(hx, hy, hz), material->material);
	Py_INCREF(material);
	return (PyObject*)v;
}

static void Box_Destructor(PyObject* self)
{
	delete ((BoxObject *)self)->shape;
	PyObject_Del(self);
}

static PyObject *Box_Getattr(PyObject *self, char *name)
{
	return Py_FindMethod(BoxMethods, self, name);
}

//=========================== Triangle Object ===========================

typedef struct {
	PyObject_HEAD
	Triangle *triangle;
} TriangleObject;

static PyObject *Triangle_Constructor(PyObject* self, PyObject* args, PyObject *kwd);
static void Triangle_Destructor(PyObject* self);
static PyObject *Triangle_Getattr(PyObject *self, char *name);
static PyObject *Triangle_getNormal(PyObject* self, PyObject* args);
static PyObject *Triangle_setSmooth(PyObject* self, PyObject* args);

static PyTypeObject TriangleType = {
	PyObject_HEAD_INIT(NULL)
	0,				/*ob_size*/
	"Triangle",			/*tp_name*/
	sizeof(TriangleObject),		/*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	Triangle_Destructor,		/*tp_dealloc*/
	0,				/*tp_print*/
	Triangle_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 */
};

static PyMethodDef TriangleMethods[] = {
	{"getNormal", (PyCFunction)Triangle_getNormal, METH_NOARGS, "Get normal of whole triangle."},
	{"setSmooth", (PyCFunction)Triangle_setSmooth, METH_NOARGS, "Set the triangle smooth."},
	{NULL, NULL}
};

static PyObject* Triangle_Constructor(PyObject* self, PyObject* args, PyObject *kwd)
{
	TriangleObject *v;
	MaterialObject *material;
	static char *kwdlist[] = {"A", "B", "C", "material", NULL};
	NormalVertexObject *A, *B, *C;

	if (!PyArg_ParseTupleAndKeywords(args, kwd, "O!O!O!O!", kwdlist,
		&NormalVertexType, &A, &NormalVertexType, &B, &NormalVertexType, &C,
		&MaterialType, &material))
		return NULL;

	v = PyObject_New(TriangleObject, &TriangleType);
	v->triangle = new Triangle(A->nvertex, B->nvertex, C->nvertex, material->material);
	Py_INCREF(material);
	Py_INCREF(A);
	Py_INCREF(B);
	Py_INCREF(C);
	return (PyObject*)v;
}

static void Triangle_Destructor(PyObject* self)
{
	delete ((TriangleObject *)self)->triangle;
	PyObject_Del(self);
}

static PyObject *Triangle_Getattr(PyObject *self, char *name)
{
	return Py_FindMethod(TriangleMethods, self, name);
}

static PyObject* Triangle_getNormal(PyObject* self, PyObject* args)
{
	PyObject *obj;

	Vector3 N = ((TriangleObject *)self)->triangle->getNormal();

	obj = Py_BuildValue("(fff)", N.x, N.y, N.z);
	return obj;
}

static PyObject* Triangle_setSmooth(PyObject* self, PyObject* args)
{
	((TriangleObject *)self)->triangle->setSmooth();
	Py_INCREF(Py_None);
	return Py_None;
}

//=========================== Raytracer Object ===========================

typedef struct {
	PyObject_HEAD
	Raytracer *raytracer;
	vector<PyObject*> *children;
} RaytracerObject;

static PyObject *Raytracer_Constructor(PyObject* self, PyObject* args);
static void Raytracer_Destructor(PyObject* self);
static PyObject *Raytracer_Getattr(PyObject *self, char *name);
static PyObject *Raytracer_render(PyObject* self, PyObject* args);
static PyObject *Raytracer_setcamera(PyObject* self, PyObject* args);
static PyObject *Raytracer_addshape(PyObject* self, PyObject* args);
static PyObject *Raytracer_addlight(PyObject* self, PyObject* args);
static PyObject *Raytracer_ambientocclusion(PyObject* self, PyObject* args, PyObject *kwd);

static PyTypeObject RaytracerType = {
	PyObject_HEAD_INIT(NULL)
	0,				/*ob_size*/
	"Raytracer",			/*tp_name*/
	sizeof(RaytracerObject),	/*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	Raytracer_Destructor,		/*tp_dealloc*/
	0,				/*tp_print*/
	Raytracer_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 */
};

static PyMethodDef RaytracerMethods[] = {
	{"render", (PyCFunction)Raytracer_render, METH_VARARGS, "Render scene and return image data."},
	{"setcamera", (PyCFunction)Raytracer_setcamera, METH_VARARGS, "Set camera for the scene."},
	{"addshape", (PyCFunction)Raytracer_addshape, METH_VARARGS, "Add new shape to scene."},
	{"addlight", (PyCFunction)Raytracer_addlight, METH_VARARGS, "Add new light source to scene."},
	{"ambientocclusion", (PyCFunction)Raytracer_ambientocclusion, METH_VARARGS | METH_KEYWORDS,
		"Set ambient occlusion parametrs - samples: int (0 = disable), distance: float, angle: float."},
	{NULL, NULL}
};

static PyObject* Raytracer_Constructor(PyObject* self, PyObject* args)
{
	RaytracerObject *v;

	if(!PyArg_ParseTuple(args, ""))
		return NULL;

	v = PyObject_New(RaytracerObject, &RaytracerType);
	v->raytracer = new Raytracer();
	v->children = new vector<PyObject*>();
	v->raytracer->setCamera(new Camera());
	v->raytracer->setTop(new Octree());

	return (PyObject*)v;
}

static void Raytracer_Destructor(PyObject* self)
{
	vector<PyObject*>::iterator o;
	for (o = ((RaytracerObject *)self)->children->begin();
		o != ((RaytracerObject *)self)->children->end(); o++)
		Py_DECREF(*o);
	delete ((RaytracerObject *)self)->raytracer;
	PyObject_Del(self);
}

static PyObject *Raytracer_Getattr(PyObject *self, char *name)
{
	return Py_FindMethod(RaytracerMethods, self, name);
}

static PyObject* Raytracer_render(PyObject* self, PyObject* args)
{
	int w = 0, h = 0;
	unsigned char *chardata;
	Float *data;
	PyObject *o;

	if (!PyArg_ParseTuple(args, "(ii)", &w, &h))
		return NULL;

	printf("[pyrit] Running ray tracer\n");
	((RaytracerObject *)self)->raytracer->getTop()->optimize();
	data = (Float *) malloc(w*h*3*sizeof(Float));
	DefaultSampler sampler(data, w, h);
	((RaytracerObject *)self)->raytracer->setSampler(&sampler);
	((RaytracerObject *)self)->raytracer->render();
	if (!data) {
		Py_INCREF(Py_None);
		return Py_None;
	}

	// convert data to char
	printf("[pyrit] Converting image data (float to char)\n");
	chardata = (unsigned char *) malloc(w*h*3);
	Float *d = data;
	for (unsigned char *c = chardata; c != chardata + w*h*3; c++, d++) {
		if (*d > 1.0)
			*c = 255;
		else
			*c = (unsigned char)(*d * 255.0);
	}
	free(data);
	o = Py_BuildValue("s#", chardata, w*h*3);
	free(chardata);
	printf("[pyrit] Done.\n");
	return o;
}

static PyObject* Raytracer_setcamera(PyObject* self, PyObject* args)
{
	CameraObject *cam;

	if (!PyArg_ParseTuple(args, "O!", &CameraType, &cam))
		return NULL;

	((RaytracerObject *)self)->raytracer->setCamera(cam->camera);

	Py_INCREF(cam);
	Py_INCREF(Py_None);
	return Py_None;
}

static PyObject* Raytracer_addshape(PyObject* self, PyObject* args)
{
	PyObject *obj;

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

	((RaytracerObject *)self)->raytracer->addshape(
		((BoxObject*)obj)->shape);

	((RaytracerObject *)self)->children->push_back(obj);
	Py_INCREF(obj);
	Py_INCREF(Py_None);
	return Py_None;
}

static PyObject* Raytracer_addlight(PyObject* self, PyObject* args)
{
	LightObject *lightobj;

	if (!PyArg_ParseTuple(args, "O!", &LightType, &lightobj))
		return NULL;
	((RaytracerObject *)self)->raytracer->addlight(lightobj->light);
	((RaytracerObject *)self)->children->push_back((PyObject*)lightobj);
	Py_INCREF(lightobj);
	Py_INCREF(Py_None);
	return Py_None;
}

static PyObject* Raytracer_ambientocclusion(PyObject* self, PyObject* args, PyObject *kwd)
{
	int samples = 0;
	Float distance = 0.0, angle = 0.0;
	static char *kwdlist[] = {"samples", "distance", "angle", NULL};

	if (!PyArg_ParseTupleAndKeywords(args, kwd, "iff", kwdlist,
		&samples, &distance, &angle))
		return NULL;

	((RaytracerObject *)self)->raytracer->ambientocclusion(samples, distance, angle);
	Py_INCREF(Py_None);
	return Py_None;
}

//=========================== Module Methods ===========================

static PyMethodDef ModuleMethods[] = {
	{"Raytracer", (PyCFunction) Raytracer_Constructor,
		METH_VARARGS, "Raytracer object constructor."},
	{"Light", (PyCFunction) Light_Constructor,
		METH_VARARGS | METH_KEYWORDS, "Light source object constructor."},
	{"Camera", (PyCFunction) Camera_Constructor,
		METH_VARARGS | METH_KEYWORDS, "Camera object constructor."},
	{"Material", (PyCFunction) Material_Constructor,
		METH_VARARGS | METH_KEYWORDS, "Material object constructor."},
	{"NormalVertex", (PyCFunction) NormalVertex_Constructor,
		METH_VARARGS | METH_KEYWORDS, "NormalVertex object constructor."},
	{"Sphere", (PyCFunction) Sphere_Constructor,
		METH_VARARGS | METH_KEYWORDS, "Sphere object constructor."},
	{"Box", (PyCFunction) Box_Constructor,
		METH_VARARGS | METH_KEYWORDS, "Box object constructor."},
	{"Triangle", (PyCFunction) Triangle_Constructor,
		METH_VARARGS | METH_KEYWORDS, "Triangle object constructor."},
	{NULL, NULL}
};


extern "C" void initraytracer(void)
{
	Py_InitModule("raytracer", ModuleMethods);
}
