---------------------------
-- Post-Process Effects
---------------------------
PostProcessEffects = capability.fbo() and capability.texrectangle() and technique.arb_vf():supported_tex4() and {} or nil
if (PostProcessEffects) then
	PostProcessEffects.blurmtls = {}
	for i=1,5 do
		local mtl = material.load("posteffects/blur"..(i*2)..".mtl")
		local texctrl = mtl:gettexcontrol("blursource")
		local wctrl = mtl:getcontrol("weights")
		local cctrl = mtl:getcontrol("coords")
		table.insert(PostProcessEffects.blurmtls,{mtl=mtl,texctrl=texctrl,wctrl=wctrl,cctrl=cctrl})
	end
	do
		local mtl = material.load("posteffects/glowthresh.mtl")
		local texctrl = mtl:gettexcontrol("source")
		local tctrl = mtl:getcontrol("thresh")
		local wctrl = mtl:getcontrol("weight")
		PostProcessEffects.glowthreshmtl = {mtl=mtl,texctrl=texctrl,tctrl=tctrl,wctrl=wctrl}
	end
	
	do
		-- powerof2 default texture sizes
		local w,h = window.size()
		PostProcessEffects.texw = w >= 1024 and  512 or 256
		PostProcessEffects.texh = h >= 768 and 512 or 256
	end


---------------------------
-- BlurKernelGauss
--
-- gausprops = {{scale,amplitude,offset}}


class "BlurKernelGauss"
function BlurKernelGauss : BlurKernelGauss(size,coloratt,gausprops,nullmod,nullgreater)
	self.size = size or 8
	self.coloratt = coloratt or 1.0
	self.gausprops = gausprops or {{0.18,0.06,0}}
	self.nullmod = nullmod
	self.nullgreater = nullgreater
end

-- must be implemented by all BlurKernels
-- returns coords,weighs arrays
function BlurKernelGauss : getVectors(texw,texh,horizontal,minw)
	local stepx = 1/texw
	local stepy = 1/texh
	local thresh = minw or 0.01
	
	local coords = {}
	local weights = {}
	
	local cent = (self.size - 1.0 ) / 2.0
	local gpsize = #self.gausprops
	local start = -math.floor( self.size / 2.0 )
	
	-- gauss as by tron2.0 glow demo
	-- http://http.download.nvidia.com/developer/SDK/Individual_Samples/3dgraphics_samples.html#Glow
	for i=0,self.size-1 do
		local coeff = 0
		for n,g in ipairs(self.gausprops) do
			local x = i - cent + g[3]
			local radi = (x * x) / (cent * cent)
			local tmp = radi * g[1]
			tmp = 1.0/math.exp(tmp)
			coeff = coeff + (tmp*g[2])
		end
		if (self.nullmod and self.nullgreater and math.mod(i,self.nullmod) > self.nullgreater) then
			coeff = 0
		end
		if (coeff > thresh) then
			local x,y = horizontal and ((start+i)*stepx) or 0, (not horizontal) and ((start+i)*stepy) or 0
			table.insert(coords,{x,y,0,0})
			local outw = coeff*self.coloratt
			table.insert(weights,{outw,outw,outw,0})
		end
	end
	
	-- pad to even count
	if (math.mod(#coords,2) ~= 0) then
		table.insert(coords,{0,0,0,0})
		table.insert(weights,{0,0,0,0})
	end

	return coords,weights
end

-- special must not change name
-- basically disables blurring and leaves texture unchanged
BlurKernelNone = {}

-- setup some kernels
-- should always make first the special flag
PostProcessEffects.blurkernels = {
	--					size,	coloratt,	gausprops,	nullmod,nullgreater
	--										gausprops = {{scale,amplitude,offset}}
	BlurKernelNone,
	
	new(BlurKernelGauss,	8,		3,		{{0.25,0.06,0},{3.0,0.15,0}}),
	new(BlurKernelGauss,	17,		1.6,	{{0.25,0.06,0},{3.0,0.15,0}}),
	new(BlurKernelGauss,	27,		0.9,	{{0.18,0.06,0},{4.5,0.11,0}}),
	new(BlurKernelGauss,	44,		0.2,	{{0.18,0.24,0},{4.5,0.41,0}}),
	new(BlurKernelGauss,	28,		0.33,	{{0.2,0.3,0},{4.5,0.11,0}}),
	new(BlurKernelGauss,	21,		0.9,	{{0.11,0.1,0},{4.5,0.11,0}}),
	new(BlurKernelGauss,	28,		0.9,	{{0.09,0.3,0}}),
	new(BlurKernelGauss,	24,		1.0,	{{18.2,6.5,0.3},{18.2,0.3,-0.3}}),
	new(BlurKernelGauss,	28*3,	0.379,	{{0.5,0.55,0}},6,2),
}

-- use this func to make sure blur gets valid kernels
function PostProcessEffects.getblurkernel(kernelid)
	kernelid = kernelid and (math.max(1,math.min(kernelid,#PostProcessEffects.blurkernels))) or 1
	
	return PostProcessEffects.blurkernels[kernelid]
end


---------------------------
-- PEBlurTex
-- 
-- system to blur a p2 texture, creates fbo/fbooff pair to do so
--
-- last allows a rcmdtable to fill the texinout with content
-- it abuses the fbo created by BlurTex

class "PEBlurTex"
function PEBlurTex : PEBlurTex (view,rcafter,texinout,kernel,rcmdsmakein)

	self.view = view
	self.rcmds = {}
	
	self.rcmdsmakein = rcmdsmakein
	self.rcafter = rcafter
	
	local tw,th = texinout:dimension()
	self.textemp = texture.create2d("temp",tw,th,textype.rgba(),false,false,false)
	self.textemp:clamp(true)
	
	self.texinout = texinout
	self.kernel = kernel
	
	self:update()
end

function PEBlurTex : update(newkernel,newtexinout)
	self:clearRcmds()
	
	-- in case of actual update
	self.kernel = newkernel or self.kernel
	self.texinout = newtexinout or self.texinout
	
	local tw,th = self.texinout:dimension()
	if(newtexinout) then
		if (tw ~= ExtMath.nearestpowerof2(tw) or th ~= ExtMath.nearestpowerof2(th)) then
			error("blurtexture must be power of 2")
		end
		self.textemp:resize(tw,th)
	end
	
	-- special flag
	local nokernel = (self.kernel == BlurKernelNone)
	
	if (nokernel and not self.rcmdsmakein) then
		-- do nothing
		return
	end
	
	-- get texcoords and weights for hoizontal/vertical pass
	local hcoords,hweights = unpack((not nokernel) and {self.kernel:getVectors(tw,th,true)} or {nil,nil})
	local vcoords,vweights = unpack((not nokernel) and {self.kernel:getVectors(tw,th)} or {nil,nil})
	
	-- function that activates all our rcmds
	local function addcmdstoview()
		-- insert rcmds after rcafter, final order must match table
		-- hence reverse indexing
		local cnt = #self.rcmds
		for i=cnt,1,-1 do
			self.view:rcmdadd(self.rcmds[i],self.rcafter)
		end
	end
	
	
	-- create FBO
	local fbo = renderfbo.new(tw,th)
	local rc = rcmdfbobind.new()
	rc:setup(fbo)
	table.insert(self.rcmds,rc)
	

	local hasdrawbuffers = (not nokernel) and capability.drawbuffers() > 1 or false
	-- if the card supports more than a single color attachment, we use 2
	-- as switching between attachments is faster than changing texture assignment
	
	-- assign textures
	rc = rcmdfbotex.new()
	rc:color(0,self.textemp)
	
	local rcassign = rc
	if (hasdrawbuffers) then
		rc:color(1,self.texinout)
	end
	table.insert(self.rcmds,rc)
	
	
	-- render into temp first, read from in
	rc = rcmdfbodrawto.new()
	rc:setup(0)
	table.insert(self.rcmds,rc)
		
	if (self.rcmdsmakein) then
		-- change as we need to generate texinout first
		if (hasdrawbuffers) then
			-- render into texinout first
			rc:setup(1)
		else
			-- change texassignment
			rcassign:color(0,self.texinout)
		end
		
		-- now makein
		for i,r in ipairs(self.rcmdsmakein) do
			table.insert(self.rcmds,r)
		end
		
		if (nokernel) then
			-- shortcut to finish
			table.insert(self.rcmds,rcmdfbooff.new())
			addcmdstoview()
			return
		end
		
		-- change assignment back to temp
		if (hasdrawbuffers) then
			rc = rcmdfbodrawto.new()
			rc:setup(0)
			table.insert(self.rcmds,rc)
		else
			rc = rcmdfbotex.new()
			rc:color(0,self.textemp)
			table.insert(self.rcmds,rc)
		end
	end
	
	-- initialize to black
	-- only needed when multiple passes are needed
	local rclear = rcmdclear.new()
	rclear:color(true)
	rclear:colorvalue(0,0,0,0)
	rclear:depth(false)
	rclear:stencil(false)
	table.insert(self.rcmds,rclear)
	
	local curclearpos = #self.rcmds
	
	-- function to add blurpasse
	local function addpasses(hor,coords,weights,tex)
		local cnt = #coords
		local firstpass = true
		while (cnt > 0) do
			-- render full screen quad
			local rc = rcmddraw2dmesh.new()
			table.insert(self.rcmds,rc)
			
			-- pick biggest shader possible
			local size = math.min(cnt,#PostProcessEffects.blurmtls*2)
			local mtab = PostProcessEffects.blurmtls[size/2]
			
			-- assign material and override the texturedummy with the actual
			-- blursource texture
			rc:matsurface(mtab.mtl)
			rc:moTexcontrol(mtab.texctrl,tex)
			-- pump shader values
			for n=1,size do
				local w = table.remove(weights)
				local c = table.remove(coords)
				
				rc:moControl(mtab.wctrl,n-1,unpack(w))
				rc:moControl(mtab.cctrl,n-1,unpack(c))
			end
			
			cnt = cnt - size
			if (firstpass and cnt < 1) then
				-- in case we have a single pass only
				-- no need to clear to black, as we just draw unblended
				-- so remove the last active clear cmd
				table.remove(self.rcmds,curclearpos)
			else
				-- in case we need multiple passes (big kernel)
				-- we blend each pass additively
				rc:rfBlend(true)
				rc:rsBlendmode(blendmode.add())
			end
			firstpass = false
		end
	end
	
	-- do horizontal passes
	addpasses(true,hcoords,hweights,self.texinout)
	
	-- change MRT target, or switch texture
	-- render into texinout
	if (hasdrawbuffers) then
		rc = rcmdfbodrawto.new()
		rc:setup(1)
	else
		rc = rcmdfbotex.new()
		rc:color(0,self.texinout)
	end
	table.insert(self.rcmds,rc)
	
	-- insert second clear
	-- might be removed again if addpasses only needs a single pass
	table.insert(self.rcmds,rclear)
	curclearpos = #self.rcmds
	
	-- do vertical passes
	addpasses(false,vcoords,vweights,self.textemp)
	
	-- stop fbo rendering
	table.insert(self.rcmds,rcmdfbooff.new())
	
	addcmdstoview()
end

function PEBlurTex : clearRcmds()
	for i,r in pairs(self.rcmds) do
		self.view:rcmdremove(r)
	end
	self.rcmds = {}
end

function PEBlurTex : delete ()
	self:clearRcmds()
end

---------------------------
-- PEBlurThreshold
--
-- creates a glowsource of current framebuffer content via a simple threshold function
-- spreads values above threshold to 0-1, allows rcmdspreblur to draw on top of that mask
-- blurs to a p2 texture 
--


class "PEBlurThreshold"
function PEBlurThreshold : PEBlurThreshold(view,rcmdafter,thresh,kernelid,tw,th,rcmdspreblur)
	if ((tw and tw ~= ExtMath.nearestpowerof2(tw)) or (th and th ~= ExtMath.nearestpowerof2(th))) then
		error("texturesize must be power of 2")
	end
	
	tw,th = tw or PostProcessEffects.texw,th or PostProcessEffects.texh
	
	self.texgrab = texture.createfullwindow("wingrab",textype.rgba(),false)
	self.texglow = texture.create2d("glowout",tw,th,textype.rgba(),false,false,false)
	self.texgrab:clamp(true)
	self.texglow:clamp(true)
	
	self.rcmds = {}
	self.view = view
	self.rcmdafter = rcmdafter
	self.threshv = thresh or 0.9
	self.kernel = PostProcessEffects.getblurkernel(kernelid)
	
	-- grab tex, with cpy
	local rcgrab = rcmdcopytex.new()
	--rcgrab:autosize(1)
	rcgrab:tex(self.texgrab)
	table.insert(self.rcmds,rcgrab)
	view:rcmdadd(rcgrab,rcmdafter)
	
	
	-- rescale tex + turn to glowsource
	-- is handed over to blur, so dont store in rcmds
	local rc = rcmddraw2dmesh.new()
	local mtab = PostProcessEffects.glowthreshmtl
	rc:matsurface(mtab.mtl)
	rc:moTexcontrol(mtab.texctrl,self.texgrab)
	--rc:autosize(1)
	self.rcmakeglow = rc
	
	self.rcmdsmakeglow = {self.rcmakeglow}
	if (rcmdspreblur) then
		for i,r in ipairs(rcmdspreblur) do
			table.insert(self.rcmdsmakeglow,r)
		end
	end
	
	-- blur glowsource
	-- gets inserted after grab 
	-- also generates the blurinput
	self.blur = new(PEBlurTex,view,rcgrab,self.texglow,self.kernel,self.rcmdsmakeglow)
	
	self:thresh(self.threshv)
	self:weight({0.3, 0.59, 0.11,0})
end

function PEBlurThreshold : getTexGlow ()
	return self.texglow
end

function PEBlurThreshold : getTexGrab ()
	return self.texgrab
end

-- get or set
-- threshold(value must be greater than this)
function PEBlurThreshold : thresh (newthreshv)
	if (not newthreshv) then return self.threshv end
	self.threshv = newthreshv
	
	local mtab = PostProcessEffects.glowthreshmtl
	self.rcmakeglow:moControl(mtab.tctrl,newthreshv,1/(1-newthreshv),0,0)
end


-- get or set
-- float4 required, dotproduct with incoming color is intensity used for threshold
function PEBlurThreshold : weight (newbv)
	if (not newbv) then return self.weightv end
	self.weightv = newbv
	
	local mtab = PostProcessEffects.glowthreshmtl
	self.rcmakeglow:moControl(mtab.wctrl,unpack(newbv))
end

-- set kernelid and/or texsize
function PEBlurThreshold : update(kernelid,tw,th)
	if (tw and th) then
		if (tw ~= ExtMath.nearestpowerof2(tw) or th ~= ExtMath.nearestpowerof2(th)) then
			error("texture size must be power of 2")
		end
		self.texglow:resize(tw,th)
	end
	self.kernel = kernelid and PostProcessEffects.getblurkernel(kernelid) or self.kernel
	
	self.blur:update(self.kernel,tw and th and self.texglow or nil)
end

function PEBlurThreshold : clearRcmds()
	self.blur:clearRcmds()
	for i,r in pairs(self.rcmds) do
		self.view:rcmdremove(r)
	end
	-- but dont delete own rcmds
end

function PEBlurThreshold : delete ()
	self:clearRcmds()
end

----------------------------------
-- PERcmdsViewTex
--
-- renders rcmds into a new view/texture
-- disables prevview 

class "PEViewTexFBO"
function PEViewTexFBO:PEViewTexFBO(needdepth,rcmdsmake,tw,th,fnprocoutrcmds)
	if ((tw and tw ~= ExtMath.nearestpowerof2(tw)) or (th and th ~= ExtMath.nearestpowerof2(th))) then
		error("texturesize must be power of 2")
	end
	
	tw,th = tw or PostProcessEffects.texw,th or PostProcessEffects.texh
	
	-- fixme, unique name for out
	self.textemp = texture.create2d(fnprocoutrcmds and "temp" or "fboout",tw,th,textype.rgba(),false,false,false)
	self.texout = fnprocoutrcmds and texture.create2d("fboout",tw,th,textype.rgba(),false,false,false) or self.textemp
	
	local hasdrawbuffers = fnprocoutrcmds and capability.drawbuffers() > 1 or false
	
	local view = l3dview.new()
	self.view = view 
	self.rcmdsmake = rcmdsmake
	self.fnprocoutrcmds = fnprocoutrcmds
	
	-- do distortview setup
	view:viewsize(tw, th)
	
	-- create fbo
	local fbo = renderfbo.new(tw,th)
	local rc = rcmdfbobind.new()
	rc:setup(fbo)
	view:rcmdadd(rc)
	

	

	
	rc = rcmdfbotex.new()
	rc:color(0,self.textemp)
	if (hasdrawbuffers) then
		rc:color(1,self.texout)
	end
	view:rcmdadd(rc)
	
	-- ati crashes on depthtexture assignment (even if 0) if a renderbuffer is already assigned
	if (needdepth) then
		local rb = renderbuffer.new()
		rb:setup(textype.depth(),tw,th)
		rc = rcmdfborb.new()
		rc:depth(rb)
		view:rcmdadd(rc)
	end
	
	rc = rcmdfbodrawto.new()
	rc:setup(0)
	view:rcmdadd(rc)
	
	-- add rcmds
	for i,r in ipairs(rcmdsmake) do
		view:rcmdadd(r)
	end
	
	-- do postprocess
	if (fnprocoutrcmds) then
		if (hasdrawbuffers) then
			rc = rcmdfbodrawto.new()
			rc:setup(1)
		else
			rc = rcmdfbotex.new()
			rc:color(0,self.texout)
		end
		view:rcmdadd(rc)
		
		local outcmds = fnprocoutrcmds(self.textemp)
		for i,r in ipairs(outcmds) do
			view:rcmdadd(r)
		end
	end
	
	-- finish
	view:rcmdadd(rcmdfbooff.new())
end


function PEViewTexFBO : delete ()
	self.view:delete()
end

function PEViewTexFBO:getTexOut()
	return self.texout
end

function PEViewTexFBO:getL3DView()
	return self.view
end

end

-- FIXME
-- this code should be moved outside this file
--------------------------------
-- Init Effects
--
-- view will get simplerenderqueue + bgcolor applied
--
-- detaillevel
--	0 no fx
--	1 simple glow (just add texture, no blurring at all)
--	2 glow with blur
--	3 glow with blut and distort
function postfxinit(detaillevel)
	print ("postfxinit")
	
	if (not PostProcessEffects) then 
		detaillevel = 0 
		print ("no effects capabilities")
	end

	if (not postfx) then
		postfx = {}
		postfx.detaillevel = detaillevel
	else
		if (detaillevel == postfx.detaillevel) then return end
		if (postfx.removeall) then 
			postfx.removeall() 
		end
		postfx.detaillevel = detaillevel
	
	end
	
	-- first prep some stuff
	-- from view we take out layermax-1 and layermax-2
	-- -2 will be used for distortion
	-- -1 for additional glow
	
	local view = UtilFunctions.simplerenderqueue(view)
	view:filldrawlayers(true)
	view.rClear:colorvalue(0,0,0,0) 
		
	local rlayerglow = view.rLayers[l3dlist.l3dlayercount()-1]	
	rlayerglow = {rlayerglow.stencil,rlayerglow.drawlayer,rlayerglow.drawprt}
	local rlayerdistort = view.rLayers[l3dlist.l3dlayercount()-2]
	rlayerdistort = {rlayerdistort.stencil,rlayerdistort.drawlayer,rlayerdistort.drawprt}
	-- remove them from view
	for i,r in ipairs(rlayerglow) do
		view:rcmdremove(r)
	end
	for i,r in ipairs(rlayerdistort) do
		view:rcmdremove(r)
	end
	
	if (true) then
		local shd = shader.load("vtex1mod2.shd")
		local rlayerships = view.rLayers[5]
		
		local rcon = rcmdshaders.new()
		rcon:shaders(shd,shd,shd,shd)
		
		local rcoff = rcmdshadersoff.new()
		
		view:rcmdadd(rcon,rlayerships.stencil)
		view:rcmdadd(rcoff,rlayerships.drawprt)
	
	end
	
	if (detaillevel < 1) then return end
	
	
	-- distortion is a bit more complex
	if (detaillevel > 2) then
		postfx.distort = {}
		
		-- render rlayerdistort into a black fbo
		--
		
		local rc = rcmdclear.new()
		rc:colorvalue(0,0,0,0)
		rc:color(true)
		rc:depth(true)
		rc:stencil(true)
		table.insert(rlayerdistort,1,rc)
		
		-- the outputfunc returns a table of rcmds
		-- that are rendered into the outtex
		-- do nothing first
		--local function fnprocoutrcmds(tex)
		--
		--end
		
		local eff = new(PEViewTexFBO,true,rlayerdistort,nil,nil,nil) --fnprocoutrcmds
		local nview = eff:getL3DView()
		nview:activate(l3dset.get(0))
		view:filldrawlayers(false)
		
		postfx.distort.eff = eff
	end
	
	if (detaillevel > 0) then
		postfx.glow = {}
		
		local rcglowafter = view.rLayers[l3dlist.l3dlayercount()].drawprt
		-- glow 		l3dview,aftercmd, thresh,scale,kernelid, texw, texh, {rcmdspreglowblur}
		local eff= new(PEBlurThreshold,view,rcglowafter,0.8,(detaillevel>1) and 2 or 1,nil,nil,rlayerglow)
		
		postfx.glow.eff = eff
	end
	
	if(postfx.glow or postfx.distort) then
		-- glow and distort
		-- distort
		-- glow
		
		if (postfx.glow and postfx.distort) then
			local rcfinal = rcmddraw2dmesh.new()
			view:rcmdadd(rcfinal)
			
			local mtl = material.load("posteffects/distortandadd.mtl")

			local tsrc = mtl:gettexcontrol("source")
			local tglw = mtl:gettexcontrol("add")
			local tdst = mtl:gettexcontrol("distortmul")
			
			local dscl = mtl:getcontrol("distortscale")
			local dnoi = mtl:getcontrol("distortnoise")
			local doff = mtl:getcontrol("distortoffset")
			
			
			assert(tsrc and tglw and tdst and dscl and dnoi and doff,"rcglowext material does not contain tex/controls")
			rcfinal:matsurface(mtl)
			
			rcfinal:moTexcontrol(tsrc,postfx.glow.eff:getTexGrab())
			rcfinal:moTexcontrol(tglw,postfx.glow.eff:getTexGlow())
			rcfinal:moTexcontrol(tdst,postfx.distort.eff:getTexOut())
			
			function postfx.glow.strength(val)
				return val and rcfinal:color(val,val,val,val) and val or rcfinal:color()
			end
			
			function postfx.distort.scale(x,y)
				rcfinal:moControl(dscl,x,y,0,0)
			end
			function postfx.distort.noise(x,y)
				rcfinal:moControl(dnoi,x,y,0,0)
			end
			function postfx.distort.offset(x,y)
				rcfinal:moControl(doff,x,y,0,0)
			end
			
			function postfx.removeall()
				postfx.glow.eff:delete()
				postfx.distort.eff:delete()
				postfx.glow = nil
				postfx.distort = nil
				view:rcmdremove(rcfinal)
				view:filldrawlayers(true)
				postfx.removeall = nil
			end
			
		elseif(postfx.glow) then
			local rcfinal = rcmddraw2dmesh.new()
			view:rcmdadd(rcfinal)
			
			rcfinal:matsurface(postfx.glow.eff:getTexGlow())
			rcfinal:rfBlend(true)
			rcfinal:rsBlendmode(blendmode.add())
			
			function postfx.glow.strength(val)
				return val and rcfinal:color(val,val,val,val) and val or rcfinal:color()
			end
			function postfx.removeall()
				postfx.glow.eff:delete()
				postfx.glow = nil
				view:rcmdremove(rcfinal)
				view:filldrawlayers(true)
				postfx.removeall = nil
			end
		elseif(postfx.distort) then
			-- need to grab fbuffer first
			-- TODO
		end
	end
	
	-- some default values
	if (postfx.glow) then 
		postfx.glow.strength(0.4) 
		postfx.glow.eff:thresh(0.8)
	end
	if (postfx.distort) then 
		postfx.distort.scale(10,10) 
		postfx.distort.noise(0.1,0.1)
		postfx.distort.offset(0,0)
	end
	
	
	-- call postfx.removeall() to kill fx
	
	--render.drawtexture(postfx.distort.eff:getTexOut())
	
	local st,str = l3dlist.fbotest()
	if (st) then
		print(st,str)
		print "Could not create FBO, reverting postfx"
		if (postfx.removeall) then 
			postfx.removeall() 
		end
		
		view = UtilFunctions.simplerenderqueue(view)
		view:filldrawlayers(true)
		view.rClear:colorvalue(0,0,0,0) 
	end
end