sounds = {
	tap1 = sound.load("tap1.wav"),
	tap2 = sound.load("tap2.wav"),
	tap3 = sound.load("tap3.wav"),
	tap4 = sound.load("tap4.wav"),
	tap6 = sound.load("tap6.wav"),
	point = sound.load("point.wav")
}


local function updateWindowDims (sz) 
	screenwidth,screenheight = 500*sz,800*sz
	if (window.fullscreen()) then screenwidth,screenheight = 800,600 end
	screenratio = 500/800--screenwidth/screenheight
	--window.resstring(string.format("%dx%dx32",screenwidth,screenheight))
	window.width(screenwidth)
	window.height(screenheight)
	window.refsize(screenwidth,screenheight)
	window.update()	
end
--registry.save("system_resolution",false)
confignosave("window_width")
confignosave("window_height")
updateWindowDims(0.8)

l3dcamera.default():frontplane(8)
l3dcamera.default():backplane(32)
l3dcamera.default():fov(-7.5)

space = dspacehash.new()

do
	--lset = l3dset.getnormal()
	local r,g,b = 0.8,0.8,0.8
--	l3dset.getdefaultview(l3dset.getbackground()):backgroundcolor(r,g,b,1)
	lview = l3dset.get(0):getdefaultview()
	lset = l3dset.get(0)

	lview = UtilFunctions.simplerenderqueue(lview)
	
	lview.rClear:colorvalue(0.7,0.7,0.7,0)
	
	
	layer = lset:layer(0)
	layer1 = lset:layer(1)
	layer2 = lset:layer(2)
	layer3 = lset:layer(3)
	layer4 = lset:layer(4)
	sunl3d = l3dlight.new("sun",layer)
	sunl3d:makesun()
	sun = actornode.new("")
	sun:pos(-3000000,-1700000,1000000)
	sunl3d:diffuse(1.2,1.2,1.2,0)
	local amb = 0.4
	sunl3d:ambient(amb,amb,amb,0)
	sunl3d:linkinterface(sun)
end


static = {
	space = dspacehash.new(),
	geomlut = {},
	list = {}
}

static.space = dspacehash.new()
static.space:collidetest(false)
space:add(static.space)

function static.new (parentclass,l3d,geom)
	local self = {
		s3d = scenenode.new("",true,0,0,0),
		l3ds = {},
		geoms = {},
	}
	setmetatable(self,{__index = parentclass or static})
	if (geom) then
		table.insert(self.geoms,geom)
		static.geomlut[geom] = self
		static.space:add(geom)
	end
	if (l3d) then
		l3d:linkinterface(self.s3d)
		l3d:rfLitSun(true)
		table.insert(self.l3ds,l3d)
	end
	
	table.insert(static.list,self)
	return self
end

function static.delete (self) 
	for i,l3d in ipairs (self.l3ds) do -- delete all l3ds
		l3d:delete() 
	end
	for i,geom in ipairs (self.geoms) do -- delete all geoms
		entity.geomlut[geom] = nil -- remove the geom from the lut
		geom:delete()
	end
	self.s3d:delete()
	for i,ent in ipairs(static.list) do
		if (ent == self) then 
			table.remove(static.list,i)
			break
		end
	end	
end

function static.pos (self,x,y,z)
	if (not x) then return self.s3d:worldpos() end
	self.s3d:localpos(x,y,z)
	for i,geom in ipairs(self.geoms) do
		geom:pos(x,y,z)
	end
end


staticbox = {}
setmetatable(staticbox,{__index = static})
function staticbox.new (position,size)
	local self = static.new(self,
		l3dprimitive.newbox("sbox",layer,unpack(size)),
		dgeombox.new(unpack(size)))

	self:pos(unpack(position))
	
	return self
end

entity = {
	list = {}, -- list of all entities. The order of enties may change if 
				-- entities are removed
	inactives = {},
	inactthink = 1,
	inactthinksize = 50,
				
	geomlut = {},-- dgeom reverse LookUpTable (find out entity if geom is knonw)
	
	space = dspacehash.new() -- the space the entities are put into
}

space:add(entity.space)


---
-- constructs new entity that is inserted in the entity.list 
-- if l3d or geom is passed, it is automaticly linked with the actor
function entity.new (parentclass,l3d,geom)
	local self = {
		l3ds = {}, -- the l3ds that are associated with this entity
		body = dbody.new(), -- the body for the simulation
		actor = actornode.new(""), -- the actor for position representation
		geoms = {}, -- the geoms of the body, inserted in entity.space
		sounds = {}, -- list of soundnodes
	}
	setmetatable(self,{__index = parentclass or entity})
	
	self.actor:link (self.body) -- link the actor to the body
	if (geom) then  -- a geom has been passed...
		geom:body(self.body) -- link the geom with the body
		table.insert(self.geoms,geom) -- insert the geom in the geomlist
		entity.space:add(geom) -- add it to the entity space
		entity.geomlut[geom] = self -- insert it into the lookuptable
	end
	if (l3d) then 
		l3d:linkinterface(self.actor) 
		table.insert(self.l3ds,l3d) 
		l3d:rfLitSun(true)
	end
	
	table.insert(entity.list,self)
	self.id = table.getn(entity.list)
	self.eactive = true
		
	return self
end

function entity.addl3d (self,l3d)
	l3d:linkinterface(self.actor) 
	table.insert(self.l3ds,l3d) 
	l3d:rfLitSun(true)	
end

function entity:color(r,g,b,a)
	for i,l3d in ipairs(self.l3ds) do
		l3d:color(r,g,b,a)
	end
end

function entity.deactivate(self)
	if (not self.eactive) then return end	
	local replace = table.remove(entity.list)
	if (self ~= replace) then
		entity.list[self.id] = replace
		replace.id = self.id
	end
	table.insert(entity.inactives,self)
	self.id = table.getn(entity.inactives)
	self.eactive = false
end

function entity.activate (self)
	if (self.eactive) then return end
	local replace = table.remove(entity.inactives)
	if (self ~= replace) then
		entity.inactives[self.id] = replace
		replace.id = self.id
	end
	table.insert(entity.list,self)
	self.id = table.getn(entity.list)
	self.eactive = true
end

function entity.addsound(self,snd)
	local node = soundnode.new(snd)
	node:autodelete(false)
	node:loop(false)
	table.insert(self.sounds,node)
	return node
end

---
-- deletes the entity and cleans stuff up
function entity.delete (self,nounlist)
	for i,l3d in ipairs (self.l3ds) do -- delete all l3ds
		l3d:delete() 
	end
	for i,geom in ipairs (self.geoms) do -- delete all geoms
		entity.geomlut[geom] = nil -- remove the geom from the lut
		geom:delete()
	end
	for i,sound in ipairs(self.sounds) do
		sound:delete()
	end
	
	self.body:delete()
	self.actor:delete()
	if (self.eactive) then
		local rem = table.remove(entity.list)
		if (rem~=self) then
			entity.list[self.id] = rem
			rem.id = self.id
		end
	else
		local rem = table.remove(entity.inactives)
		if (rem~=self) then
			entity.inactives[self.id] = rem
			rem.id = self.id
		end
	end
	
end

function entity.deleteall (self)
	while (table.getn(entity.list)>0) do
		entity.delete(entity.list[1],true)
	end
	while (table.getn(entity.inactives)>0) do
		entity.delete(entity.inactives[1],true)
	end
end


function entity:pos (x,y,z)
	if (not x) then return self.body:pos() end
	self.body:pos(x,y,z)
end

function entity:rot (x,y,z)
	if (not x) then return self.body:rot() end
	self.body:rot(x,y,z)
end


---
-- empty function for thinkcycles
function entity.think (self) self:deactivate() end

---
-- calls entity.think for each entity in the list
function entity.thinkall ()
	local n = table.getn(entity.inactives)
	for i = entity.inactthink,entity.inactthink + entity.inactthinksize do
		n = table.getn(entity.inactives)
		if (i>n) then break end
		local v = entity.inactives[i]
		if (not entity.inactives[i]) then _print(i,n) end
		v:think()
		if (v.eactive) then
			i = i-1
			n = n-1
		end
	end
	entity.inactthink = (entity.inactthink + entity.inactthinksize > n) and 1 or 
						(entity.inactthink + entity.inactthinksize)
	
	for i,v in ipairs(entity.list) do
--	_print(v) 
		v:think()
		if (not v.eactive) then i = i-1 end
	end
end

box = {}
setmetatable(box,{__index = entity})
function box.new (parentclass,position, size, density)
	local l3d = l3dprimitive.newbox("box",layer,unpack(size))
	local l3d2 = l3dprimitive.newbox("box",layer1,unpack(size))
	local l3d3 = l3dprimitive.newbox("box",layer1,unpack(size))
	l3d2:rfWireframe(true)
	l3d2:rsLinewidth(screenwidth/400)
	l3d3:rfWireframe(true)
	l3d3:rsLinewidth(screenwidth/800)
	l3d3:rsLinefactor(screenwidth/400)
	l3d3:rfNocull(true)
	l3d3:rfNodepthtest(true)
	l3d3:rsLinestipple(string.rep("1000",4))
	local self = entity.new(parentclass or box,
		l3d,
		dgeombox.new(unpack(size)))
	self:addl3d(l3d2)
	self:addl3d(l3d3)
	l3d2:color(0,0,0,1)
	l3d3:color(0,0,0,1)
	l3d2:rfLitSun(false)
	l3d3:rfLitSun(false)
	self.body:massbox(density,unpack(size))
	self.body:pos(unpack(position))
	return self
end

hitpsys = {
	psys = particlesys.load("hit.prt")
}
function hitpsys.spawn (x,y,z,nx,ny,nz, fx)
	local actor = actornode.new("spawner",x,y,z)
	local pem = l3dpemitter.new("hitspawn",layer4,hitpsys.psys)
	local xx,xy,xz = unpack(ExtMath.v3normalize({nx,ny,nz}))
	local zx,zy,zz = 0,0,1
	local yx,yy,yz = unpack(ExtMath.v3cross({zx,zy,zz},{xx,xy,xz}))
	pem:startage(500-math.min(500,fx))
	--print(fx)
	--print(xx,xy,xz, zx,zy,zz, yx,yy,yz)
	actor:rotaxis(zx,zy,zz,xx,xy,xz, yx,yy,yz)
	pem:linkinterface(actor)
	pem:start(0)
	TimerTask.new(function () actor:delete() pem:delete() end, 150)
end

ball = {
	
}
setmetatable(ball,{__index = entity})
function ball.new (position, rad, density)
	local self = entity.new(box,
		l3dprimitive.newsphere("box",layer1,rad),
		dgeomsphere.new(rad))
	local trail = l3dtrail.new("balltrail",35,layer3)
	self:addl3d(trail)
	trail:spawnstep(10)
	trail:size(0,0.18)
	trail:size(1,0.05)
	trail:size(2,0)
	trail:color(0,1,0,0,0.5)
	trail:color(1,1,0,0,0.25)
	trail:color(2,1,0,0,0)
	trail:rfNocull(true)
	trail:rfBlend(true)
	trail:rsBlendmode(blendmode.decal())
	
	local pfc = particleforceid.newmagnet(hitpsys.psys,"name")
	pfc:range(rad*4)
	pfc:strength(-0.5)
	pfc:linkworldref(self.actor)
	--pfc:vector(unpack(vector))
	pfc:activate()--]]
	--trail:rotationlock(3)
	--trail:uselocal(true)
	--trail:localrotdeg(90,90,0)
	trail:facecamera(true)
	self.l3ds[1]:color(1,0,0,1)
	local border = l3dprimitive.newsphere("box",layer,rad+0.03)
	border:color(0,0,0,1)
	border:rfNodepthmask(true)
	self:addl3d(border)
	self.body:masssphere(density,rad)
	self.body:pos(unpack(position))
	self.body:rotdrag(0.4,0.4,0.4)
	trail:rfLitSun(false)
	return self
end

player = {}
setmetatable(player,{__index = entity})
function player.new ()
	local self = box.new(player,{0,0,-3.5},{screenratio*2,screenratio*0.8,screenratio*0.4},0.045/screenratio)
	self.body:gravity(false)
	self.body:lock(2,0)
	self.body:autodisable(false)
end

key = {
	left = Keyboard.keycode.LEFT,
	right = Keyboard.keycode.RIGHT,
	down = Keyboard.keycode.DOWN,
	reset = Keyboard.keycode.SPACE
}

function player.think (self)
	local fx = (Keyboard.isKeyDown(key.left) and -1 or 0)+(Keyboard.isKeyDown(key.right) and 1 or 0)
	local fy = (Keyboard.isKeyDown(key.down) and 0 or 1)
	
	local inp = {input.joystick()}	
	
	if (inp[1]) then fx = fx + inp[5] or 0 fy = (inp[inp[3]+5] and 0 or 1) end -- I asume here that every joystick has 2 axis ...
	
	local x,y,z = self.body:pos()
	self.body:addforce(fx*0.0002,0,-(z+3.5)*0.001+fy*0.00035)
	x,y,z = self.body:velocity()
	self.body:velocity(x*0.96,y*0.93,z*0.93)
	local a,b,c = self.body:rotrad()
	x,y,z = self.body:angvel()
	self.body:angvel(x*0.5-a*0.2,y*0.5-b*0.2+fx*0.05,z*0.5-c*0.2)
	self.geoms[1]:surfaceid(1)
	dworld.surfacecombination(1,0,1)
	self.l3ds[1]:color(1,0.8,0,1)

	
	for i = 0, self.body:jointcount()-1 do
		local j = self.body:joint(i)	
		local g = j:geom2()==self.geoms[1] and j:geom1() or j:geom2()
		local b = j:body2()==self.body and j:body1() or j:body2()
		if (b == pball.body) then 
			local n = j:body2()==self.body and true or false
			local xf1,yf1,zf1, xt1,yt1,zt1, xf2,yf2,zf2, xt2,yt2,zt2 = j:feedback()
			local x,y,z = n and xf1 or xf2,n and yf1 or yf2, n and zf1 or zf2
			x,y,z = x*0.5,y*0.5,z
			local d = ((x*x+y*y+z*z)^0.5) * 8000
			if (d>0) then
				local x,y,z = x/d,y/d,z/d
				local px,py,pz = self.body:world2local(j:pos())
				if (math.abs(px)<0.7 and pz>0) then
					--_print(px,py,pz)
					b:addforce(px*0.001,0,fy*0.001)
				end
			end
		elseif(b~=nil) then
			local x,y,z = self.body:world2local(b:pos())
			if (z>0.1) then 
				local x,y,z = b:velocity()
				local v = (x*x+y*y+z*z)^0.5
				b:state(true)
				if (v<0.05) then
					b:addforce(0,0,math.max(0,fy*0.001))
				end
			end
		end
	end
end



camnode = actornode.new("camera")
l3dcamera.default():linkinterface(camnode)
camnode:pos(0,-20,0)

--dworld.surfacebitsoftcfm(0,false)
dworld.iterations(4)
dworld.surfacebitsoftcfm(0,false)
dworld.surfacebitsofterp(0,false)
dworld.surfacebitslip1(0,false)
dworld.surfacebitslip2(0,false)
dworld.surfacebitapprox1(0,false)
dworld.surfacemu(0,1)

dworld.surfacebitsofterp(1,false)
dworld.surfacebitslip1(1,false)
dworld.surfacebitslip2(1,false)
dworld.surfacebitapprox1(1,false)


dworld.gravity(0,0,-0.002)
dworld.autodisable(true)
dworld.autodisablesteps(1)
dworld.autodisableang(0.04)
dworld.autodisabletime(200)
dworld.autodisablevel(0.01)
dworld.autodisable(true)
dworld.contactsurfacelayer(0.01)

do 
	local B
	local r,g,b = 0,0,0
	B = staticbox.new({0,0,-7.05},{20,3,2.5}) B.geoms[1]:surfaceid(4) B.l3ds[1]:color(r,g,b,1)
	B = staticbox.new({screenratio*12.2,0,0},{8,3,20})   B.l3ds[1]:color(r,g,b,1)
	B = staticbox.new({-screenratio*12.2,0,0},{8,3,20})  B.l3ds[1]:color(r,g,b,1)
	B = staticbox.new({0,0,7.05},{20,3,2.5})B.geoms[1]:surfaceid(4) B.l3ds[1]:color(r,g,b,1)
end


dofile("levels.lua")
do -- gui ----------------------------------------------------------------------
	MouseCursor.showMouse(true)

	local level = 1
	local gameruns = false
	local root = Container.getRootContainer()
	local menu = 	root:add(GroupFrame:new(10,10,100,50))

	menu:add(Label:new(20,20,100,16,"Actinoids"))
	local startbtn = menu:add(Button:new(10,10,120,28,"Start Game"))
	local quit = menu:add(Button:new(10,10,120,28,"Quit"))
	startbtn:focus()
	function quit:onClicked () system.exit() end
	
	local scoremenu = root:add(GroupFrame:new(10,10,120,50))
	local scorelabel = scoremenu:add(Label:new(20,20,80,16,"00000000",false,Label.LABEL_ALIGNRIGHT,1))
	scoremenu:setVisibility(false)

	local levelfinishedmenu = root:add(GroupFrame:new(10,10,240,240))

	levelfinishedmenu:setVisibility(false)

	score = {
		ticks = 0,
		bricks = 0,
		bonus = 0,
		lastscore = 0,
		ballfetches = 0,
	}
	function score.set(points)	
		scorelabel:setText(string.format("%s%0.8d",points>=0 and "+" or "\v900",points)) 
	end
	function score.update()	score.set(score.getscore())	end
	function score.tick () score.ticks = score.ticks + 1  scoretext_think() end
	function score.getscore()
		return score.bricks - score.ballfetches*500 + score.bonus
	end

	local cam = l3dcamera.default()
	local scoretexts = {}
	local usedscoretexts = {}
	for i=1,40 do 
		local l2dshad = l2dtext.new("score","",0)
		l2dshad:rfNodraw(true)
		l2dshad:spacing(8)
		local l2d = l2dtext.new("score","",0)
		l2d:rfNodraw(true)
		l2d:spacing(8)		
		table.insert(scoretexts,{l2d,l2dshad})
	end
	
	function scoretext_new (points,x,y)
		local l2d,shad = unpack(table.remove(scoretexts) or {})
		if (not l2d) then return end
		table.insert(usedscoretexts,{
			l2d = l2d, shad = shad,
			time = system.time(),x=x,y=y, 
			len = string.len(tostring(points)),
			points = points})
		l2d:pos(x,y,0)
		shad:pos(x+1.5,y+1.5,0)
		l2d:text("\vb"..tostring(points))
		shad:text("\vb"..tostring(points))
		l2d:color(0,0.8,0,1)
		shad:color(0,0,0,1)
		l2d:rfNodraw(false)
		shad:rfNodraw(false)
		l2d:scale(1,1,1)
		shad:scale(1,1,1)
	end
	function scoretext_think()
		local t = system.time()
		for i,tx in ipairs(usedscoretexts) do
			local age = t - tx.time
			if (age>1000) then 
				table.remove(usedscoretexts,i)
				i = i -1  
				table.insert(scoretexts,{tx.l2d,tx.shad})
				tx.l2d:rfNodraw(true)
				tx.shad:rfNodraw(true)
			else
				local x,y = tx.x,tx.y - age*0.1
				local sc =(age*0.002)+0.5
				x = x + math.sin(age*0.01+x+tx.points*0.2)*2
				tx.l2d:pos(x-sc*tx.len/2*16,y,0)
				tx.shad:pos(x+1.5-sc*tx.len/2*16,y+1.5,0)		
				tx.shad:scale(sc,sc,sc)
				tx.l2d:scale(sc,sc,sc)
				tx.l2d:color(0,0.8,0,1-age*0.001)
				tx.shad:color(0,0,0,1-age*0.001)
			end
		end
	end

	local combopitches = {
		0.2,0.4,0.6,0.8,1,1.2,1.4,1.6,1.8,2.0
	}
	
	function score.bricked (brick)
		local prev = score.getscore()
		score.bricks = score.bricks + 1
		local bonus = math.max(0,(score.lastscore+200-score.ticks))
		if (bonus==0) then
			score.combo = 0
		else
			score.combo = (score.combo or 0) + 1
		end
		score.bonus = score.bonus + bonus + score.combo^2
		local dif =  score.getscore()-prev
		local x,y,z =brick:pos()
		x,y,z = cam:toscreen(x,y,z)
		scoretext_new(dif,x,y)
		score.lastscore = score.ticks
		score.update()
		local snd = soundnode.new(sounds.point)
		local n = math.min(table.getn(combopitches),score.combo+1)
		--_print(n)
		snd:gain(1.5)
		snd:pitch(0.05)--math.min(2,score.combo*0.05+0.05))
		snd:play()
		--sounds.point:play()
	end

	function score.ballfetch()
		score.ballfetches = score.ballfetches + 1
		score.update()
	end
	
	function startbtn:onClicked()
		MouseCursor.showMouse(false)
		menu:setVisibility(false)
		scoremenu:setVisibility(true)
		scoremenu:layout()
		score.set(0)
		gameruns = true
		level = 1
		start(levels[1])
	end
	
	function scoremenu:layout()
		local w,h = self:getSize()
		self:setBounds((screenwidth-w)/2,-20,w,h)
	end
	
	function menu:layout()
		local w = 100
		local h = 110
		local y = 20
		self:setBounds((screenwidth-w)/2,(screenheight-h)/2,w,h)
		
		for i,c in pairs(self.components) do
			local mw,mh = c:getMinSize()
			local x = (w-mw)/2
			
			c:setBounds(x,y,mw,mh)
			
			y = y + mh+4
		end
	end
	
	local upd = updateWindowDims
	function updateWindowDims (sz) 
		upd(sz)
		
		menu:layout()
		scoremenu:layout()
	end
	
	function endlevel ()
		if (gameruns) then
			level = math.mod(level ,table.getn(levels))+1
			start(levels[level])
		end
	end
	
	updateWindowDims(0.8)
end ----------------------------------------------------------------------------


dofile("game.lua")
