dofile "Scripts/box2d_utils.lua"

--
-- World
--
World = {}
WorldMT = { __index = World }

function World:Create ( left, top, right, bottom )

	local world = {}
	setmetatable ( world, WorldMT )

	world.left = left
	world.top = top
	world.right = right
	world.bottom = bottom

	world.outofsync = false
	world.destroy_body_queue = {}
	world.destroy_joint_queue = {}

	world.b2world = b2CreateWorld ( left, top, right, bottom )
	world.b2world.parent = world
	world.bodies = {}
	world.joints = {}

	return world

end

function World:Destroy ( )

	self:DestroyAllJoints ( )
	self:DestroyAllBodies ( )

	self.outofsync = false
	self.destroy_body_queue = {}
	self.destroy_joint_queue = {}

	self.b2world.parent = nil
	self.b2world = nil

end

function World:Sync ( )

	while #self.destroy_body_queue > 0 do
		local last = table.remove ( self.destroy_body_queue )
		self:DestroyBody ( last )
	end

	while #self.destroy_joint_queue > 0 do
		local last = table.remove ( self.destroy_joint_queue )
		self:DestroyJoint ( last )
	end

end

function World:CreateBody ( x, y, angle )

	return self:CreateBodyEx ( x, y, angle, 0, 0, false, false, true, false )

end

function World:CreateBodyEx ( x, y, angle, linearDamping, angularDamping, fixedRotation, isBullet, allowSleep, isSleeping )

	local body = {}
	setmetatable ( body, BodyMT )

	body.x = x
	body.y = y
	body.angle = angle
	body.linearDamping = linearDamping
	body.angularDamping = angularDamping
	body.fixedRotation = fixedRotation
	body.isBullet = isBullet
	body.allowSleep = allowSleep
	body.isSleeping = isSleeping
	body.shapes = {}

	body.world = self

	body:Generate ( )

	table.insert ( self.bodies, body )

	return body

end

function World:DestroyBody ( body )

	body:Degenerate ( )

	for i, v in pairs ( self.bodies ) do
		if v == body then
			table.remove ( self.bodies, i )
			break
		end
	end

	body.world = nil

end

function World:DestroyBodySync ( body )

	table.insert ( self.destroy_body_queue, body )

end

function World:CreateJointDistance ( body1, body2, a1x, a1y, a2x, a2y )

	local joint = {}
	setmetatable ( joint, DistanceJointMT )

	joint.world = self
	joint.body1 = body1
	joint.body2 = body2
	joint.a1x = a1x
	joint.a1y = a1y
	joint.a2x = a2x
	joint.a2y = a2y

	joint:Generate ( )

	table.insert ( self.joints, joint )

	return joint

end

function World:CreateJointPulley ( body1, body2, ga1x, ga1y, ga2x, ga2y, a1x, a1y, a2x, a2y, ratio )

	local joint = {}
	setmetatable ( joint, PulleyJointMT )

	joint.world = self
	joint.body1 = body1
	joint.body2 = body2
	joint.ga1x = ga1x
	joint.ga1y = ga1y
	joint.ga2x = ga2x
	joint.ga2y = ga2y
	joint.a1x = a1x
	joint.a1y = a1y
	joint.a2x = a2x
	joint.a2y = a2y
	joint.ratio = ratio

	joint:Generate ( )

	table.insert ( self.joints, joint )

	return joint

end

function World:CreateJointRevolute ( body1, body2, ax, ay )

	local joint = {}
	setmetatable ( joint, RevoluteJointMT )

	joint.world = self
	joint.body1 = body1
	joint.body2 = body2
	joint.ax = ax
	joint.ay = ay

	joint:Generate ( )

	table.insert ( self.joints, joint )

	return joint

end

function World:DestroyJoint ( joint )

	joint:Degenerate ( )

	for i, v in pairs ( self.joints ) do
		if v == joint then
			table.remove ( self.joints, i )
			break
		end
	end

	joint.world = nil

end

function World:DestroyJointSync ( body )

	table.insert ( self.destroy_joint_queue, body )

end

function World:DestroyAllBodies ( )

	while #self.bodies > 0 do
		local last = table.remove ( self.bodies )
		last:Degenerate ( )
		last.world = nil
	end

end

function World:DestroyAllJoints ( )

	while #self.joints > 0 do
		local last = table.remove ( self.joints )
		last:Degenerate ( )
		last.world = nil
	end

end

function World:QueryPoint ( x, y, limit )

	local q = b2QueryWorld ( self.b2world, x - 1, y - 1, x + 1, y + 1, #self.bodies )

	-- filter query
	local array = {}
	local pt = b2Vec2 ( x, y )

	for i, v in pairs ( q ) do
		local b2body = v:GetBody ( )
		local xform = b2body:GetXForm ( )
		if v:TestPoint ( xform, pt ) == true then
			table.insert ( array, v.parent )
			if #array == limit then
				break
			end
		end
	end

	return array

end

function World:QueryAABB ( left, top, right, bottom, limit )

	-- aabb has to be properly aligned
	assert ( left < right )
	assert ( top < bottom )

	local q = b2QueryWorld ( self.b2world, left, top, right, bottom, limit )

	local array = {}
	for i, v in pairs ( q ) do
		table.insert ( array, v.parent )
	end

	return array

end

function World:Update ( ms )

	self.b2world:Step ( ms / 1000, 10 )

	for i, v in pairs ( self.bodies ) do
		v:Update ( )
	end

end



--
-- Joints
--
Joint = {}
JointMt = { __index = Joint }

function Joint:GetBody1 ( )

	return self.b2joint:GetBody1 ( ).parent

end

function Joint:GetBody2 ( )

	return self.b2joint:GetBody2 ( ).parent

end

function Joint:Degenerate ( )

	if self.b2joint ~= nil then
		b2DestroyJoint ( self.world.b2world, self.b2joint )

		self.b2joint.parent = nil
		self.b2joint = nil
	end

end


RevoluteJoint = {}
RevoluteJointMT = { __index = RevoluteJoint }

setmetatable ( RevoluteJoint, { __index = Joint } )

function RevoluteJoint:GetType ( )

	return 'revolute'

end

function RevoluteJoint:Generate ( )

	self:Degenerate ( )

	self.b2joint = b2CreateJointRevolute ( self.world.b2world, self.body1.b2body, self.body2.b2body, self.ax, self.ay )
	self.b2joint.parent = self

end




PulleyJoint = {}
PulleyJointMT = { __index = PulleyJoint }

setmetatable ( PulleyJoint, { __index = Joint } )

function PulleyJoint:GetType ( )

	return 'pulley'

end

function PulleyJoint:Generate ( )

	self:Degenerate ( )

	self.b2joint = b2CreateJointPulley ( self.world.b2world, self.body1.b2body, self.body2.b2body, self.ga1x, self.ga1y, self.ga2x, self.ga2y, self.a1x, self.a1y, self.a2x, self.a2y, self.ratio )
	self.b2joint.parent = self

end




DistanceJoint = {}
DistanceJointMT = { __index = DistanceJoint }

setmetatable ( DistanceJoint, { __index = Joint } )

function DistanceJoint:GetType ( )

	return 'distance'

end

function DistanceJoint:Generate ( )

	self:Degenerate ( )

	self.b2joint = b2CreateJointDistance ( self.world.b2world, self.body1.b2body, self.body2.b2body, self.a1x, self.a1y, self.a2x, self.a2y )
	self.b2joint.parent = self

end



--
-- Body
--
Body = {}
BodyMT = { __index = Body }

function Body:Generate ( )

	self:Degenerate ( )

	self.b2body = b2CreateBody ( self.world.b2world, self.x, self.y, self.angle, self.linearDamping, self.angularDamping, self.fixedRotation, self.isBullet, self.allowSleep, self.isSleeping )
	self.b2body.parent = self

	if self.OnCreate then
		self:OnCreate ( )
	end

end

function Body:Degenerate ( )

	if self.b2body then
		if self.OnDestroy then
			self:OnDestroy ( )
		end

		self:DestroyAllJoints ( )
		self:DestroyAllShapes ( )

		self.b2body.parent = nil
		b2DestroyBody ( self.b2body )
		self.b2body = nil
	end

end

function Body:DestroyAllShapes ( )

	while #self.shapes > 0 do
		local last = table.remove ( self.shapes )

		last.b2shape.parent = nil
		b2DestroyShape ( last.b2shape )
		last.b2shape = nil
	end

end

function Body:DestroyAllJoints ( )

	if self.b2body then
		-- destroy all associated joints
		local joint_edge = self.b2body:GetJointList ( )
		while joint_edge do
			local j = joint_edge.joint.parent
			for i, v in pairs ( self.world.joints ) do
				if v == j then
					table.remove ( self.world.joints, i )
					break
				end
			end
			joint_edge = joint_edge.next
		end
	end

end


function Body:Update ( )

	if self.b2body:IsSleeping ( ) then
		return
	end

	local p = self.b2body:GetPosition ( )
	local r = self.b2body:GetAngle ( )
	self.x = p.x
	self.y = p.y
	self.angle = r

	if self.OnTransform then
		self:OnTransform ( )
	end

end

function Body:GetPosition ( )

	return Point ( self.x, self.y )

end

function Body:SetPosition ( x, y )

	if b2SetXForm ( self.b2body, x, y, self.angle ) == false then
		return
	end
	self.x = x
	self.y = y

	if self.OnTransform then
		self:OnTransform ( )
	end

end

function Body:GetAngle ( )

	return self.angle

end

function Body:SetAngle ( r )

	b2SetXForm ( self.b2body, self.x, self.y, r )
	self.angle = r

	if self.OnTransform then
		self:OnTransform ( )
	end

end

function Body:GetMass ( )

	return self.b2body:GetMass ( )

end

function Body:CreateShapeCircle ( radius, density, friction, restitution, isSensor )

	local shape = {}
	setmetatable ( shape, ShapeCircleMT )

	shape.body = self
	shape.density = density
	shape.friction = friction
	shape.restitution = restitution
	shape.isSensor = isSensor
	shape:SetRadius ( radius )

	shape:Generate ( )

	table.insert ( self.shapes, shape )
	return shape

end

function Body:CreateShapePolygon ( vertices, density, friction, restitution, isSensor )

	local shape = {}
	setmetatable ( shape, ShapePolygonMT )

	shape.body = self
	shape.density = density
	shape.friction = friction
	shape.restitution = restitution
	shape.isSensor = isSensor
	shape:SetVertices ( vertices )

	shape:Generate ( )

	table.insert ( self.shapes, shape )
	return shape

end

function Body:DestroyShape ( shape )

	shape.b2shape.body = nil
	b2DestroyShape ( shape.b2shape )
	shape.b2shape = nil

	for i, v in pairs ( self.shapes ) do
		if v == shape then
			table.remove ( self.shapes, i )
			break
		end
	end

end

function Body:GetWorld ( )

	return self.world

end







--
-- Shapes
--
ShapeCircle = {}
ShapeCircleMT = { __index = ShapeCircle }

function ShapeCircle:Generate ( )

	self:Degenerate ( )

	self.b2shape = b2CreateShapeCircle ( self.body.b2body, self.radius, self.density, self.friction, self.restitution, self.isSensor )
	self.b2shape.parent = self

end

function ShapeCircle:Degenerate ( )

	if self.b2shape then	
		self.b2shape.parent = nil
		b2DestroyShape ( self.b2shape )
		self.b2shape = nil
	end

end

function ShapeCircle:GetType ( )

	return 'circle'

end

function ShapeCircle:GetBody ( )

	return self.body

end

function ShapeCircle:SetRadius ( radius )

	-- assert the radius is valid
	assert ( radius > 0 )

	self.radius = radius

end





ShapePolygon = {}
ShapePolygonMT = { __index = ShapePolygon }

function ShapePolygon:Generate ( )

	self:Degenerate ( )

	self.b2shape = b2CreateShapePolygon ( self.body.b2body, self.vertices, self.density, self.friction, self.restitution, self.isSensor )
	self.b2shape.parent = self

end

function ShapePolygon:Degenerate ( )

	if self.b2shape then	
		self.b2shape.parent = nil
		b2DestroyShape ( self.b2shape )
		self.b2shape = nil
	end

end

function ShapePolygon:GetType ( )

	return 'polygon'

end

function ShapePolygon:GetBody ( )

	return self.body

end

function ShapePolygon:SetVertices ( vertices )

	-- assert the polygon is convex
	assert ( vertices and #vertices >= 3 )
	local ax = vertices[1].x
	local ay = vertices[1].y
	local bx = vertices[2].x
	local by = vertices[2].y

	for i = 1, #vertices do
		local c = i + 2
		if c > #vertices then
			c = c - #vertices
		end
		local cx = vertices[c].x
		local cy = vertices[c].y

		-- signed area of a triangle
		assert ( (ax - cx) * (by - cy) - (ay - cy) * (bx - cx) > 0 )

		-- positive if abc is counterclockwise
		-- negative if abc is clockwise
		-- zero if abc is a degenerate

		ax = bx
		ay = by
		bx = cx
		by = cy
	end

	self.vertices = vertices

end








