/* 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)

- (ChipmunkBody *)addBody:(ChipmunkBody *)obj;
- (ChipmunkBody *)removeBody:(ChipmunkBody *)obj;

@end

@implementation ChipmunkBody {
	cpBody _body;
	id _userData;
}

// MARK: Integration Helpers

-(void)updateVelocity:(cpFloat)dt gravity:(cpVect)gravity damping:(cpFloat)damping
{
	cpBodyUpdateVelocity(&_body, gravity, damping, dt);
}

-(void)updatePosition:(cpFloat)dt
{
	cpBodyUpdatePosition(&_body, dt);
}

static void
VelocityFunction
(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt)
{
	[(ChipmunkBody *)body->userData updateVelocity:dt gravity:gravity damping:damping];
}

static void
PositionFunction
(cpBody *body, cpFloat dt)
{
	[(ChipmunkBody *)body->userData updatePosition:dt];
}

// Check if the method was overridden.
// No reason to add the extra method overhead if it's not needed.
-(BOOL)methodIsOverriden:(SEL)selector
{
	return ([self methodForSelector:selector] != [[ChipmunkBody class] instanceMethodForSelector:selector]);
}

// MARK: Constructors

+(ChipmunkBody *)bodyFromCPBody:(cpBody *)body
{	
	ChipmunkBody *obj = body->userData;
	cpAssertHard([obj isKindOfClass:[ChipmunkBody class]], "'body->data' is not a pointer to a ChipmunkBody object.");
	
	return obj;
}

+ (id)bodyWithMass:(cpFloat)mass andMoment:(cpFloat)moment
{
	return [[[self alloc] initWithMass:mass andMoment:moment] autorelease];
}

+ (id)staticBody
{
	ChipmunkBody *body = [[self alloc] initWithMass:0.0f andMoment:0.0f];
	body.type = CP_BODY_TYPE_STATIC;
	
	return [body autorelease];
}

+ (id)kinematicBody
{
	ChipmunkBody *body = [[self alloc] initWithMass:0.0f andMoment:0.0f];
	body.type = CP_BODY_TYPE_KINEMATIC;
	
	return [body autorelease];
}

- (id)initWithMass:(cpFloat)mass andMoment:(cpFloat)moment
{
	if((self = [super init])){
		cpBodyInit(&_body, mass, moment);
		_body.userData = self;
		
		// Setup integration callbacks if necessary.
		if([self methodIsOverriden:@selector(updateVelocity:gravity:damping:)]){
			_body.velocity_func = VelocityFunction;
		}
		
		if([self methodIsOverriden:@selector(updatePosition:)]){
			_body.position_func = PositionFunction;
		}
	}
	
	return self;
}

- (void) dealloc
{
	cpBodyDestroy(&_body);
	[super dealloc];
}

- (cpTransform)transform {return _body.transform;}
- (cpBody *)body {return &_body;}


@synthesize userData = _userData;

// accessor macros
#define getter(type, lower, upper) \
- (type)lower {return cpBodyGet##upper(&_body);}
#define setter(type, lower, upper) \
- (void)set##upper:(type)value {cpBodySet##upper(&_body, value);};
#define both(type, lower, upper) \
getter(type, lower, upper) \
setter(type, lower, upper)


both(cpBodyType, type, Type)
both(cpFloat, mass, Mass)
both(cpFloat, moment, Moment)
both(cpVect, centerOfGravity, CenterOfGravity)
both(cpVect, position, Position)
both(cpVect, velocity, Velocity)
both(cpVect, force, Force)
both(cpFloat, angle, Angle)
both(cpFloat, angularVelocity, AngularVelocity)
both(cpFloat, torque, Torque)

-(ChipmunkSpace *)space {
	cpSpace *space = cpBodyGetSpace(&_body);
	return (ChipmunkSpace *)(space ? cpSpaceGetUserData(space) : nil);
}

- (cpFloat)kineticEnergy {return cpBodyKineticEnergy(&_body);}

- (cpVect)localToWorld:(cpVect)v {return cpBodyLocalToWorld(&_body, v);}
- (cpVect)worldToLocal:(cpVect)v {return cpBodyWorldToLocal(&_body, v);}

- (cpVect)velocityAtLocalPoint:(cpVect)p {return cpBodyGetVelocityAtLocalPoint(&_body, p);}
- (cpVect)velocityAtWorldPoint:(cpVect)p {return cpBodyGetVelocityAtWorldPoint(&_body, p);}

- (void)applyForce:(cpVect)force atLocalPoint:(cpVect)point {cpBodyApplyForceAtLocalPoint(&_body, force, point);}
- (void)applyForce:(cpVect)force atWorldPoint:(cpVect)point {cpBodyApplyForceAtWorldPoint(&_body, force, point);}
- (void)applyImpulse:(cpVect)impulse atLocalPoint:(cpVect)point {cpBodyApplyImpulseAtLocalPoint(&_body, impulse, point);}
- (void)applyImpulse:(cpVect)impulse atWorldPoint:(cpVect)point {cpBodyApplyImpulseAtWorldPoint(&_body, impulse, point);}

- (bool)isSleeping {return cpBodyIsSleeping(&_body);}

- (void)activate {cpBodyActivate(&_body);}
- (void)activateStatic:(ChipmunkShape *)filter {cpBodyActivateStatic(&_body, filter.shape);}
- (void)sleepWithGroup:(ChipmunkBody *)group {cpBodySleepWithGroup(&_body, group.body);}
- (void)sleep {cpBodySleep(&_body);}

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

static void PushShape(cpBody *ignored, cpShape *shape, NSMutableArray *arr){[arr addObject:shape->userData];}
- (NSArray *)shapes
{
	NSMutableArray *arr = [NSMutableArray array];
	cpBodyEachShape(&_body, (cpBodyShapeIteratorFunc)PushShape, arr);
	
	return arr;
}

static void PushConstraint(cpBody *ignored, cpConstraint *constraint, NSMutableArray *arr){[arr addObject:constraint->userData];}
- (NSArray *)constraints
{
	NSMutableArray *arr = [NSMutableArray array];
	cpBodyEachConstraint(&_body, (cpBodyConstraintIteratorFunc)PushConstraint, arr);
	
	return arr;
}

static void CallArbiterBlock(cpBody *body, cpArbiter *arbiter, ChipmunkBodyArbiterIteratorBlock block){block(arbiter);}
- (void)eachArbiter:(ChipmunkBodyArbiterIteratorBlock)block
{
	cpBodyEachArbiter(&_body, (cpBodyArbiterIteratorFunc)CallArbiterBlock, block);
}

@end
