/** Object module; access to Object objects in Blender
  * $Id: BPY_object.c,v 1.3 2002/11/25 12:01:57 mein Exp $
 *
 * ***** BEGIN GPL/BL DUAL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version. The Blender
 * Foundation also sells licenses for use in proprietary software under
 * the Blender License.  See http://www.blender.org/BL/ for information
 * about this.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): none yet.
 *
 * ***** END GPL/BL DUAL LICENSE BLOCK *****
  *
  */
#include "Python.h"
#include "BPY_macros.h"
#include "MEM_guardedalloc.h"
#include "opy_vector.h" /* matrix datatypes */

#include "b_interface.h" // most datatypes

#include "opy_datablock.h"

#include "BLI_arithb.h" /* Mat4Invert */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* PROTOS */


/* ------------------------------------------------------------------------ */

/**************************************************/
/* Object properties for access by datablock code */

#define NULLFUNC 0
#define NULLHANDLING 0

/* structure: see opy_datablock.h */
/* attrname, DNA_membername, type      stype, min,  max, index,dlist,    
		handlingflag, extra1Ptr, extra2Ptr, extra3Ptr */

DataBlockProperty Object_Properties[]= {
	{"LocX",	"loc[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {0}, {3, -sizeof(float)}}, 
	{"LocY",	"loc[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {1}, {3, -sizeof(float)}}, 
	{"LocZ",	"loc[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {2}, {3, -sizeof(float)}}, 
	{"loc",		"loc[3]",	DBP_TYPE_VEC, 0, 3.0}, 

	{"dLocX",	"dloc[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {0}, {3, -sizeof(float)}}, 
	{"dLocY",	"dloc[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {1}, {3, -sizeof(float)}}, 
	{"dLocZ",	"dloc[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {2}, {3, -sizeof(float)}}, 
	{"dloc",	"dloc[3]",	DBP_TYPE_VEC, 0, 3.0}, 

	{"RotX",	"rot[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {0}, {3, -sizeof(float)}}, 
	{"RotY",	"rot[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {1}, {3, -sizeof(float)}}, 
	{"RotZ",	"rot[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {2}, {3, -sizeof(float)}}, 
	{"rot",		"rot[3]",	DBP_TYPE_VEC, 0, 3.0}, 

	{"dRotX",	"drot[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {0}, {3, -sizeof(float)}}, 
	{"dRotY",	"drot[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {1}, {3, -sizeof(float)}}, 
	{"dRotZ",	"drot[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {2}, {3, -sizeof(float)}}, 
	{"drot",	"drot[3]",	DBP_TYPE_VEC, 0, 3.0}, 

	{"SizeX",	"size[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {0}, {3, -sizeof(float)}}, 
	{"SizeY",	"size[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {1}, {3, -sizeof(float)}}, 
	{"SizeZ",	"size[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {2}, {3, -sizeof(float)}}, 
	{"size",	"size[3]",	DBP_TYPE_VEC, 0, 3.0}, 

	{"dSizeX",	"dsize[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {0}, {3, -sizeof(float)}}, 
	{"dSizeY",	"dsize[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {1}, {3, -sizeof(float)}}, 
	{"dSizeZ",	"dsize[3]",	DBP_TYPE_FLO, 0, 0.0,	0.0, {2}, {3, -sizeof(float)}}, 
	{"dsize",	"dsize[3]",	DBP_TYPE_VEC, 0, 3.0}, 

	{"EffX",	"effx",		DBP_TYPE_FLO, DBP_TYPE_FUN, 0.0,	0.0, {0}, {0}, DBP_HANDLING_FUNC, Object_special_getattr, 0, Object_special_setattr}, 
	{"EffY",	"effy",		DBP_TYPE_FLO, DBP_TYPE_FUN, 0.0,	0.0, {0}, {0}, DBP_HANDLING_FUNC, Object_special_getattr, 0, Object_special_setattr}, 
	{"EffZ",	"effz",		DBP_TYPE_FLO, DBP_TYPE_FUN, 0.0,	0.0, {0}, {0}, DBP_HANDLING_FUNC, Object_special_getattr, 0, Object_special_setattr}, 

	{"Layer",	"layer",	DBP_TYPE_INT, DBP_TYPE_FUN, 0.0,	0.0, {0}, {0}, DBP_HANDLING_FUNC, Object_special_getattr, 0, Object_special_setattr}, 
	{"layer",	"layer",	DBP_TYPE_INT, DBP_TYPE_FUN, 0.0,	0.0, {0}, {0}, DBP_HANDLING_FUNC, Object_special_getattr, 0, Object_special_setattr}, 

	{"parent",	"*parent",	DBP_TYPE_FUN, 0, 0.0,	0.0, {0}, {0}, 0, 0, get_DataBlock_func}, 
	{"track",	"*track",	DBP_TYPE_FUN, 0, 0.0,	0.0, {0}, {0}, 0, 0, get_DataBlock_func}, 
	{"data",	"*data",	DBP_TYPE_FUN, 0, 0.0,	0.0, {0}, {0}, 0, 0, get_DataBlock_func}, 
	{"ipo",		"*ipo",		DBP_TYPE_FUN, 0, 0.0,	0.0, {0}, {0}, 0, 0, get_DataBlock_func}, 

	{"mat",		"matrix",		DBP_TYPE_FUN, 0, 0.0, 0.0, {0}, {0}, DBP_HANDLING_FUNC, Object_special_getattr, newMatrixObject}, 
	{"matrix",		"matrix",		DBP_TYPE_FUN, 0, 0.0, 0.0, {0}, {0}, DBP_HANDLING_FUNC, Object_special_getattr, newMatrixObject}, 

	{"colbits",	"colbits",	DBP_TYPE_SHO, 0, 0.0,	0.0},  
	{"drawType",	"dt",	DBP_TYPE_CHA, 0, 0.0,	0.0},  
	{"drawMode",	"dtx",	DBP_TYPE_CHA, 0, 0.0,	0.0},  
	
	{NULL}
};

/*************************/
/* Object module methods */

DATABLOCK_GET(Objectmodule, object, getObjectList())

char Objectmodule_New_doc[] = "(type) - Add a new object of type 'type' in the current scene";
static PyObject *Objectmodule_New(PyObject *self, PyObject *args)
{
	Object *ob;
	int type;

	if (!PyArg_ParseTuple(args, "i", &type)) {
		PyErr_SetString(PyExc_TypeError, "type expected");
		return 0;
	}	
	/* add object */
	ob = object_new(type);
	return DataBlock_fromData(ob);
}

static char Objectmodule_getSelected_doc[]=
"() - Returns a list of selected Objects in the active layer(s)\n\
The active object is the first in the list, if visible";

static PyObject *Objectmodule_getSelected (PyObject *self, PyObject *args)
{
	PyObject *ob, *list;
	Base *base;
	Object *tmp;
	
	list= PyList_New(0);
	
	if (ActiveBase && SelectedAndLayer(ActiveBase)) {
		tmp = ActiveObject; /* active object is first in list */
		if (!tmp) goto no_selection;
		ob = DataBlock_fromData(tmp);
		PyList_Append(list, ob); Py_DECREF(ob); // because previous call increfs
	} 

	base = FirstBase;
	while (base) {
		if (SelectedAndLayer(base) && base != ActiveBase) {
			PyObject *ob = DataBlock_fromData(ObjectfromBase(base));
			if (!ob) goto no_selection;
			PyList_Append(list, ob); Py_DECREF(ob); 
		}
		base= base->next;
	}
	return list;
no_selection:
	Py_DECREF(list);
	Py_INCREF(Py_None);
	return Py_None;

}


struct PyMethodDef Objectmodule_methods[] = {
	{"New",			Objectmodule_New,			METH_VARARGS, Objectmodule_New_doc},
	// emulation :
	{"Get",			Objectmodule_get,			METH_VARARGS, Objectmodule_get_doc}, // XXX
	{"get",			Objectmodule_get,			METH_VARARGS, Objectmodule_get_doc},
	{"getSelected", Objectmodule_getSelected,	METH_VARARGS, Objectmodule_getSelected_doc},
	{NULL, NULL}
};

/*************************/
/* Object object methods */

/* Object_get is defined as macro; see opy_datablock.h */


static char Object_getType_doc[] = "() - returns Object type";

static PyObject *Object_getType(PyObject *self, PyObject *args)
{
	Object *ob= PYBLOCK_AS_OBJECT(self);
	return Py_BuildValue("i", (short) ob->type);
}

static char Object_getMatrix_doc[] = "() - returns 4D matrix of object";

static PyObject *Object_getMatrix(PyObject *self, PyObject *args)
{
	Object *ob= PYBLOCK_AS_OBJECT(self);
	return newMatrixObject(ob->obmat);
}

static char Object_getInverseMatrix_doc[] = "() - returns inverse 4D matrix of object";

static PyObject *Object_getInverseMatrix(PyObject *self, PyObject *args)
{
	Object *ob= PYBLOCK_AS_OBJECT(self);
	float inverse[4][4];
	Mat4Invert(inverse, ob->obmat);
	return newMatrixObject(inverse);
}

static char Object_clrParent_doc[]= 
"(mode = 0, fast = 0) - clears parent object.\n\
If specified:\n\
   mode   2: keep object transform\n\
   fast > 0: don't update scene hierarchy (faster)\n\
";

static PyObject *Object_clrParent(PyObject *self, PyObject *args)
{
	int mode = 0, ret;
	int fast = 0;
	Object *ob= PYBLOCK_AS_OBJECT(self);

	BPY_TRY(PyArg_ParseTuple(args, "|ii",  &mode, &fast));
	ret = object_clrParent(ob, mode, fast);
	return Py_BuildValue("i", ret);
}

DATABLOCK_ASSIGN_IPO(Object, object) // defines Object_assignIpo

static char Object_makeParent_doc[]=
"([obj1, obj2, ...], mode = 0, fast = 0) - makes 'self' a parent of the\n\
objects in the list.\n\
If specified:\n\
    mode <> 0: do not clear parent inverse\n\
    fast <> 0      : do not update scene hierarchy (faster)\n\
\n\
If fast is set, you will have to call Scene.getCurrent.update() before\n\
redraw.";

static PyObject *Object_makeParent(PyObject *self, PyObject *args)
{
	int i, ret;
	PyObject *list;
	int noninverse = 0;
	int fast = 0;

	DataBlock *parblk = (DataBlock*) self;

	BPY_TRY(PyArg_ParseTuple(args, "O|ii", &list, &noninverse, &fast));
	if (!PySequence_Check(list)){
		PyErr_SetString(PyExc_TypeError, "expects a list of objects");
		return 0;
	}

	for (i = 0; i < PySequence_Length(list); i ++) {
		DataBlock *childblk = (DataBlock *) PySequence_GetItem(list, i);

		if (!DataBlock_Check(childblk)) {
			PyErr_SetString(PyExc_TypeError, "Object Type expected");
			 return 0;
		}
		ret = object_makeParent((Object *) parblk->data, (Object *) childblk->data, noninverse, fast);

		Py_DECREF((PyObject *) childblk); // don't need it anymore
		if (ret == 0) { // could not parent
			PyErr_SetString(PyExc_RuntimeError, "parenting failed!");
			return 0;
		}	
	}	

	if (PyErr_Occurred()) {
		PyErr_Print();
	}
	return Py_BuildValue("i", 1);
}

static char Object_getMaterials_doc[] = "() - returns a list of object materials";

static PyObject *Object_getMaterials(PyObject *self, PyObject *args)
{
	DataBlock *objectblk = (DataBlock*) self;
	Object *object = PYBLOCK_AS_OBJECT(objectblk);
	return PyList_fromMaterialList(object->mat, object->totcol);
}

static char Object_setMaterials_doc[] = "(materialList) - sets object materials";

static PyObject *Object_setMaterials(PyObject *self, PyObject *args)
{
	int len;
	int ret;
	DataBlock *objectblk = (DataBlock*) self;
	Object *object = PYBLOCK_AS_OBJECT(objectblk);
	PyObject *list;
	Material **matlist;

	BPY_TRY(PyArg_ParseTuple(args, "O!", &PyList_Type, &list));
	len = PySequence_Length(list);
	if (len) {
		matlist = newMaterialList_fromPyList(list);
		if (!matlist) {
			PyErr_SetString(PyExc_TypeError, 
			"materialList must be a list of valid materials!");
			return 0;
		}
		ret = object_setMaterials(object, matlist, len);
	} else {
		ret = 0;
	}
	return Py_BuildValue("i", ret);
}

static char Object_copy_doc[] = "() - returns a copy of the object, sharing the same data";

static PyObject *Object_copy(PyObject *self, PyObject *args)
{
	Object *new;

	DataBlock *objectblk = (DataBlock*) self;
	Object *object = PYBLOCK_AS_OBJECT(objectblk);

	new = object_copy(object);
	return DataBlock_fromData(new);
}

static char Object_shareFrom_doc[] = "(obj) - link data of 'self' with data of 'obj' -- \n\
only if of same type!";

static PyObject *Object_shareFrom(PyObject *self, PyObject *args)
{
	DataBlock *blockA = (DataBlock*) self;
	DataBlock *blockB;
	Object *object, *other;
	int t;

	BPY_TRY(PyArg_ParseTuple(args, "O!", &DataBlock_Type, &blockB));

	if (!DataBlock_isType(blockB, ID_OB)) {
		PyErr_SetString(PyExc_TypeError, "Argument 1 is not of type 'Object'");
		return NULL;
	}

	object = PYBLOCK_AS_OBJECT(blockA);
	other  = PYBLOCK_AS_OBJECT(blockB);

	if (other->type != object->type) {
		PyErr_SetString(PyExc_TypeError, "Objects are not of same data type");
		return NULL;
	}	
	t = object->type;
	switch (t) {
		case OB_MESH:
			return Py_BuildValue("i", object_linkdata(object, other->data));
		default:
			PyErr_SetString(PyExc_TypeError, "Type not supported");
			return NULL;
	}
}

/******************/
/* get & set attr */

static float g_zero_float= 0.0;

/* Object attributes functions which require getter/setter C functions 
   different from the access provided by DataBlock support */

/* get special attributes through datablock property structure */

void *Object_special_getattr(void *vdata, char *name) 
{
	Object *ob= (Object *) vdata;
	int scriptflag;

	if (STREQ(name, "layer")) {
		return &ob->lay;
		
	} else if (strncmp(name, "eff", 3)==0) {
		Ika *ika= ob->data;

		if (ob->type==OB_IKA && ika) {
			if      (name[3]=='x') return &ika->effg[0];
			else if (name[3]=='y') return &ika->effg[1];
			else if (name[3]=='z') return &ika->effg[2];
		}
	
		return &g_zero_float;
	/* these only for compatibiliy... XXX */
	} else if (STREQ(name, "matrix")) {
		scriptflag = during_script();
		disable_where_script(1);
		where_is_object(ob);
		disable_where_script(scriptflag);
		
		return &ob->obmat;
	} else if (STREQ(name, "inverse") || STREQ(name, "inverseMatrix")) {
		return Object_getInverseMatrix(vdata, 0);
	}
	/* end compatibility */
	
	PyErr_SetString(PyExc_AttributeError, name);
	return NULL;
}

int Object_special_setattr(void *vdata, char *name, PyObject *py_ob)
{
	Object *ob= (Object *) vdata;

	if (STREQ(name, "layer")) {
		Base *base;
		int ival;
		
		if (!PyArg_Parse(py_ob, "i", &ival)) return -1;
		
		ob->lay= ival;
	// TODO this is old stuff, maybe move to update routine at end of
	// script execution ?
		base= (G.scene->base.first);
		while (base) {
			if (base->object == ob) base->lay= ob->lay;
			base= base->next;
		}
	// end TODO

		return 0;
	} else if (strncmp(name, "eff", 3)==0) {
		Ika *ika= ob->data;
		float fval;
		
		if (!PyArg_Parse(py_ob, "f", &fval)) return -1;
		
		if (ob->type==OB_IKA && ika) {
			if (name[3]=='x') ika->effg[0]= fval;
			else if (name[3]=='y') ika->effg[1]= fval;
			else if (name[3]=='z') ika->effg[2]= fval;
			
			itterate_ika(ob);
		}
		return 0;
	}

	PyErr_SetString(PyExc_AttributeError, name);
	return -1;
}



#undef MethodDef
#define MethodDef(func) _MethodDef(func, Object)

struct PyMethodDef Object_methods[] = {
	MethodDef(makeParent),
	MethodDef(copy),
	MethodDef(shareFrom),
	MethodDef(getMatrix),
	MethodDef(getType),
	MethodDef(getInverseMatrix),
	MethodDef(clrParent),
	MethodDef(assignIpo),
	MethodDef(clrIpo),
	MethodDef(getMaterials),
	MethodDef(setMaterials),
	{NULL, NULL}
};

#undef BPY_ADDCONST
#define BPY_ADDCONST(dict, name) insertConst(dict, #name, PyInt_FromLong(OB_##name))

PyObject *initObject(void)
{
	PyObject *mod, *dict, *d;

	mod= Py_InitModule(MODNAME(BLENDERMODULE) ".Object", Objectmodule_methods);
	dict= PyModule_GetDict(mod);
	d = ConstObject_New();
	PyDict_SetItemString(dict, "Types", d);
	BPY_ADDCONST(d, EMPTY);
	BPY_ADDCONST(d, MESH);
	BPY_ADDCONST(d, LAMP);
	BPY_ADDCONST(d, CAMERA);

	d = ConstObject_New();
	PyDict_SetItemString(dict, "DrawTypes", d);
	/* dt flags */
	BPY_ADDCONST(d, BOUNDBOX);
	BPY_ADDCONST(d, WIRE);
	BPY_ADDCONST(d, SOLID);
	BPY_ADDCONST(d, SHADED);
	BPY_ADDCONST(d, TEXTURE);
	d = ConstObject_New();
	PyDict_SetItemString(dict, "DrawModes", d);
	/* dtx flags */
	BPY_ADDCONST(d, BOUNDBOX);
	BPY_ADDCONST(d, AXIS);
	BPY_ADDCONST(d, TEXSPACE);
	insertConst(d, "NAME", PyInt_FromLong(OB_DRAWNAME));
	return mod;
}

