/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software
 * 
 * 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.
 */

#import "ObjectiveChipmunk/ObjectiveChipmunk.h"

@interface ChipmunkSpace(DoubleDispatch)

- (ChipmunkShape *)addShape:(ChipmunkShape *)obj;
- (ChipmunkShape *)removeShape:(ChipmunkShape *)obj;

@end

@implementation ChipmunkShape

@synthesize userData = _userData;

+(ChipmunkShape *)shapeFromCPShape:(cpShape *)shape
{
	ChipmunkShape *obj = shape->userData;
	cpAssertHard([obj isKindOfClass:[ChipmunkShape class]], "'shape->data' is not a pointer to a ChipmunkShape object.");
	
	return obj;
}

- (void) dealloc {
	[self.body release];
	cpShapeDestroy(self.shape);
	[super dealloc];
}


- (cpShape *)shape {
	[self doesNotRecognizeSelector:_cmd];
	return nil;
}

- (ChipmunkBody *)body {
	cpBody *body = cpShapeGetBody(self.shape);
	return (body ? cpBodyGetUserData(body) : nil);
}

- (void)setBody:(ChipmunkBody *)body {
	if(self.body != body){
		[self.body release];
		cpShapeSetBody(self.shape, [body retain].body);
	}
}

-(cpFloat)mass {return cpShapeGetMass(self.shape);}
-(void)setMass:(cpFloat)mass {cpShapeSetMass(self.shape, mass);}

-(cpFloat)density {return cpShapeGetDensity(self.shape);}
-(void)setDensity:(cpFloat)density {cpShapeSetDensity(self.shape, density);}

-(cpFloat)moment {return cpShapeGetMoment(self.shape);}
-(cpFloat)area {return cpShapeGetArea(self.shape);}
-(cpVect)centerOfGravity {return cpShapeGetCenterOfGravity(self.shape);}

// accessor macros
#define getter(type, lower, upper) \
- (type)lower {return cpShapeGet##upper(self.shape);}
#define setter(type, lower, upper) \
- (void)set##upper:(type)value {cpShapeSet##upper(self.shape, value);};
#define both(type, lower, upper) \
getter(type, lower, upper) \
setter(type, lower, upper)

getter(cpBB, bb, BB)
both(BOOL, sensor, Sensor)
both(cpFloat, elasticity, Elasticity)
both(cpFloat, friction, Friction)
both(cpVect, surfaceVelocity, SurfaceVelocity)
both(cpCollisionType, collisionType, CollisionType)
both(cpShapeFilter, filter, Filter)

-(ChipmunkSpace *)space {
	cpSpace *space = cpShapeGetSpace(self.shape);
	return (ChipmunkSpace *)(space ? cpSpaceGetUserData(space) : nil);
}

- (cpBB)cacheBB {return cpShapeCacheBB(self.shape);}

- (ChipmunkPointQueryInfo *)pointQuery:(cpVect)point
{
	cpPointQueryInfo info;
	cpShapePointQuery(self.shape, point, &info);
	return (info.shape ? [[[ChipmunkPointQueryInfo alloc] initWithInfo:&info] autorelease] : nil);
}

- (ChipmunkSegmentQueryInfo *)segmentQueryFrom:(cpVect)start to:(cpVect)end radius:(cpFloat)radius
{
	cpSegmentQueryInfo info;
	if(cpShapeSegmentQuery(self.shape, start, end, radius, &info)){
		return [[[ChipmunkSegmentQueryInfo alloc] initWithInfo:&info start:start end:end] autorelease];
	} else {
		return nil;
	}
}


- (NSArray *)chipmunkObjects {return [NSArray arrayWithObject:self];}
- (void)addToSpace:(ChipmunkSpace *)space {[space addShape:self];}
- (void)removeFromSpace:(ChipmunkSpace *)space {[space removeShape:self];}

@end


@implementation ChipmunkPointQueryInfo

- (id)initWithInfo:(cpPointQueryInfo *)info
{
	if((self = [super init])){
		_info = (*info);
		[self.shape retain];
	}
	
	return self;
}

- (cpPointQueryInfo *)info {return &_info;}
- (ChipmunkShape *)shape {return (_info.shape ? _info.shape->userData : nil);}
- (cpVect)point {return _info.point;}
- (cpFloat)distance {return _info.distance;}
- (cpVect)gradient {return _info.gradient;}

- (void)dealloc
{
	[self.shape release];
	[super dealloc];
}


@end


@implementation ChipmunkSegmentQueryInfo

- (id)initWithInfo:(cpSegmentQueryInfo *)info start:(cpVect)start end:(cpVect)end
{
	if((self = [super init])){
		_info = (*info);
		_start = start;
		_end = end;
		
		[self.shape retain];
	}
	
	return self;
}

- (cpSegmentQueryInfo *)info {return &_info;}
- (ChipmunkShape *)shape {return (_info.shape ? _info.shape->userData : nil);}
- (cpFloat)t {return _info.alpha;}
- (cpVect)normal {return _info.normal;}
- (cpVect)point {return _info.point;}
- (cpFloat)dist {return cpvdist(_start, _end)*_info.alpha;}
- (cpVect)start {return _start;}
- (cpVect)end {return _end;}

- (void)dealloc
{
	[self.shape release];
	[super dealloc];
}


@end


@implementation ChipmunkShapeQueryInfo

@synthesize shape = _shape;
- (cpContactPointSet *)contactPoints {return &_contactPoints;}

- (id)initWithShape:(ChipmunkShape *)shape andPoints:(cpContactPointSet *)set
{
	if((self = [super init])){
		_shape = [shape retain];
		_contactPoints = *set;
	}
	
	return self;
}

- (void)dealloc {
	[_shape release];
	[super dealloc];
}

@end

@implementation ChipmunkCircleShape {
	cpCircleShape _shape;
}


+ (ChipmunkCircleShape *)circleWithBody:(ChipmunkBody *)body radius:(cpFloat)radius offset:(cpVect)offset
{
	return [[[self alloc] initWithBody:body radius:radius offset:offset] autorelease];
}

- (cpShape *)shape {return (cpShape *)&_shape;}

- (id)initWithBody:(ChipmunkBody *)body radius:(cpFloat)radius offset:(cpVect)offset {
	if((self = [super init])){
		[body retain];
		cpCircleShapeInit(&_shape, body.body, radius, offset);
		self.shape->userData = self;
	}
	
	return self;
}

- (cpFloat)radius {return cpCircleShapeGetRadius((cpShape *)&_shape);}
- (cpVect)offset {return cpCircleShapeGetOffset((cpShape *)&_shape);}

@end


@implementation ChipmunkSegmentShape {
	cpSegmentShape _shape;
}

+ (ChipmunkSegmentShape *)segmentWithBody:(ChipmunkBody *)body from:(cpVect)a to:(cpVect)b radius:(cpFloat)radius
{
	return [[[self alloc] initWithBody:body from:a to:b radius:radius] autorelease];
}

- (cpShape *)shape {return (cpShape *)&_shape;}

- (id)initWithBody:(ChipmunkBody *)body from:(cpVect)a to:(cpVect)b radius:(cpFloat)radius {
	if((self = [super init])){
		[body retain];
		cpSegmentShapeInit(&_shape, body.body, a, b, radius);
		self.shape->userData = self;
	}
	
	return self;
}

- (void)setPrevNeighbor:(cpVect)prev nextNeighbor:(cpVect)next
{
	cpSegmentShapeSetNeighbors((cpShape *)&_shape, prev, next);
}

- (cpVect)a {return cpSegmentShapeGetA((cpShape *)&_shape);}
- (cpVect)b {return cpSegmentShapeGetB((cpShape *)&_shape);}
- (cpVect)normal {return cpSegmentShapeGetNormal((cpShape *)&_shape);}
- (cpFloat)radius {return cpSegmentShapeGetRadius((cpShape *)&_shape);}

@end


@implementation ChipmunkPolyShape {
	cpPolyShape _shape;
}

+ (id)polyWithBody:(ChipmunkBody *)body count:(int)count verts:(const cpVect *)verts transform:(cpTransform)transform radius:(cpFloat)radius
{
	return [[[self alloc] initWithBody:body count:count verts:verts transform:transform radius:radius] autorelease];
}

+ (id)boxWithBody:(ChipmunkBody *)body width:(cpFloat)width height:(cpFloat)height radius:(cpFloat)radius
{
	return [[[self alloc] initBoxWithBody:body width:width height:height radius:radius] autorelease];
}

+ (id)boxWithBody:(ChipmunkBody *)body bb:(cpBB)bb radius:(cpFloat)radius
{
	return [[[self alloc] initBoxWithBody:body bb:bb radius:radius] autorelease];
}

- (cpShape *)shape {return (cpShape *)&_shape;}

- (id)initWithBody:(ChipmunkBody *)body count:(int)count verts:(const cpVect *)verts transform:(cpTransform)transform radius:(cpFloat)radius
{
	if((self = [super init])){
		[body retain];
		cpPolyShapeInit(&_shape, body.body, count, verts, transform, radius);
		self.shape->userData = self;
	}
	
	return self;
}

- (id)initBoxWithBody:(ChipmunkBody *)body width:(cpFloat)width height:(cpFloat)height radius:(cpFloat)radius
{
	if((self = [super init])){
		[body retain];
		cpBoxShapeInit(&_shape, body.body, width, height, radius);
		self.shape->userData = self;
	}
	
	return self;
}

- (id)initBoxWithBody:(ChipmunkBody *)body bb:(cpBB)bb radius:(cpFloat)radius
{
	if((self = [super init])){
		[body retain];
		cpBoxShapeInit2(&_shape, body.body, bb, radius);
		self.shape->userData = self;
	}
	
	return self;
}

- (int)count {return cpPolyShapeGetCount((cpShape *)&_shape);}
- (cpFloat)radius {return cpPolyShapeGetRadius((cpShape *)&_shape);}
- (cpVect)getVertex:(int)index {return cpPolyShapeGetVert((cpShape *)&_shape, index);}

@end
