/** Interfacing with Blender
 *
 * ***** 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 *****
  *
  *  $Id: b_interface.c,v 1.3 2002/11/25 12:01:57 mein Exp $
  *
  * This code is currently messy and an attempt to restructure
  * some Blender kernel level code.
  * Hopefully a template for a future C-API...
  *
  *
  */


#include "BLI_blenlib.h" // mallocs
#include "BLI_arithb.h"

#include "BKE_library.h" 
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_object.h"
#include "BKE_mesh.h"
#include "BKE_ipo.h"

#include "MEM_guardedalloc.h"

#include "Python.h" 
#include "BPY_macros.h" 
#include "structmember.h" 

#include "BDR_editobject.h"

#include "b_interface.h"

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

/************************************************************************
 * Generic low level routines
 *
 */

/** This just returns a pointer to the global struct.
  *
  * Mainly introduced for debugging purposes..
  *
  */

Global *getGlobal(void)
{
	return &G;
}	

/** define list getters:
	These functions return a linked list pointer (ListBase *) from the global
	Blender-object list.

	Example:
		oblist = getObjectList();
		firstobject = oblist->first;

	 */

/*
DEF_GETLIST(Scene, scene)
DEF_GETLIST(Object, object)
DEF_GETLIST(Mesh, mesh)
DEF_GETLIST(Camera, camera)
DEF_GETLIST(Material, mat)
DEF_GETLIST(Lamp, lamp)
DEF_GETLIST(World, world)
DEF_GETLIST(Ipo, ipo)
DEF_GETLIST(Image, image)
DEF_GETLIST(Texture, tex)
DEF_GETLIST(Text, text)
DEF_GETLIST(Key, key)
*/

/* gets a datablock object from the ID list by name */
ID *getFromList(ListBase *list, char *name) 
{
	ID *id = list->first;

	while (id) {
		if(STREQ(name, getIDName(id))) break;
			id= id->next;
	}
	return id;
}

void printList(ListBase *list)
{
	ID *walk = list->first;
	ID *lastwalk = 0;
	printf("List: %s\n", walk->name);
	while (walk) {
		printf("   %s\n", walk->name);
		lastwalk = walk;
		walk= walk->next;
	}
	if (list->last != lastwalk)
	{
		printf("****: listbase->last pointing to wrong end!\n");
		// list->last = lastwalk;
	}
}


/** (future) garbage collector subroutine */


int gc_mainlist(ListBase *lb)
{
	ID *id = (ID *) lb->first;

	while (id) {
		if (getIDUsers(id) == 0) {
			switch(GET_ID_TYPE(id)) {
			case ID_OB:
				BPY_debug(("free [Object %s]\n", getIDName(id)));
				unlink_object((Object *) id);
				free_libblock(lb, id);
				break;
			default: break;
			}
		}
		id = id->next;
	}
	return 1;
}

	/** Garbage collection function.  EXPERIMENTAL!
	 *  This should free Blender from all unreferenced Objects (i.e. 
	 *  user count == 0). 
	 *  Don't expect too much yet -- User counting isn't done
	 *  consequently in Blender. Neither parenting or bevelcurves
	 *  etc. respect user counts...therefore, no circular references
	 *  show up -- which are in fact possible; example:
	 *
	 *  A BevelCurve is parenting its BevelObject: so there is a 
	 *  reference from the BevelObject to the BevelCurve, and a
	 *  reference back from the Bevelcurve to the BevelObject.
	 *
	 *  There are a lot of cleanup functions in Blender taking care
	 *  of updating (invalidating) references to deleted objects.
	 *  See unlink_object() for more details.
	 *
	 *  This function must not be called inside a script, so don't go
	 *  and create a wrapper for it :-)
	 *  In a hopefully later implementation, the Python garbage collection
	 *  might be used. For the moment, this is better than 'Save and Reload'
	 */

	int garbage_collect(Main *m)
	{
		/* Remember, all descriptor objects must BOB_DECUSER on their raw 
		Blender Datablock in their __del__ method (C-API: dealloc function) */

		gc_mainlist(&m->object);

		/* TODO proper kernel level functions for safely freeing these objects
		 * must first be implemented... 
		gc_mainlist(&m->mesh);
	gc_mainlist(&m->mat);
	gc_mainlist(&m->lamp);
	gc_mainlist(&m->camera);

	.. and this list is far from being complete.
	*/

	return 1;
}

/** expands pointer array of length 'oldsize' to length 'newsize'.
  * A pointer to the (void *) array must be passed as first argument 
  * The array pointer content can be NULL, in this case a new array of length
  * 'newsize' is created.
  */

static int expandPtrArray(void **p, int oldsize, int newsize)
{
	void *newarray;

	if (newsize < oldsize) {
		return 0;
	}	
	newarray = MEM_callocN(newsize * sizeof(void *), "PtrArray");
	if (*p) {
		memcpy(newarray, *p, oldsize);
		MEM_freeN(*p);
	}	
	*p = newarray;
	return 1;
}

/************************************************************************
 * Material object low level routines
 *
 */

/* MAXMAT = maximum number of materials per object/ object data */

#define MATINDEX_CHECK(x) \
	if ((x) < 0 || (x) >= MAXMAT) { printf("illegal matindex!\n"); return 0; }

/** Returns a new material list (material pointer array) of length 'len'
  *
  */

Material **newMaterialList(int len)
{
	Material **matlist = 
		(Material **) MEM_mallocN(len * sizeof(Material *), "MaterialList");
	return matlist;
}

/** releases material list and decrements user count on materials */

int releaseMaterialList(Material **matlist, int len)
{
	int i;
	Material *m;

	MATINDEX_CHECK(len);

	for (i= 0; i < len; i++) {

		m = matlist[i];
		BOB_XDECUSER((ID *) m);
	}
	MEM_freeN(matlist); 
	return 1;
}

/** Synchronizes Object <-> data material lists. Blender just wants it. */

int synchronizeMaterialLists(Object *object, void *data)
{
	// get pointer to data's material array:
	// and            number of data materials
	// ... because they will need modification.

	Material ***p_dataMaterials = give_matarar(object); 
	short *nmaterials = give_totcolp(object);

	if (object->totcol > *nmaterials){ // more object mats than data mats
		*nmaterials = object->totcol;
		return expandPtrArray((void *) p_dataMaterials, *nmaterials, object->totcol);
	} else if (object->totcol < *nmaterials) {
		object->totcol = *nmaterials;
		return expandPtrArray((void *) &object->mat, object->totcol, *nmaterials);
	}
	return 1; // in this case, no synchronization needed; they're of equal
	          // length
}

/************************************************************************
 * Object low level routines
 *
 */

/** creates a new empty object of type OB_ (TODO: should be enum)
  *
  */


Object *object_new(int type)
{
	Object *object;
	char name[32];

	Global *g = getGlobal();

	switch(type) {
		case OB_MESH: strcpy(name, "Mesh"); break;
		case OB_CURVE: strcpy(name, "Curve"); break;
		case OB_SURF: strcpy(name, "Surf"); break;
		case OB_FONT: strcpy(name, "Text"); break;
		case OB_MBALL: strcpy(name, "Mball"); break;
		case OB_CAMERA: strcpy(name, "Camera"); break;
		case OB_LAMP: strcpy(name, "Lamp"); break;
		case OB_IKA: strcpy(name, "Ika"); break;
		case OB_LATTICE: strcpy(name, "Lattice"); break;
		case OB_WAVE: strcpy(name, "Wave"); break;
		case OB_ARMATURE: strcpy(name,"Armature");break;
		default:  strcpy(name, "Empty");
	}

	object = alloc_libblock(getObjectList(), ID_OB, name);

	/* user count is set to 1 by alloc_libblock, we just reset it to 0... */
	BOB_USERCOUNT((ID*) object) = 0; // it's a new object, so no user yet
	object->flag = 0;
	object->type = type;

	/* transforms */
	QuatOne(object->quat);
	QuatOne(object->dquat);
	
    object->col[3]= 1.0;    // alpha 

	object->size[0] = object->size[1] = object->size[2] = 1.0;
	object->loc[0] = object->loc[1] = object->loc[2] = 0.0;
	Mat4One(object->parentinv);
	Mat4One(object->obmat);
	object->dt = OB_SHADED; // drawtype

	object_setdefaults(object);

	object->lay = 1; // Layer, by default visible

	switch(type) {
		case OB_MESH: object->data= add_mesh(); g->totmesh++; break;
		case OB_CAMERA: object->data= add_camera(); break;
		case OB_LAMP: object->data= add_lamp(); g->totlamp++; break;

	// TODO the following types will be supported later
	//	case OB_CURVE: object->data= add_curve(OB_CURVE); g->totcurve++; break;
	//	case OB_SURF: object->data= add_curve(OB_SURF); g->totcurve++; break;
	//	case OB_FONT: object->data= add_curve(OB_FONT); break;
	//	case OB_MBALL: object->data= add_mball(); break;
	//	case OB_IKA: object->data= add_ika(); object->dt= OB_WIRE; break;
	//	case OB_LATTICE: object->data= (void *)add_lattice(); object->dt= OB_WIRE; break;
	//	case OB_WAVE: object->data= add_wave(); break;
	//	case OB_ARMATURE: object->data=add_armature();break;
	}

	g->totobj++; // gee, I *hate* G
	return object;
}

/* returns new Base */
Base *object_newBase(Object *object)
{
	Base *base;
	base = MEM_callocN(sizeof(Base), "newbase");
	if (!base)
		return 0;
	base->object = object;
	base->lay = object->lay;
	base->flag = object->flag;
	return base;
}

Object *object_copy(Object *object)
{
	Object *new;

	new = copy_object(object);
	BOB_USERCOUNT((ID*) new) = 0; // it's a new object, so no user yet
	return new;
}

/* Set draw mode of object */
void object_setDrawMode(Object *object, int modebits)
{
	object->dt = (modebits & 0xff);
	object->dtx = (modebits >> 8);
}

int object_getDrawMode(Object *object)
{
	return (((int) object->dtx) << 8 ) + object->dt;
}

/* link data to Object object */
int object_linkdata(Object *object, void *data)
{
	ID *oldid, *id;
	int valid;

	if (!data) return 0;

	oldid = (ID*) object->data; 
	id = (ID*) data;

	valid = 0;

#define _CASE(objtype, idtype) \
	case objtype:\
		if (GET_ID_TYPE(id) == idtype) \
			valid = 1; \
		break;	

	switch (object->type) {
	_CASE(OB_MESH, ID_ME)
	_CASE(OB_CAMERA, ID_CA)
	_CASE(OB_LAMP, ID_LA)
	default: // not supported
		return 0;
	}
	if (valid) {
		object->data = data;
		BOB_INCUSER(id);
		if (oldid)
			BOB_DECUSER(oldid); // dec users

		// extra check for multi materials on meshes:
		// This is a hack to check whether object material lists are of same
		// length as their data material lists..
		//if (GET_ID_TYPE(id) == ID_ME) {
			//test_object_materials(id);
		//}	
		return 1;
	}
	return 0;
}

/* release data from object object */

int object_unlinkdata(Object *object)
{
	ID *id = object->data;

	BOB_XDECUSER(id);
	return 1;
}

/** set Object materials:
  * takes a list of Material pointers of maximum length MAXMAT
  */

int object_setMaterials(Object *object, Material **matlist, int len)
{
	int i;

	MATINDEX_CHECK(len)
	if (object->mat) {
		releaseMaterialList(object->mat, len);
	}
	// inc user count on all materials
	for (i = 0; i < len; i++) {
		BOB_XINCUSER( (ID *) matlist[i]);
	}

	object->mat = matlist;
	object->totcol = len;
	object->actcol = len - 1; // XXX
	// workaround: blender wants the object's data material list
	// to be of the same length, otherwise colourful fun happens.
	// so, we synchronize this here:

	switch (object->type)
	{
		case OB_MESH:
		case OB_CURVE:
		case OB_FONT:
		case OB_SURF:
		case OB_MBALL:
			synchronizeMaterialLists(object, object->data);
			break;
		default:
			return 0;
	}
	return 1;
}

/** make 'object' the parent of the object 'child' 
  *
  * mode = 1: set parent inverse matrix to _1_ ('clear inverse')
  * fast = 1: Don't update scene base (hierarchy). In this case,
  *           sort_baselist() needs to be called explicitely before redraw.
  */

int object_makeParent(Object *parent, Object *child, int mode, int fast) 
{
	if (test_parent_loop(parent, child)) {
		PyErr_SetString(PyExc_RuntimeError, "parenting loop detected!");
		return 0;
	}
	child->partype = PAROBJECT;
	child->parent = parent;
	if (mode == 1) {
		Mat4One(child->parentinv); // parent inverse = unity
		child->loc[0] = 0.0; child->loc[1] = 0.0; child->loc[2] = 0.0; 
	} else {
		what_does_parent(child);
		Mat4Invert(child->parentinv, parent->obmat); // save inverse
	}

	/* This is some bad global thing again -- we should determine 
	   the current scene
	   another way. Later. */
	if (!fast) 
		sort_baselist(getGlobal()->scene);

	return 1;
}

/** Unlink parenting hierarchy:
  *
  * mode = 2: keep transform
  * fast = 1: don't update scene bases. see makeParent()
  */

int object_clrParent(Object *child, int mode, int fast) 
{
	Object *par;

	par = child->parent;
	child->parent = 0;
	if (mode == 2) { // keep transform
		apply_obmat(child);
	}
	if (!fast) 
		sort_baselist(getGlobal()->scene);
	return 1;
}

/** Set object's defaults */

int object_setdefaults(Object *ob)
{
	if(U.flag & MAT_ON_OB) ob->colbits= -1;
	switch(ob->type) {
		case OB_CAMERA:
		case OB_LAMP:
			ob->trackflag = OB_NEGZ;
			ob->upflag = OB_POSY;
			break;
		default:
			ob->trackflag = OB_POSY;
			ob->upflag = OB_POSZ;
	}
	ob->ipoflag = OB_OFFS_OB + OB_OFFS_PARENT;

	/* duplivert settings */

	ob->dupon = 1; ob->dupoff = 0;
	ob->dupsta = 1; ob->dupend = 100;

	/* Gameengine defaults*/
	ob->mass= ob->inertia= 1.0;
	ob->formfactor= 0.4;
	ob->damping= 0.04;
	ob->rdamping= 0.1;
	ob->anisotropicFriction[0] = 1.0;
	ob->anisotropicFriction[1] = 1.0;
	ob->anisotropicFriction[2] = 1.0;

	/* default to not use fh in new system */
	ob->gameflag= OB_PROP;	/*|OB_DO_FH; */
	
	return 1;
}

/************************************************************************
 * Creation of new data blocks
 * 
 * We [ab|re]use the blender kernel functions, but set the user count to 0,
 * because the object does not have users yet.
 * Currently, the user count is abused as reference count which should be
 * separate in future
 */

Material *material_new(void)
{
	Material *m = add_material("Material");
	BOB_USERCOUNT((ID*) m) = 0; // set 'refcount' to 0, because 
	                            // it's a free material
	return m;
}

Lamp *lamp_new()
{
	Lamp *la;

	la = add_lamp();
	BOB_USERCOUNT((ID*) la) = 0; 

	return la;
}	

Camera *camera_new()
{
	Camera *cam;
        
 	cam = add_camera();
	BOB_USERCOUNT((ID*) cam) = 0; 
	return cam;
}	

Ipo *ipo_new(int type, char *name)
{
	Ipo *ipo;

	ipo = add_ipo(name, type);
	BOB_USERCOUNT((ID*) ipo) = 0; 
	return ipo;
}


/* Finds the ipo curve with channel code 'code' in the datablock 'ipo' 
   and returns it, if found (NULL otherwise) */

IpoCurve *ipo_findcurve(Ipo *ipo, int code) 
{
	IpoCurve *ipocurve;

	ipocurve = ipo->curve.first;
	while(ipocurve) {
		if (ipocurve->adrcode == code) break;
		ipocurve = ipocurve->next;
	}
	return ipocurve;
}	


/** Returns a new Ipo curve */
IpoCurve *ipocurve_new()
{
	IpoCurve *curve;

	curve = MEM_callocN(sizeof(IpoCurve), "new_ipocurve");
	curve->flag = IPO_VISIBLE;
	return curve;
}	

IpoCurve *ipocurve_copy(IpoCurve *curve)
{
	IpoCurve *newcurve;

	newcurve = MEM_callocN(sizeof(IpoCurve), "new_ipocurve");
	memcpy(newcurve, curve, sizeof(IpoCurve));
	// copy bez triples:
	newcurve->bezt= MEM_mallocN(curve->totvert*sizeof(BezTriple), "ipocurve_copy");
	memcpy(newcurve->bezt, curve->bezt, curve->totvert*sizeof(BezTriple)); 
	return newcurve;
}

/** Assign ipo to object object */

/* macros, see b_interface.h */

DEF_ASSIGN_IPO(object, Object)  // defines object_assignIpo()

DEF_ASSIGN_IPO(camera, Camera)  

DEF_ASSIGN_IPO(lamp, Lamp)  

DEF_ASSIGN_IPO(material, Material)  

/************************************************************************
 * Mesh object low level routines
 *
 */

/** Returns a new, free (non owned) mesh.
  * add_mesh() automatically returns a mesh object with users = 1,
  * so we set it to 0. Hack, hack.
  */

Mesh *mesh_new(void)
{
	Mesh *me = add_mesh();
	((ID *) me)->us = 0;
	return me;
}

/** updates drawing properties etc. of mesh */

void mesh_update(Mesh *mesh)
{
	edge_drawflags_mesh(mesh);
	tex_space_mesh(mesh);
}

/************************************************************************
 * Scene object low level routines
 *
 */

/** Returns current Scene */

Scene *scene_getCurrent(void)
{
	return getGlobal()->scene;
}

/* returns base of object 'object' in Scene 'scene', 0 if nonexistant 
 * A base is basically an visual instantiation of an 3D object (Object)
 * in a Scene. See scene_linkObject()
 *
 */

Base *scene_getObjectBase(Scene *scene, Object *object)
{
	Base *base;
	base = scene->base.first;
	while (base)
	{
		if (object == base->object) // it exists
			return base;
		base = base->next;
	}
	return NULL;
}

/* links an object into a scene */
int scene_linkObject(Scene *scene, Object *object)
{
	Base *base, *b;
	b = scene_getObjectBase(scene, object);
	if (b)
		return 0;
	base = object_newBase(object); 
	if (!base) { 
		return 0;
	}	
	BOB_INCUSER((ID *) object); // incref the object 
	BLI_addhead(&scene->base, base);
	return 1;
}

/* unlinks an object from a scene */
int scene_unlinkObject(Scene *scene, Object *object)
{
	Base *base;
	base = scene_getObjectBase(scene, object);
	if (base) {
		BLI_remlink(&scene->base, base);
		BOB_DECUSER((ID *) object);
		MEM_freeN(base);
		scene->basact = 0; // make sure the just deleted object has no longer an 
		                   // active base (which happens if it was selected
		return 1;
	}
	else return 0;
}

