/*
 * C++ RayTracer
 * Module for Python
 * file: raytracermodule.cc
 *
 * Radek Brich, 2006
 */

#include <Python.h>

#include <vector>
#include "scene.h"
#include "raytracer.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;
}

//=========================== 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 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[] = {
	{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 (!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);
}

//=========================== 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);
}

//=========================== Plane Object ===========================

typedef struct {
	PyObject_HEAD
	Plane *shape;
} PlaneObject;

static PyObject *Plane_Constructor(PyObject* self, PyObject* args, PyObject *kwd);
static void Plane_Destructor(PyObject* self);
static PyObject *Plane_Getattr(PyObject *self, char *name);

static PyTypeObject PlaneType = {
	PyObject_HEAD_INIT(NULL)
	0,				/*ob_size*/
	"Plane",			/*tp_name*/
	sizeof(PlaneObject),		/*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	Plane_Destructor,		/*tp_dealloc*/
	0,				/*tp_print*/
	Plane_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 PlaneMethods[] = {
	{NULL, NULL}
};

static PyObject* Plane_Constructor(PyObject* self, PyObject* args, PyObject *kwd)
{
	PlaneObject *v;
	MaterialObject *material;
	static char *kwdlist[] = {"normal", "d", "material", NULL};
	PyObject *TNorm = NULL;
	float nx, ny, nz, d;

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

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

	v = PyObject_New(PlaneObject, &PlaneType);
	v->shape = new Plane(Vector3(nx, ny, nz), d, material->material);
	Py_INCREF(material);
	return (PyObject*)v;
}

static void Plane_Destructor(PyObject* self)
{
	delete ((PlaneObject *)self)->shape;
	PyObject_Del(self);
}

static PyObject *Plane_Getattr(PyObject *self, char *name)
{
	return Py_FindMethod(PlaneMethods, self, name);
}

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

typedef struct {
	PyObject_HEAD
	Triangle *shape;
} 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 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[] = {
	{NULL, NULL}
};

static PyObject* Triangle_Constructor(PyObject* self, PyObject* args, PyObject *kwd)
{
	TriangleObject *v;
	MaterialObject *material;
	static char *kwdlist[] = {"A", "B", "C", "material", NULL};
	PyObject *A = NULL, *B = NULL, *C = NULL;
	float ax, ay, az, bx, by, bz, cx, cy, cz;

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

	if (!PyArg_ParseTuple(A, "fff", &ax, &ay, &az))
		return NULL;

	if (!PyArg_ParseTuple(B, "fff", &bx, &by, &bz))
		return NULL;

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

	v = PyObject_New(TriangleObject, &TriangleType);
	v->shape = new Triangle(Vector3(ax, ay, az), Vector3(bx, by, bz),
		Vector3(cx, cy, cz), material->material);
	Py_INCREF(material);
	return (PyObject*)v;
}

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

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

//=========================== 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_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."},
	{"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*>();

	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;
	char *chardata;
	float *data;
	PyObject *o;

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

	printf("[pyrit] Raytracing...\n");
	data = ((RaytracerObject *)self)->raytracer->render(w, h);
	if (!data) {
		Py_INCREF(Py_None);
		return Py_None;
	}

	// convert data to char
	printf("[pyrit] Converting image data (float to char)...\n");
	chardata = (char *) malloc(w*h*3);
	float *d = data;
	for (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_addshape(PyObject* self, PyObject* args)
{
	PyObject *obj;

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

	((RaytracerObject *)self)->raytracer->addshape(
		((PlaneObject*)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."},
	{"Material", (PyCFunction) Material_Constructor,
		METH_VARARGS | METH_KEYWORDS, "Material object constructor."},
	{"Sphere", (PyCFunction) Sphere_Constructor,
		METH_VARARGS | METH_KEYWORDS, "Sphere object constructor."},
	{"Plane", (PyCFunction) Plane_Constructor,
		METH_VARARGS | METH_KEYWORDS, "Plane object constructor."},
	{"Triangle", (PyCFunction) Triangle_Constructor,
		METH_VARARGS | METH_KEYWORDS, "Triangle object constructor."},
	{NULL, NULL}
};


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