-- copy all globals into locals, some locals are prefixed with a G to reduce name clashes
local coroutine,package,string,table,math,io,os,debug,assert,dofile,error,_G,getfenv,getmetatable,ipairs,Gload,loadfile,loadstring,next,pairs,pcall,print,rawequal,rawget,rawset,select,setfenv,setmetatable,tonumber,tostring,type,unpack,_VERSION,xpcall,module,require=coroutine,package,string,table,math,io,os,debug,assert,dofile,error,_G,getfenv,getmetatable,ipairs,load,loadfile,loadstring,next,pairs,pcall,print,rawequal,rawget,rawset,select,setfenv,setmetatable,tonumber,tostring,type,unpack,_VERSION,xpcall,module,require

local apps=apps
local win=win


local function print(...) _G.print(...) end

local wstr=require("wetgenes.string")

local function mxrandom(mn,mx,mp)
	local r=math.random(mn/mp,mx/mp)*mp
	if math.random()>0.5 then r=-r end
	return r
end


module("bulb.cells")

function bake(state,cells)
	local cells=cells or {}
	local gl=state.gl
	
	local cake=state.cake
	local game=cake.game	
	local canvas=cake.canvas
	local flat=canvas.flat
	
	local beep=state.rebake("bulb.beep")
	local field	=state.rebake("bulb.field")
	local score	=state.rebake("bulb.score")
	local ball	=state.rebake("bulb.ball")
	local play	=state.rebake("bulb.game_play")
	local emits	=state.rebake("bulb.emits")
	local can	=state.rebake("bulb.can")
	local cuts	=state.rebake("bulb.cuts")

	local levels=state.rebake("bulb.levels")

	local cellbase={}
	local cellmeta={__index=cellbase} -- a meta table for cells
	

	local boss={}

	cells.map={}
	cells.setup_map=function()
		
		local m={}

		if play and play.mode=="endurance" then

			local l=4+play.level
			if l>12 then l=12 end

			for i=1,6*6 do
				m[i]=(i%l)+1
			end

	-- re jiggle
			local t={}
			while #m>0 do
				t[#t+1]=table.remove(m,math.random(1,#m))
			end
			m=t
		
		elseif play and play.mode=="puzzle" then

			m=levels.getmap(play.level)

		end

		cells.map=m
	end
--	cells.setup_map()

	function cells.state_change()

	-- handle state changes

		if cells.state_next then
		
			if cells.state and cells.state.clean then
				cells.state.clean()
			end
			
			if type(cells.state_next)=="string" then	 -- change by name
				cells.state_name=cells.state_next -- remember name
				cells.state_next=cells.states[cells.state_next]
			end

			cells.state_last=cells.state
			cells.state=cells.state_next
			cells.state_next=nil
			
			if cells.state and cells.state.setup then
				cells.state.setup()
			end
			
		end
		
	end

	cells.states={}
	cells.states.wait={
		setup=function()
			cells.frame=cells.frame+1 -- frame bump when we enter the wait state
			cells.chain=0 --remove chain bonus
		end,
		update=function()
		end,
	}
	cells.states.fill={
		setup=function()
			cells.fill_count=0
			cells.fill_last=-1
			for c in cells.all() do
				c.old=true
			end
			boss.reset() -- clear all boss flags
		end,
		update=function()
			cells.fill_count=cells.fill_count+1
			local idx=math.floor(cells.fill_count/2)

			if idx~=cells.fill_last then -- do each cell only once
				
				local done=false
				repeat
					if idx>=6*14 then
						cells.state_next="checkblobs"
						done=true
					else
						cells.fill_last=idx
						cells.fill_count=idx*2
					
						local c=cells.tab[idx]

						if c.id>0 then
							emits.newdrop_cell(c)
						end
					
						c.id=cells.map[idx+1] or 0
						c.gx=math.random(-32*16,32*16)/16
						c.gy=math.random(-32*16,32*16)/16
						
						c.rz=math.random(0,360)
							
						c.blobs=nil -- clear blobdata
						c.master=nil -- and masters
							
						if can.blob(c.id) then
							
							for n in c:neighbours() do
								if n.blobs and n.id==c.id and not n.old then -- found a join to a master
									local b=n.blobs
									b[#b+1]={ c.cx-n.master.cx , c.cy-n.master.cy } -- we maintain this relative position
									c.blobs=n.blobs
									c.master=n.master
									if #b>=3  then b.size=1.50 end
									if #b>=4  then b.size=2.00 end
									if #b>=7  then b.size=2.50 end
									if #b>=9  then b.size=2.75 end
									if #b>=14 then b.size=3.25 end
									if #b>=19 then b.size=3.75 end
									break
								end
							end
							
							if not c.blobs then -- start a new master
								local b={size=1.0}
								b[1]={0,0}
								c.blobs=b
								c.master=c -- points to self so is the master of this blob
							end
						
						end

						c.old=nil
						
						if c.id~=0 then
							done=true
						else
							idx=idx+1
						end
						
					end
				until done
			end
		end,
	}
	cells.states.animate={
		setup=function()
		end,
		update=function() -- animate the cells before continuing
			local finished=true
			for c in cells.all() do
				if c.animate then
				
					c.animate=c.animate-1
					
					c.px=c.px+c.vx
					c.py=c.py+c.vy
				
					if c.animate<=0 then -- last one
						c.animate=nil -- clear flag
						c.vx=nil
						c.vy=nil
						c.px=c.bx
						c.py=c.by
					else
						finished=false
					end
				end
			end
			if finished then
				cells.state_next="fall"
			end
		end,
	}
	cells.states.thunk={
		setup=function()
		end,
		update=function()
			if ball.state_name~="swing" then return end -- only while swinging
		
			for c in cells.all() do
--				if c.active~=cells.frame then -- can only activate once (or trigger could clear to force again)
					local t=can.trigger_update(c.id)
					local cid=c.id
					if t then
--						c.active=cells.frame -- mark as done
						c:trigger(t)
						if c.id~=cid then -- cell removed/changed, so update is over
							return
						end
					end
--				end
			end

			cells.state_next="wait" -- all done, nothing left to thunk

		end,
	}
	cells.states.fall={
		setup=function()
		end,
		update=function()
			if ball.state_name~="swing" then return end -- only while swinging		

			cells.state_next="clear" -- if there is nothing to fall then do this next

			for c in cells.all() do
				if c.id~=0 and c.limit~=game.frame and (not can.float(c.id)) then -- this cell is solid and call fall
				
					local stick
					local isstick=function()
						if type(stick)~="boolean" then -- check sticky
							stick=false
							for cb in c:neighbours() do
								if can.stick(cb.id) then stick=true end
							end
						end
						return stick
					end
					
					if can.blob(c.id) then -- special test for big balls
					
						if c.master==c then -- only deal with the master
							local ia,ib,ic=-1,1,2
							if c.cy%2==0 then
								local ia,ib,ic=1,-1,-2
							end
							for x=ia,ib,ic do -- left/right fall down check
								local fall=true
								for i,bb in ipairs(c.blobs) do
									local b=cells.get_cell(c.cx+bb[1],c.cy+bb[2])
									local n=b:neighbour(x,-1)
									if not n then fall=false break end
									if n.id~=0 and n.master~=c.master then -- nor empty or part of us
										fall=false
										break
									end
								end
								if fall then
									local master
									local blobs=c.blobs
									for i,bb in ipairs(blobs) do
										local b=cells.get_cell(c.cx+bb[1],c.cy+bb[2])
										local n=b:neighbour(x,-1)
--print("C=",b.cx,b.cy,"yto",n.cx,n.cy)
										if not master then master=n end -- cash new master
										b:swapid(n)
										-- rebuild datas after changing place
										b.master=nil
										b.blobs=nil
										n.blobs=blobs
										n.master=master
										bb[1]=n.cx-master.cx
										bb[2]=n.cy-master.cy
									end
									break
								end
							end
						end
					
					else

						local done
						local botpush
						for cb in c:bottoms() do
							if cb.id==0 then -- this cell must fall down

								if not isstick() then
									c:swapid(cb)
									botpush=nil
									break -- this cell has been delt with
								end
							
							elseif can.push_left(cb.id) then
								botpush=-1
							elseif can.push_right(cb.id) then
								botpush= 1
							end
						end
						
						-- slide cells left or right due to pushballs
						if botpush then
							local cb=cells.get_cell(c.cx+botpush,c.cy)
							if cb and cb.id==0 then
								if not isstick() then
									c:swapid(cb)
								end
							end
						end
					end
				end
			end

			if cells.state_next=="animate" then
				beep.play("fall")
			end
		end,
	}
	cells.states.checkblobs={
		setup=function()
		end,
		update=function()

			if play.level==100 then -- boss level special setup
				if not cuts.co then
					cuts.start_boss()
				end
			else
				cells.state_next="clear"
			end
		end,
	}
	cells.states.clear={
		setup=function()
		end,
		update=function()
--print("update")		
			local linkmap={}
			local clearcount=0

			for c in cells.all(0,6) do
				if c.active==cells.frame then
					local t=can.trigger_bottom(c.id)
					if t then
						c:trigger(t)
						clearcount=clearcount+1
					end
				end
			end
			
			cells.maxy=470
			local spreading=true
			while spreading do
				spreading=false
				for c in cells.all() do
					if c.id~=0 then
						if c.py < cells.maxy then
							cells.maxy=c.py			-- update the top cell watermark
						end
					end
					if c.active==cells.frame and c.id>0 and c.id<=16 then -- this cell is active so may clear
						for cn in c:neighbours() do
							if (cn.id==c.id) and (c.id~=0) then
								cn.active=c.active -- the activeness spreads

								if not c.links then -- make a new linkmap
									c.links=c.links or {}
									c.links[c]=c
									linkmap[c.links]=#linkmap+1
									linkmap[#linkmap+1]=c.links
--print("new",c.links,c.id,c.idx)
								end
								
								if cn.links then -- need to merge links
									if cn.links == c.links then -- already merged
									else
										spreading=true
										if linkmap[c.links] < linkmap[cn.links] then -- must merge towards the oldest
											for i,v in pairs(cn.links) do
--print("add",c.links,v.id,v.idx)
												c.links[i]=v
											end
											cn.links=c.links -- merged now so share table
										else
											for i,v in pairs(c.links) do
--print("add",cn.links,v.id,v.idx)
												cn.links[i]=v
											end
											c.links=cn.links -- merged now so share table
										end
									end
								else -- just add to our links
--print("add",c.links,cn.id,cn.idx)
									spreading=true
									cn.links=c.links
									c.links[cn]=cn -- added
								end
							end
						end
					end
				end
			end


			for c in cells.all() do
			
				if c.links then
				
					local links=c.links
					local count=0 for i,v in pairs(c.links) do count=count+1 end
					

					local x=0
					local y=0

					if count>=3 then -- can kill

						local id=0

						-- trigger by proximity to clears
						for i,v in pairs(c.links) do
							for c in v:neighbours() do
								c:trigger(can.trigger_proximity(c.id),v)
							end
						end

						for i,v in pairs(c.links) do
							if v.id~=0 then -- make sure it has not already been cleared in this loop
								id=v.id
								x=x+v.px
								y=y+v.py
								emits.newdrop_cell(v)
								v.id=0 -- remove
								v.active=0
								clearcount=clearcount+1
								
								v:push() -- explosive push

							end
							v.links=nil -- finished with links
						end

--print(c.id,c.idx,"=",count,links,x,y)
--if x==0 then debug.debug() end



--print("chain ="..cells.chain)
						local num=(2^(cells.chain))*((count)^3)
						score.add(num)
						local f=score.newfloater(x/count,(y/count)-cells.float,num,id)
						beep.play("score")
--						ball.dip=ball.dip-num/10
--						if ball.dip<0 then ball.dip=0 end
						cells.floor=cells.floor-num/10
						if cells.floor<0 then cells.floor=0 end

						cells.chain=cells.chain+1 -- every match increases chain

--						if cells.chain_new then
--							score.newstr(x/count,(y/count)-cells.float,"x"..cells.chain)
--							cells.chain_new=false
--						end
					end
					
				end

				c.links=nil -- finished with links
--				v.active=0
			end
			
			if clearcount==0 then
				cells.state_next="thunk" -- all done, nothing left to clear
			else
				cells.state_next="fall" -- go back to doing nothing
--				cells.chain_new=true
			end
		end,
	}

	function cells.loads()
	end
	
	function cells.setup()

		cells.counts=nil

--		cells.counts={}
--		cells.counts[0]=0

		cells.frame=0

		cells.loads()

		cells.float=0
		cells.floor=0
		cells.vec=0
		cells.cw=6
		cells.ch=14
		
--		local ldat=require("bulb.levelsdata").levels[2].map
		cells.tab={}
		for i=0,(cells.cw*cells.ch)-1 do
			cells.tab[i]=cells.newcell(i)
			cells.tab[i].id=0
		end
		
		cells.state_next="fill"
		cells.update()
	end


	function cells.update()
		if play.pause then return end
	
		cells.counts={}
		cells.counts[0]=0
		
		cells.state_change()
		if cells.state and cells.state.update then cells.state.update() end	
	
		local d=cells.floor-cells.float
		cells.vec=d/16
		cells.float=cells.float+cells.vec
		if d<=1/16 and d>=-1/16 then cells.float=cells.floor end

		for i=0,(cells.cw*cells.ch)-1 do
			local v=cells.tab[i]
			if v.id>0 then -- 0 is nothing

				local color=can.color(v.id)
				if color then
					cells.counts[color]= ( cells.counts[color] or 0 ) + 1
					cells.counts[0]=cells.counts[0]+1
				end
			end
				


			-- bounce back to center
			v.gx=v.gx-(v.fx/16)
			v.gy=v.gy-(v.fy/16)
			
			-- friction
			v.gx=v.gx*14/16
			v.gy=v.gy*14/16
			
			local r=(v.gx+v.gy)
			
			v.rz=v.rz+r
			
			v.fx=v.fx+v.gx
			v.fy=v.fy+v.gy

			if v.id==0 or can.float(v.id) then
				v.rz=0 -- no rotate or move
				v.fx=0
				v.fy=0
			end
		end
		
--		if cells.state_name=="fill" then -- wait for fill before providing counts
--			cells.counts=nil
--		end
	end

	function cells.draw()
--		gl.PushMatrix()
--		gl.Translate(field.px,field.py,0)

--		if play.pause then return end
	
		local bulbs=cake.sheets.get("bulbs")
		bulbs:batch_start()
		
		for i=0,(cells.cw*cells.ch)-1 do
			local v=cells.tab[i]
		
			if v.id~=0 then -- 0 is nothing
--				cake.blit("bulbs",nil,
--					field.px+v.px-(game.bulb_size/2) , field.py+v.py-(game.bulb_size/2),
--					game.bulb_size*(v.id-1),0,game.bulb_size,game.bulb_size )

				local fi,fx,fy = 1,v.fx,v.fy
				for c in v:neighbours() do
--					if c.id>0 then
						fi=fi+1
						fx=fx+c.fx
						fy=fy+c.fy
--					end
				end
				fx=fx/fi
				fy=fy/fi
				if can.float(v.id) then fx=0 fy=0 end
				if v.master then
					if v.master==v then
						local px=0
						local py=0
						local pi=#v.blobs
						for i,bb in ipairs(v.blobs) do
							local b=cells.get_cell(v.cx+bb[1],v.cy+bb[2])
							px=px+b.px
							py=py+b.py
						end
						px=px/pi
						py=py/pi
						bulbs:draw(v.id,px+fx, py-cells.float+fy , v.rz , game.bulb_size*v.blobs.size )
					end
				else
					local r=0
					if     can.push_left(v.id) then
						r=-((game.frame*4)%360)
					elseif can.push_right(v.id) then
						r=  (game.frame*4)%360
					end
					bulbs:draw(v.id,v.px+fx, v.py-cells.float+fy , v.rz + r, game.bulb_size )
				end
			end
		end
		bulbs:batch_stop()
		bulbs:batch_draw()

		local r1,g1,b1,a1=0,0.5,1,0.5
		local r2,g2,b2,a2=0,0,0,0

		flat.tristrip("xyzrgba",{
			30+0,20-2+440-cells.float,0,			r1*a1,g1*a1,b1*a1,1*a1,
			30+260,20-2+440-cells.float,0,			r1*a1,g1*a1,b1*a1,1*a1,
			30+0,20+4+440,0,						r2*a2,g2*a2,b2*a2,1*a2,
			30+260,20+4+440,0,						r2*a2,g2*a2,b2*a2,1*a2,

--[[
			30+0,20+0,0,		r*d,g*d,b*d,1*d,
			30+260,20+0,0,		r*d,g*d,b*d,1*d,
			30+0,20+440,0,		0,0,0,0,
			30+260,20+440,0,	0,0,0,0,
]]
		})


--		gl.PopMatrix()


		boss.draw() -- draw boss stuff if we are on the boss level

	end


	function cells.newcell(idx)

		local cell={}
		setmetatable(cell,cellmeta)
		
		cell.cake=cake
		cell.idx=idx
		cell.id=0
		cell.cx=idx%cells.cw
		cell.cy=(idx-cell.cx)/cells.cw
		
		cell.ax=0
		if cell.cy%2==1 then cell.ax=1/2 end
		
		cell.bx=(cell.ax+cell.cx+1.25)*game.unit
		cell.by=math.floor((cells.ch)*game.unit*3/4) - ( (cell.cy)*math.floor(game.unit*12/16) ) + (game.unit/2)
		
		cell.px=cell.bx
		cell.py=cell.by
		
		cell.rz=math.random(0,360)

-- anim fiddle twiddle
		cell.fx=0
		cell.fy=0
		cell.fz=0
		cell.gx=math.random(-32*16,32*16)/16
		cell.gy=math.random(-32*16,32*16)/16
		cell.gz=0
		
		return cell
	end
	-- clear special flags and id of this cell
	function cellbase.clear(c)
		c.id=0
		c.blobs=nil
		c.active=nil
		c.master=nil
	end

	-- convert an x,y pixel position into its closest cell cx,cy indexs
	-- notably the x,y is upside down in cells as we start at the bottom and
	-- in pixels we start at the top, this pixel is assumed to be relative to the field
	function cells.pxy_to_cxy(px,py)
		
		local nx=(px)-game.unit+10
		local ny=((cells.ch)*game.unit*12/16) - (py+cells.float) + (game.unit*(1/4))

		local cy=math.floor(ny/(game.unit*12/16))
		local bx=0
		if cy%2==1 then bx=game.unit/2 end -- shift in odd rows
		local cx=math.floor((nx-bx)/(game.unit))
		if cx<0 then cx=0 end
		if cx>5 then cx=5 end
		
		return cx,cy
	end

	-- turn a cell location, cx,cy into a real pixel location px,py
	function cells.cxy_to_pxy(cx,cy)

		local bx=0
		if cy%2==1 then bx=game.unit/2 end
		
		local px=bx+(cx+1)*game.unit+10
		local py=math.floor((cells.ch)*game.unit*12/16) - ( (cy)*math.floor(game.unit*12/16) ) - cells.float

		return px+(game.unit/2),py+(game.unit/2)
	end


	-- turn a cell location into that cell or nil if out of bounds
	function cells.get_cell(cx,cy)
		if cx<0 then return nil end
		if cx>=cells.cw then return nil end
		if cy<0 then return nil end
		if cy>=cells.ch then return nil end
		local c=cells.tab[cx+(cy*cells.cw)]
		return c
	end

	function cellbase.can(c,nam) return (can[nam])(c.id) end

	-- iterate through all the cells
	function cellbase.all(mn,mx)
		local len=mx or #cells.tab
		local i=mn or 0
		return function()
			if i>len then return nil end -- no more edges
			local c=cells.tab[i]
			i=i+1
			return c
		end
	end
	cells.all=cellbase.all -- via cell or cells

	-- all cells in area , 1 this cell , 2 neighbours , 3 neghbours of neghbours and so on
	function cellbase.area(cell,siz)
		local a={}
		local b={}

		if siz>=1 then a[cell]=true end -- seed
		
		for i=2,siz do
			for ac in  pairs(a) do
				for c in ac:neighbours() do -- neghbours of seed
					b[c]=true
				end
			end
			for bc in  pairs(b) do -- copy results into running total
				a[bc]=true
				b[bc]=false
			end
		end
		return pairs(a)
	end

	-- a neghbour of this cell, 
	function cellbase.neighbour(cell,x,y)
		local n_y_look={  1 ,  1 ,  0 ,  0 , -1 , -1 }
		local n_x_look={  0 ,  1 , -1 ,  1 ,  0 ,  1 }
		if cell.cy%2==0 then
		      n_x_look={ -1 ,  0 , -1 ,  1 , -1 ,  0 }
		end
		
		if     y>0 then
			if x<0 then
				return cells.get_cell(cell.cx+n_x_look[1],cell.cy+n_y_look[1])
			else
				return cells.get_cell(cell.cx+n_x_look[2],cell.cy+n_y_look[2])
			end
		elseif y<0 then
			if x<0 then
				return cells.get_cell(cell.cx+n_x_look[5],cell.cy+n_y_look[5])
			else
				return cells.get_cell(cell.cx+n_x_look[6],cell.cy+n_y_look[6])
			end
		else
			if x<0 then
				return cells.get_cell(cell.cx+n_x_look[3],cell.cy+n_y_look[3])
			else
				return cells.get_cell(cell.cx+n_x_look[4],cell.cy+n_y_look[4])
			end
		end
	end

	-- iterate through all the neightbours of this cell
	function cellbase.neighbours(cell)
		local len=#cells.tab
		local n_x_look={  0 ,  1 , -1 ,  1 ,  0 ,  1 }
		local n_y_look={  1 ,  1 ,  0 ,  0 , -1 , -1 }
		if cell.cy%2==0 then
		      n_x_look={ -1 ,  0 , -1 ,  1 , -1 ,  0 }
		end
		local i=1
		return function()
			while i<=6 do
				local c=cells.get_cell(cell.cx+n_x_look[i],cell.cy+n_y_look[i])
				i=i+1
				if c then
					return c
				end
			end
			return nil -- no more edges
		end
	end

	-- iterate through all the bellow neightbours of this cell
	function cellbase.bottoms(cell)
		local n_x_look={  0 ,  1 }
		local n_y_look={ -1 , -1 }
		if cell.cy%2==0 then
		      n_x_look={  0 , -1 }
		end
		local i=1
		return function()
			while i<=2 do
				local c=cells.get_cell(cell.cx+n_x_look[i],cell.cy+n_y_look[i])
				i=i+1
				if c then
					return c
				end
			end
			return nil -- no more edges
		end
	end
	
	-- iterate through all the above neightbours of this cell
	function cellbase.tops(cell)
		local n_x_look={  0 ,  1 }
		local n_y_look={  1 ,  1 }
		if cell.cy%2==0 then
		      n_x_look={  0 , -1 }
		end
		local i=1
		return function()
			while i<=2 do
				local c=cells.get_cell(cell.cx+n_x_look[i],cell.cy+n_y_look[i])
				i=i+1
				if c then
					return c
				end
			end
			return nil -- no more edges
		end
	end

	function cellbase.getpx(cell)
		return cell.px
	end
	function cellbase.getpy(cell)
		return cell.py-cells.float
	end


	function cellbase.fakeboom(cell,other)
		emits.boom.create{
			px=cell:getpx(),
			py=cell:getpy(),
			life=2,
		}
	end

	function cellbase.boom(cell,other)

		emits.boom.create{
			px=cell:getpx(),
			py=cell:getpy(),
			life=2,
		}
		
		beep.play("boom")

		if other and other.ball then
			if can.damage(other.id) then
				cellbase.damage(other)
			else
				other.id=0
			end
		end
				
		for c in cell:neighbours() do
			if can.remove(c.id) then
				emits.newdrop_ball(c.id,c:getpx(),c:getpy(),c.rz,nil,mxrandom(4,8,1/64),mxrandom(4,8,1/64)-2)
				c.id=0
			end
			if can.damage(c.id) then
				c:damage()
			end
			c:push(4)
		end
		cell.id=0

	end

	function cellbase.shatter(c,o)
		c:push(1)
		c.id=0
		emits.blob.create{
			px=c:getpx(),
			py=c:getpy()+32,
			burst=32,
			life=1,
			id=17,
		}


		beep.play("shatter")
	end

	function cellbase.sponge(cell)
--		cell:push(1)
		cell.id=0
		emits.sponge.create{
			px=cell:getpx(),
			py=cell:getpy(),
			burst=1,
			life=1,
			id=can.id_from_name("sponge"),
		}

		beep.play("sponge")
	end

	function cellbase.push(cell,n)
		n=n or 1
		for c in cell:neighbours() do -- explosive push
			if can.remove(c.id) then
				c.gx=n*(c.px-cell.px)/4
				c.gy=n*(c.py-cell.py)/4
			end
		end
	end

	function cellbase.freeze(cell)
		for cy=cell.cy-1,0,-1 do
			local c=cells.get_cell(cell.cx,cy)
			if c then
				if can.remove(c.id) then
					c.id=can.id_from_name("ice")
				end
				if can.damage(c.id) then
					c:damage()
				end
			end
		end
		beep.play("freeze")
	end
	
	function cellbase.genocide(cell,other)
		local killid=other and other.id

		if other and other.ball then
			if can.damage(other.id) then
				cellbase.damage(other)
			else
				other.id=0
			end
		end
		
		cell.id=0
		
		emits.spurt.create{
			px=cell:getpx(),
			py=cell:getpy()+(game.unit*0.5),
			life=16,
		}

		if can.match_three(killid) then
			for i=0,6*14-1 do
				local c=cells.tab[i]
				if c.id == killid then
					emits.spurt.create{
						px=c:getpx(),
						py=c:getpy()+(game.unit*0.5),
						life=4,
					}
					emits.newdrop_cell(c)
					c.id = 0
				end
			end
		end

		beep.play("genicide")
	end

	function cellbase.squash(c,other)
		if not other.ball then -- only if we land on something
			if can.remove(other.id) then -- squash it
				emits.newdrop_ball(other.id,other:getpx(),other:getpy(),other.rz,nil,mxrandom(0,1,1/64),0)
				other.id=0
			end
			if can.damage(c.id) then
				c:damage()
			end
		end
		beep.play("squash")
	end
	
	function cellbase.fire(cell,other)
		if other and other.ball then
			if can.damage(other.id) then
				cellbase.damage(other)
			else
				other.id=0
			end
		end
		cell.id=0
		emits.fire.create{
			px=cell:getpx(),
			py=cell:getpy(),
			life=16,
			burst=1,
		}
		
		for i=0,6*14-1 do
			if cells.tab[i].cy >= cell.cy-1 then
				local c=cells.tab[i]
				if can.remove(c.id) then
					emits.newdrop_ball(c.id,c:getpx(),c:getpy(),c.rz,nil,mxrandom(4,8,1/64),mxrandom(4,8,1/64)-2)
--					c:push(4)
					c.id = 0
				end
				if can.damage(c.id) then
					c:damage()
				end
			end
		end
		beep.play("fire")
	end

	function cellbase.dopple(cell,other)
		local cid=can.id_from_name("dopple")
		
		if cell and other and (can.clone(other.id)) then -- suck id from the other cell
		
			if cell.id ==cid then cell.id =other.id end
			if other.id==cid then other.id=cell.id  end
		
		end
		beep.play("dopple")
	end

	function cellbase.upturn(cell)
		cell.id=0
		for i=0,5 do
			local c1=cells.get_cell(cell.cx,i)
			local c2=cells.get_cell(cell.cx,11-i)
			if can.move(c1.id) and can.move(c2.id) then
				c1.id , c2.id = c2.id , c1.id
			end
		end
		beep.play("upturn")
	end

	function cellbase.unwrap(cell)
		if can.unwrap(cell.id) then
			emits.newdrop_cell(cell)
			cell.id=cell.id-32
		end
	end


	cells.dropline=function(id)
		for i=0,5 do
			local c=cells.get_cell(i,13)
			if c and c.id==0 then
				c.id=id
			end
		end
	end

	function cellbase.topdrop(cell)

		if cell then
			if cell.id==can.id_from_name("blob08_stamp") then
				cells.dropline(can.id_from_name("dopple"))
			end
		end
	end

	function cellbase.skip(c,o)
		c:fakeboom(o)
		score.levelskip(1)
		cells.dropall()
		score.levelstr("Skip!")
		beep.play("skip")
		if o then o.id=0 end
		if c then c.id=0 end
	end
	
	function cellbase.warp(c,o)
		c:fakeboom(o)
		score.levelskip(5)
		cells.dropall()
		score.levelstr("Warp!")
		beep.play("warp")
		if o then o.id=0 end
		if c then c.id=0 end
	end

	function cellbase.reset(c,o)
		if c then c:fakeboom(o) end
		score.levelskip(0)
		cells.dropall()
		score.levelstr("Reset!")
		beep.play("reset")
		if o then o.id=0 end
		if c then c.id=0 end
	end
	
	function cellbase.suicide(c,o)
		c:boom(o)
		ball.state_next="die"
		cells.dropall()
		score.levelstr("Suicide!")
		beep.play("suicide")
		if o then o.id=0 end
		if c then c.id=0 end
	end


	function cellbase.escape(c,o)
		if c.master then c=c.master end
		emits.newdrop_cell(c)
		c.id = 0
		if c.blobs then
			for i,bb in ipairs(c.blobs) do
				local b=cells.get_cell(c.cx+bb[1],c.cy+bb[2])
				if b then
					b.id=0
					b.blobs=nil
					b.master=nil
				end
			end
		end
		beep.play("escape")
	end

	function cellbase.refill(c,o)
		local cid=can.id_from_name("refill")
		local id
		if c and can.match_three(c.id) then id=c.id end
		if o and can.match_three(o.id) then id=o.id end
		if id then -- replaceall these balls with other balls
--			if c.id==cid then c.id=0 end
--			if o.id==cid then o.id=0 end
			for c in cells.all() do
				if c.id==id then
					emits.newdrop_cell(c)
					c.id=ball.grab()	-- replace
				end
			end
--			if c then c.id=id end -- tre color chosen
--			if o then o.id=id end

			if o then o.id=0 end
			if c then c.id=0 end
		end
	end

	function cellbase.flea(c,o)
		if c.active==cells.frame then return end
		c.active=cells.frame
--		print("upd",c.idx)
		local stuck=false
		for v in c:tops() do if v.id~=0 then stuck=true end end -- something id resting on us
		if not stuck then -- we can leap
			local count=0
			for v in cells.all() do if can.color(v.id) then count=count+1 end end
			if count>0 then -- only jump if the level is no clear (makes it easier to finish)
				ball.relaunch(c.id,c:getpx(),c:getpy()-game.unit*1.0,
				mxrandom(2,8,1/64),-20+mxrandom(0,8,1/64))
				c.id=can.id_from_name("dopple")
			end
		end
	end
	
	function cellbase.mole(c,o)
		if c.active==cells.frame then return end
		c.active=cells.frame
		local l={}
		for v in c:neighbours() do
			if can.remove(v.id) then -- consider
				if v.id~=c.id then -- not a clone
					l[#l+1]=v
				end
			end
		end
		if #l>0 then -- dig dig dig
			local r=math.random(1,#l)
			c:swapid(l[r])
		end
	end
	
	
	cells.boss=boss
	
	boss.acts={
		{	poo={1,2,3,4,5,18}, drop=32+1 },
		{	poo={1,2,3,4,32+5,18}, drop=32+2 },
		{	poo={1,2,3,32+4,32+5,18}, drop=32+3 },
		{	poo={1,2,32+3,32+4,32+5,18}, drop=32+4 },
		{	poo={1,32+2,32+3,32+4,32+5,18}, drop=32+5 },
		{	poo={32+1,32+2,32+3,32+4,32+5,18}, drop=18 },
		{	poo={32+1,32+2,32+3,32+4,32+5,18}, drop=24 },
		{	poo={32+1,32+2,32+3,32+4,32+5,18}, drop=17 },
		{	poo={32+1,32+2,32+3,32+4,32+5,18}, drop=25 },
		{	poo={32+1,32+2,32+3,32+4,32+5,18}, drop=23 },
	}

	boss.draw=function()
		if not boss.active then return end


		local r1,g1,b1,a1=1,0,0,1
		local r2,g2,b2,a2=1,1,0,1


		local y=40
		local b=2
		local h=10
		local x=1 - ( (boss.stage-1) / (#boss.acts) )

		flat.tristrip("xyzrgba",{
			40+0,20-2+y,0,					0,0,0,1,
			40+240,20-2+y,0,				0,0,0,1,
			40+0,20+4+y+h,0,				0,0,0,1,
			40+240,20+4+y+h,0,				0,0,0,1,
			40+240,20+4+y+h,0,				0,0,0,1,

			40+0+b,20-2+y+b,0,				r1*a1,g1*a1,b1*a1,1*a1,
			40+0+b,20-2+y+b,0,				r1*a1,g1*a1,b1*a1,1*a1,
			40+240-b,20-2+y+b,0,			r2*a2,g2*a2,b2*a2,1*a2,
			40+0+b,20+4+y+h-b,0,			r1*a1,g1*a1,b1*a1,1*a1,
			40+240-b,20+4+y+h-b,0,			r2*a2,g2*a2,b2*a2,1*a2,
			40+240-b,20+4+y+h-b,0,			r2*a2,g2*a2,b2*a2,1*a2,

			40+(240*x),20-2+y,0,			0.25,0,0,1,
			40+(240*x),20-2+y,0,			0.25,0,0,1,
			40+240,20-2+y,0,				0.25,0,0,1,
			40+(240*x),20+4+y+h,0,			0.25,0,0,1,
			40+240,20+4+y+h,0,				0.25,0,0,1,

		})


	end
	
	boss.reset=function()
		boss.stage=1
		boss.count=0
		boss.active=false
		boss.px=0
		boss.py=0
	end

	boss.pxpy=function(px,py)
		boss.px=px
		boss.py=py
	end


	boss.damage=function()
		boss.stage=boss.stage+1
		local aa=boss.acts[boss.stage]
		
		emits.boom.create{
			px=boss.px,
			py=boss.py,
			life=2,
		}
		

		if aa then
			cells.dropline(aa.drop)

			beep.play("scream")

		else -- you win
--			boss.reset()
--			cellbase.reset()
--			game.next=state.rebake("bulb.game_win")
			play.slomo=0

			for c in cells.all() do
				if c and c.id>0 then
					emits.newdrop_ball(c.id,c:getpx(),c:getpy(),c.rz,nil,mxrandom(4,8,1/64),mxrandom(4,8,1/64)-2)
					c:clear()
				end
			end

			beep.play("dead")
		end

	end

	function cellbase.damage(c)
		boss.damage()
	end

	function cellbase.boss(c,o)
	
		boss.px=c:getpx() -- remember position for later fx
		boss.py=c:getpy()

		boss.active=true

		local aa=boss.acts[boss.stage]
		
		if not aa then return end

		if boss.frame~=cells.frame then -- reset
			boss.frame=cells.frame
			boss.count=0
		end
		
		boss.count=boss.count+1
		
		
		local stuck=false
		for v in c:tops() do if v.id~=0 then stuck=true end end -- something is resting on us

		if not stuck and boss.count<8 then -- jump up, coz we can and not too high

--			local id=ball.grab()	-- replace

			ball.relaunch(c.id,c:getpx(),c:getpy()-game.unit*1.0,
			mxrandom(2,8,1/64),-20+mxrandom(0,8,1/64))
--			c.id=can.id_from_name("dopple")

			c.id= aa.poo[ 1+( (boss.count-1) % (#aa.poo) ) ] -- next poo

			beep.play("bossmove")
			
		elseif boss.count<12 then -- dig down

			local l={}
			for v in c:neighbours() do
				if can.remove(v.id) then -- consider
					if v.id~=c.id then -- not a clone
						l[#l+1]=v
					end
				end
			end
			if #l>0 then -- dig dig dig
				local r=math.random(1,#l)
				c:swapid(l[r])
				beep.play("bossmove")
			end

		end
	end

	function cellbase.trigger(c,trigs,o)

		if trigs then
			if trigs.dopple 	then c:dopple(o) end
			if trigs.shatter 	then c:shatter(o) end
			if trigs.boom		then c:boom(o) end
			if trigs.sponge		then c:sponge(o) end
			if trigs.freeze		then c:freeze(o) end
			if trigs.genocide	then c:genocide(o) end
			if trigs.fire		then c:fire(o) end
			if trigs.squash		then c:squash(o) end
			if trigs.upturn		then c:upturn(o) end
			if trigs.unwrap		then c:unwrap(o) end
			if trigs.skip		then c:skip(o) end
			if trigs.warp		then c:warp(o) end
			if trigs.reset		then c:reset(o) end
			if trigs.suicide	then c:suicide(o) end
			if trigs.escape		then c:escape(o) end
			if trigs.refill		then c:refill(o) end
			if trigs.flea		then c:flea(o) end
			if trigs.mole		then c:mole(o) end
			if trigs.boss		then c:boss(o) end
			if trigs.topdrop	then c:topdrop(o) end
		end

	end
	
	function cellbase.swapid(c,cb)
	
--print("fall")
		cb.id,c.id=c.id,cb.id -- our new cell
		cb.rz,c.rz=c.rz,cb.rz -- our new cell
		cb.fx,c.fx=c.fx,cb.fx
		cb.fy,c.fy=c.fy,cb.fy
		cb.gx,c.gx=0,0--c.gx,cb.gx
		cb.gy,c.gy=0,0--c.gy,cb.gy
		
		c.active=0 -- old one is no longer active
		cb.active=cells.frame -- is now active as we moved to it
--							c.active=0 -- and this is not active anymore
		cb.animate=6
		cb.px=c.bx -- from here
		cb.py=c.by
		cb.vx=(cb.bx-c.bx)/cb.animate -- by this each frame
		cb.vy=(cb.by-c.by)/cb.animate
		cb.limit=game.frame

		for c in cb:area(2) do
			c=c.master or c
			c.gx=cb.vx*1
			c.gy=cb.vy*1
		end

		cells.state_next="animate" -- will need to animate some cells next
	end	

	function cells.is_stable()
		if ball.state_name=="die" or ( ball.state_next) then
			return false
		end
		if cells.state_name=="wait" and (not cells.state_next) then
			return true
		end
		return false
	end
	
	function cells.dropall()
		for c in cells.all() do
			if c.id>0 then emits.newdrop_cell(c) end
			c:clear()
		end
	end
	
	
	return cells
end
