func = package.loadlib ( "Plugins/Box2D.dll", "luaopen_Box2D" )
func ( )

dofile ( "Scripts/effects.lua" )

Game = {}
GameMT = { __index = Game }

function Game:Create ( )
	local game = {}
	setmetatable ( game, GameMT )

	game.head = nil
	game.count = 0
	game.next_color = 0


	game.state = 'paused'
	game.effects = EffectsManager:Create ( )

	return game
end

function Game:OnClear ( count, x, y )

end

function Game:OnDie ( body )

end

function Game:Select ( body )
	if body.selected == true then
		return
	end
	if body.state ~= 'falling' then
		return
	end

	if audio ~= nil and sound_volume > 0 then
		if self.count == 0 then
			local effect = Play:Create ( "Sounds/deselect" .. math.random ( 1, 3 ) .. ".wav", sound_volume )
			effects:AddEffect ( effect, 0, 0 )
		else
			local effect = Play:Create ( "Sounds/link" .. math.random ( 1, 6 ) .. ".wav", sound_volume )
			effects:AddEffect ( effect, 0, 0 )
		end
	end


	local ratio = ( body.shapes[1].radius - 2 ) / 48
	local s = Sprite ( body.x, body.y )
	s.canvas:set_source_image ( selector )
	s.canvas:paint ( )
	s.scalex = ratio
	s.scaley = ratio
--	s.color = colors[body.color]
	s.depth = body.sprite.depth - 1
	scene:add_child ( s )
	local effect = Fade:Create ( s, 0 )
	self.effects:AddEffect ( effect, 300, 50 )
	local effect = Resize:Create ( s, ratio * 1.75, ratio * 1.75 )
	self.effects:AddEffect ( effect, 350, 0 )
	local effect = Remove:Create ( s, scene )
	self.effects:AddEffect ( effect, 0, 351 )

	if self.head then
		body.joint = world:CreateJointDistance ( body, self.head, body.x, body.y, self.head.x, self.head.y )
		body.joint.type = 'balljoint'
		body.joint.color = self.head.color

		self.head.ishead = false
		self.head:Paint ( )
	end

	self.count = self.count + 1
	body.next = self.head
	self.head = body

	body.selected = true
	body.ishead = true
	body:Paint ( )
end



function Game:Deselect ( body )
	if body.selected ~= true then
		return
	end


	-- the chain has been severed
	if body == self.head then
		body.ishead = false
		body.selected = false
		body:Paint ( )

		self.head = body.next
		if self.head then
			self.head.ishead = true
			self.head:Paint ( )
		end

		world:DestroyJointSync ( body.joint )
		body.joint = nil
		body.next = nil
		self.count = self.count - 1
	else
		local it = self.head
		while it do
			if it.next == body then
				world:DestroyJointSync ( it.joint )
				it.joint = nil
				it.next = nil
				break
			end
			it = it.next
		end


		local it = body
		while it do
			-- this function can be called during box2d callbacks
			-- so make sure you don't destroy the joint directly
			world:DestroyJointSync ( it.joint )
			it.joint = nil

			local last = it
			it = it.next
			last.next = nil

			last.selected = false
			last:Paint ( )
			self.count = self.count - 1
		end
	end

end

function Game:DeselectAll ( )
	if self.count == 0 then
		return
	end

	if audio ~= nil and sound_volume > 0 then
		local effect = Play:Create ( "Sounds/deselect" .. math.random ( 1, 3 ) .. ".wav", sound_volume )
		effects:AddEffect ( effect, 0, 0 )
	end


	local it = self.head
	while it do
		if it.joint ~= nil then
			world:DestroyJoint ( it.joint )
			it.joint = nil
		end

		local next = it.next
		it.ishead = false
		it.selected = false
		it:Paint ( )
		it.next = nil
		it = next
	end
	self.count = 0
	self.head = nil
end

function Game:ClearAll ( )
	if game.count < 3 then
		return
	end

	self:OnClear ( self.count )

	local delay = self.count * 50

	local it = self.head
	while it do
		if it.joint then
			world:DestroyJoint ( it.joint )
			it.joint = nil
		end

		local next = it.next
		it.selected = false
		it.next = nil
		self:ExplodeBall ( it, delay )
		it = next
		delay = delay - 50
	end

	self.head = nil
	self.count = 0
end


function Game:ExplodeBall ( ball, delay )
	if ball.state ~= 'falling' then
		return false
	end

	ball.state = 'exploding'

	local d = ball.sprite.scalex + 0.2
	local effect = Resize:Create ( ball.sprite, d, d )
	self.effects:AddEffect ( effect, 500, delay )
	local effect = Fade:Create ( ball.sprite, 0 )
	self.effects:AddEffect ( effect, 500, delay )
	local effect = BodyDestroy:Create ( ball )
	self.effects:AddEffect ( effect, 0, delay + 500 )

	return true
end

function Game:ContractBall ( ball )
	if ball.state ~= 'falling' then
		return false
	end

	ball.state = 'contracting'

	local effect = Resize:Create ( ball.sprite, 0, 0 )
	self.effects:AddEffect ( effect, 500, 0 )
	local effect = BodyDestroy:Create ( ball )
	self.effects:AddEffect ( effect, 0, 500 )

	return true
end


function Game:Pick ( x, y )
	if self.count >= 3 then
		-- clear selection
		self:ClearAll ( )
	end

	local w = Point ( x, y )
	w = camera:get_world_point ( w )
	local a = world:QueryPoint ( w.x, w.y, 1 )
	if #a > 0 then
		local b = a[1]:GetBody ( )
		if b.type == 'ball' and b.state == 'falling' then
			-- picked an already selected ball
			if self.count == 1 and b.selected == true then
				return
			end
			-- begin new selection
			self:DeselectAll ( )
			self:Select ( b )
			return
		elseif b.type == 'protoball' then
			b.type = 'ball'
			b.color = self.next_color
			b:Paint ( )

			if audio ~= nil and sound_volume > 0 then
				local effect = Play:Create ( "Sounds/pick.wav", sound_volume )
				effects:AddEffect ( effect, 0, 0 )
			end


			self.next_color = self:GetNextColor ( )
			return
		end
	end

	-- canceled selection
	self:DeselectAll ( )
end

function Game:GetNextColor ( )
	local cs = {}
	for i, v in pairs ( colors ) do
		table.insert ( cs, 0 )
	end
	for i, v in pairs ( world.bodies ) do
		if v.type == 'ball' and v.state == 'falling' then
			cs[v.color] = cs[v.color] + 1
		end
	end
	local n = math.random ( #colors )
	while cs[n] == 0 do
		n = math.random ( #colors )
	end
	return n
end

function Game:Attach ( x, y )
	local w = Point ( mouse.xaxis, mouse.yaxis )
	w = camera:get_world_point ( w )
	local a = world:QueryPoint ( w.x, w.y, 1 )
	if #a > 0 then
		local b = a[1]:GetBody ( )
		if b.type == 'ball' and b.state == 'falling' then
			if self.head then
				-- adding to selection
				if b.color == self.head.color then
					-- close enough to the tail?
					if self.head:Distance ( b ) < 20 then
						if b.selected ~= true then
							self:Select ( b )
						else
							if self.head and self.head.next == b then
								self:Deselect ( self.head )
							end
						end
					end
				end
			else
				-- highlighting
			end
		end
	end
end


local rotator = 0

function Game:Update ( ms )
	world:Sync ( )

	rotator = rotator + ( ms / 16 ) * 2
	if rotator > 360 then
		rotator = rotator - 360
	end

	-- update the level
	if ms > 64 then
		ms = 64
	end

	self.effects:Update ( ms )
	UpdateLevel ( ms )
end

function Game:Paint ( )
	overlay.canvas:clear ( )

	if world == nil then
		return
	end


	if self.head then
		local radius = self.head.shapes[1].radius
		local dpp = 360 / ( 2 * 3.147 * ( radius + 16 ) ) -- degrees per pixel

		local pt2 = Point ( 0, radius + ( 16 ) )
		local pt3 = Point ( 0, radius + ( 16 ) )

		for i = rotator, rotator + 359, 120 do
			pt2:set_angle ( i - dpp * 5 )
			pt3:set_angle ( i + dpp * 5 )

			local pt = Point ( ( pt3.x - pt2.x ) / 2 + pt2.x, ( pt3.y - pt2.y ) / 2 + pt2.y )
			pt:change_radial ( 5 )

			overlay.canvas:move_to ( self.head.x + pt.x, self.head.y + pt.y )
			overlay.canvas:line_to ( self.head.x + pt2.x, self.head.y + pt2.y )
			overlay.canvas:line_to ( self.head.x + pt3.x, self.head.y + pt3.y )
			overlay.canvas:close_path ( )
		end
		overlay.canvas:set_fill_style ( colors[self.head.color], 1 )
		overlay.canvas:fill ( )
	end


	-- draw all joints
	for i, v in pairs ( world.joints ) do
		if v.type ~= 'balljoint' then
			v:Plot ( overlay.canvas )
			overlay.canvas:set_line_style ( 2, BLACK, 1 )
			overlay.canvas:stroke ( )
		end
	end

	if self.head ~= nil then
		local c = colors[self.head.color]
		overlay.canvas:move_to ( self.head.x, self.head.y )
		local it = self.head.next
		while it do
			overlay.canvas:line_to ( it.x, it.y )
			it = it.next
		end
		overlay.canvas:set_line_style ( 5, c, 0.5 )
		overlay.canvas:stroke_preserve ( )
		overlay.canvas:set_line_style ( 1, BLACK, 0.25 )
		overlay.canvas:stroke ( )
	end
end

function Game:Load ( level )
	dofile ( level )

	world = LoadLevel ( )


	self.next_color = math.random ( #colors )

	bl = b2BoundaryListener ( )
	bl.Leave = function ( b )
		local body = b.parent
		if ( body.type == 'ball' ) then
			self:Deselect ( body )
			self:ContractBall ( body )
		end
	end
	world.b2world:SetBoundaryListener ( bl )


	cl = b2ContactListener ( )
	cl.Add = function ( cp )
		local body1 = cp.shape1:GetBody ( ).parent
		local body2 = cp.shape2:GetBody ( ).parent
		if ( body1.type == 'ball' and body2.type == 'deathone' ) then
			if body1.state == 'falling' then
				self:Deselect ( body1 )
				self:ContractBall ( body1 )
				self:OnDie ( body1 )
			end
		elseif ( body1.type == 'deathzone' and body2.type == 'ball' ) then
			if body2.state == 'falling' then
				self:Deselect ( body2 )
				self:ContractBall ( body2 )
				self:OnDie ( body2 )
			end
		end
	end
	world.b2world:SetContactListener ( cl )
end

function Game:Unload ( )
	self:DeselectAll ( )

	-- purge the effects queue
	while self.effects:IsDone ( ) == false do
		self.effects:Update ( 16 )
	end

	if world then
		UnloadLevel ( )
		world = nil
	end
end

