/*#io
Box ioDoc(
		docCopyright("Steve Dekorte", 2002)
		docLicense("BSD revised")
		docDependsOn("Vector")
		docCategory("Math")
		docDescription("A primitive for fast operations on rectangles.")
		*/

#include "IoBox.h"
#include "IoState.h"
#include "IoNumber.h"
#include <math.h>

#define DATA(self) ((IoBoxData *)IoObject_dataPointer(self))

void *IoMessage_locals_boxArgAt_(IoMessage *self, void *locals, int n)
{
	IoObject *v = IoMessage_locals_valueArgAt_(self, locals, n);
	
	if (!ISBOX(v)) 
	{
		IoMessage_locals_numberArgAt_errorForType_(self, locals, n, "Box");
	}
	
	return v;
}

IoTag *IoBox_tag(void *state)
{
	IoTag *tag = IoTag_newWithName_("Box");
	tag->state = state;
	tag->freeFunc  = (TagFreeFunc *)IoBox_free;
	tag->cloneFunc = (TagCloneFunc *)IoBox_rawClone;
	tag->markFunc  = (TagMarkFunc *)IoBox_mark;
	return tag;
}

IoBox *IoBox_proto(void *state)
{
	IoBox *self = IoObject_new(state);
	self->tag = IoBox_tag(state);
	
	IoObject_setDataPointer_(self, calloc(1, sizeof(IoBoxData)));
	
	DATA(self)->origin = IoVector_newWithSize_(state, 3);
	DATA(self)->size   = IoVector_newWithSize_(state, 3);
	
	IoState_registerProtoWithFunc_(state, self, IoBox_proto);
	
	{
		IoMethodTable methodTable[] = { 
		{"set", IoBox_set},
		{"origin", IoBox_origin},
		{"size", IoBox_size},
			
		{"width", IoBox_width},
		{"height", IoBox_height},
		{"depth", IoBox_depth},
			
		{"setOrigin", IoBox_setOrigin},
		{"setSize", IoBox_setSize},
		{"Union", IoBox_Union},
			
		{"print", IoBox_print},
		{"containsPoint", IoBox_containsPoint},
		{"intersectsBox", IoBox_intersectsBox},
			/*
			 {"asString", IoBox_asString},
			 {"Min", IoBox_Min},
			 {"Max", IoBox_Max},
			 */
		{NULL, NULL},
		};
		IoObject_addMethodTable_(self, methodTable);
	}
	return self;
}

IoBox *IoBox_rawClone(IoBox *proto) 
{ 
	IoBox *self = IoObject_rawClonePrimitive(proto);
	IoObject_setDataPointer_(self, cpalloc(IoObject_dataPointer(proto), sizeof(IoBoxData)));
	
	DATA(self)->origin = IOCLONE(DATA(proto)->origin);
	DATA(self)->size   = IOCLONE(DATA(proto)->size);
	return self; 
}

IoBox *IoBox_new(void *state)
{
	IoBox *proto = IoState_protoWithInitFunction_(state, IoBox_proto);
	return IOCLONE(proto);
}

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

void IoBox_rawCopy(IoBox *self, IoBox *other)
{
	IoVector_rawCopy(DATA(self)->origin, DATA(other)->origin);
	IoVector_rawCopy(DATA(self)->size, DATA(other)->size);
}

void IoBox_rawSet(IoBox *self, 
			   NUM_TYPE x, 
			   NUM_TYPE y, 
			   NUM_TYPE z, 
			   NUM_TYPE w, 
			   NUM_TYPE h, 
			   NUM_TYPE d)
{
	IoVector_rawSetXYZ(DATA(self)->origin, x, y, z);
	IoVector_rawSetXYZ(DATA(self)->size,   w, h, d);
}

IoBox *IoBox_newSet(void *state, 
				NUM_TYPE x, 
				NUM_TYPE y, 
				NUM_TYPE z, 
				NUM_TYPE w, 
				NUM_TYPE h, 
				NUM_TYPE d)
{
	IoBox *self = IoBox_new(state);
	IoVector_rawSetXYZ(DATA(self)->origin, x, y, z);
	IoVector_rawSetXYZ(DATA(self)->size,   w, h, d);
	return self;
}

void IoBox_free(IoBox *self) 
{ 
	free(IoObject_dataPointer(self)); 
}

void IoBox_mark(IoBox *self) 
{ 
	IoObject_shouldMark((IoObject *)DATA(self)->origin); 
	IoObject_shouldMark((IoObject *)DATA(self)->size); 
}

IoVector *IoBox_rawOrigin(IoBox *self) 
{ 
	return DATA(self)->origin; 
}

IoVector *IoBox_rawSize(IoBox *self)   
{ 
	return DATA(self)->size; 
}

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

IoObject *IoBox_origin(IoBox *self, IoObject *locals, IoMessage *m)
{ 
	/*#io
	docSlot("origin", "Returns the point object for the origin of the box.")
	*/
	
	return DATA(self)->origin;
}

IoObject *IoBox_size(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("size", "Returns the point object for the size of the box. ")
	*/
	
	return DATA(self)->size;
}

IoObject *IoBox_width(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("width", "Same as; size x")
	*/
	
	return IoVector_x(DATA(self)->size, locals, m);
}

IoObject *IoBox_height(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("height", "Same as; size y")
	*/
	
	return IoVector_y(DATA(self)->size, locals, m);
}

IoObject *IoBox_depth(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("depth", "Same as; size z")
	*/
	
	return IoVector_z(DATA(self)->size, locals, m);
}

IoObject *IoBox_set(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("set(origin, size)", "Copies the values in origin and size to set the box's origin and size.")
	*/
	
	IoVector_rawCopy(DATA(self)->origin, IoMessage_locals_pointArgAt_(m, locals, 0));
	IoVector_rawCopy(DATA(self)->size,   IoMessage_locals_pointArgAt_(m, locals, 1));
	return self;
}

IoObject *IoBox_setOrigin(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("setOrigin(aPoint)", "Copies the values in aPoint to the box's origin point. ")
	*/
	
	IoVector_rawCopy(DATA(self)->origin, IoMessage_locals_pointArgAt_(m, locals, 0));
	return self;
}

IoObject *IoBox_setSize(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("setSize(aPoint)", "Copies the values in aPoint to the box's size point.  ")
	*/
	
	IoVector_rawCopy(DATA(self)->size, IoMessage_locals_pointArgAt_(m, locals, 0));
	return self;
}

IoObject *IoBox_copy(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("copy(aBox)", "Copies the values of aBox to the receiver.")
	*/
	
	IoBox *other = IoMessage_locals_boxArgAt_(m, locals, 0);
	IoVector_rawCopy(DATA(self)->origin, DATA(other)->origin);
	IoVector_rawCopy(DATA(self)->size,   DATA(other)->size);
	return self;
}

IoObject *IoBox_print(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("print", "Prints a string representation of the receiver to the standard output.")
	*/
	
	IoState_print_(IOSTATE, "Box clone set(");
	IoVector_print(DATA(self)->origin, locals, m);
	IoState_print_(IOSTATE, ", ");
	IoVector_print(DATA(self)->size, locals, m);
	IoState_print_(IOSTATE, ")");
	return self; 
}

IoObject *IoBox_Union(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("Union(aBox)", "Returns a new box containing the 2d union of the receiver and aBox.")
	*/
	
	IoBox *other = IoMessage_locals_boxArgAt_(m, locals, 0);
	IoBox_rawUnion(self, other);
	return self; 
}

void IoBox_rawUnion(IoBox *self, IoBox *other)
{
	NUM_TYPE x1, y1, z1;
	NUM_TYPE x2, y2, z2;
	NUM_TYPE ox1, oy1, oz1;
	NUM_TYPE ox2, oy2, oz2;
	
	IoVector_rawGetXYZ(DATA(self)->origin, &x1, &y1, &z1);
	IoVector_rawGetXYZ(DATA(self)->size, &x2, &y2, &z2);
	x2 += x1; 
	y2 += y1;
	
	/*
	 NUM_TYPE x1 = DATA(self)->origin->x->n;
	 NUM_TYPE y1 = DATA(self)->origin->y->n;
	 NUM_TYPE x2 = x1 + DATA(self)->size->x->n;
	 NUM_TYPE y2 = y1 + DATA(self)->size->y->n;
	 */
	IoVector_rawGetXYZ(DATA(other)->origin, &ox1, &oy1, &oz1);
	IoVector_rawGetXYZ(DATA(other)->size, &ox2, &oy2, &oz2);
	ox2 += ox1; 
	oy2 += oy1;
	
	/*
	 NUM_TYPE ox1 = DATA(other)->origin->x->n;
	 NUM_TYPE oy1 = DATA(other)->origin->y->n;
	 NUM_TYPE ox2 = ox1 + DATA(other)->size->x->n;
	 NUM_TYPE oy2 = oy1 + DATA(other)->size->y->n;
	 */
	{
		NUM_TYPE ux1 = x1 > ox1 ? x1 : ox1;
		NUM_TYPE uy1 = y1 > oy1 ? y1 : oy1;
		NUM_TYPE ux2 = x2 < ox2 ? x2 : ox2;
		NUM_TYPE uy2 = y2 < oy2 ? y2 : oy2;
		
		NUM_TYPE uw = ux2 - ux1;
		NUM_TYPE uh = uy2 - uy1;
		
		IoVector_rawSetXYZ(DATA(self)->origin, ux1, uy1, 0);
		IoVector_rawSetXYZ(DATA(self)->size, uw > 0 ? uw:0, uh > 0 ? uh:0, 0);
		/*
		 DATA(self)->origin->x->n = ux1;
		 DATA(self)->origin->y->n = uy1;
		 DATA(self)->size->x->n = uw > 0 ? uw:0;
		 DATA(self)->size->y->n = uh > 0 ? uh:0;
		 */
	}
}

int IoBox_rawContains3dPoint(IoBox *self, IoVector *otherPoint)
{
	// should really do this with stack allocated Vectors or something
	
	NUM_TYPE px, py, pz;
	NUM_TYPE x, y, z;
	NUM_TYPE w, h, d;
	
	IoVector_rawGetXYZ(otherPoint, &px, &py, &pz);
	IoVector_rawGetXYZ(DATA(self)->origin, &x, &y, &z);
	IoVector_rawGetXYZ(DATA(self)->size, &w, &h, &d);
	
	// Fix to allow Boxes with negative w, h, or d to contain Points. -jk
	if (w < 0) { w = -w; x = -x; px = -px; }
	if (h < 0) { h = -h; y = -y; py = -py; }
	if (d < 0) { d = -d; x = -x; px = -px; }
	
	return (px >= x) && 
		  (py >= y) && 
		  (pz >= z) &&
			    
		  (px <= x + w) && 
		  (py <= y + h) && 
		  (pz <= z + d);
}


IoObject *IoBox_containsPoint(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("containsPoint(aPoint)", "Returns true if aPoint is within the receiver's bounds, false otherwise.")
	*/
	
	int result;
	
	IoVector *otherPoint = IoMessage_locals_pointArgAt_(m, locals, 0);
	
	/*
	if (IoVector_rawSize(otherPoint) <= 3)
	{
		return IOBOOL(self, IoBox_rawContains3dPoint(self, otherPoint));
	}
	else
	*/
	{	
		Vector *bo = IoVector_rawVector(IoBox_rawOrigin(self));
		Vector *bs = IoVector_rawVector(IoBox_rawSize(self));
		Vector *p = IoVector_rawVector(otherPoint);

		// do a malloc since the vectors might be large
		
		Vector *b1 = Vector_clone(bo);
		Vector *b2 = Vector_clone(bs);
		
		// make bo2 the box endpoint
		
		Vector_addArray_(b2, b1); 
		
		// ensure bo1 is on the left bottom and bo2 is on the top right
		
		Vector_Min(b1, b2);
		Vector_Max(b2, bo);  
		
		result = Vector_greaterThanOrEqualTo_(p, b1) && Vector_greaterThanOrEqualTo_(b2, p);
		
		Vector_free(b1);
		Vector_free(b2);
		
		return IOBOOL(self, result);
	}
}

IoObject *IoBox_intersectsBox(IoBox *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("intersectsBox(aBox)", "Returns true if aBox is within the receiver's bounds, false otherwise.")
	*/
	
	int result = 0;
	
	/*
	Vector *bo = IoVector_rawVector(IoBox_rawOrigin(self));
	Vector *bs = IoVector_rawVector(IoBox_rawSize(self));
	Vector *p = IoVector_rawVector(otherPoint);
	
	// do a malloc since the vectors might be large
	
	Vector *bo1 = Vector_clone(bo);
	Vector *bo2 = Vector_clone(bs);
	
	// make bo2 the box endpoint
	
	Vector_addArray_(bo2, bo1); 
	
	// ensure bo1 is on the left bottom and bo2 is on the top right
	
	Vector_Min(bo1, bo2);
	Vector_Max(bo2, bo);  
	
	result = Vector_greaterThanOrEqualTo_(p, bo1) && Vector_greaterThanOrEqualTo_(bo1, p);
	
	Vector_free(bo1);
	Vector_free(bo2);
	*/
	return IOBOOL(self, result);
}


/*

 IoObject *IoBox_Min(IoBox *self, IoObject *locals, IoMessage *m)
 { 
	 IoBox *other = IoMessage_locals_pointArgAt_(m, locals, 0);
	 if (self->x->n > DATA(other)->x->n) self->x->n = DATA(other)->x->n;
	 if (self->y->n > DATA(other)->y->n) self->y->n = DATA(other)->y->n;
	 if (self->z->n > DATA(other)->z->n) self->z->n = DATA(other)->z->n;
	 return self; 
 }
 
 IoObject *IoBox_Max(IoBox *self, IoObject *locals, IoMessage *m)
 { 
	 IoBox *other = IoMessage_locals_pointArgAt_(m, locals, 0);
	 if (self->x->n < DATA(other)->x->n) self->x->n = DATA(other)->x->n;
	 if (self->y->n < DATA(other)->y->n) self->y->n = DATA(other)->y->n;
	 if (self->z->n < DATA(other)->z->n) self->z->n = DATA(other)->z->n;
	 return self; 
 }
 */
