class "EditEntity"
EditEntity.pickspace = dspacehash.new()
EditEntity.list = {}
EditEntity.simtime = 0

function sl ()
	tt = EditEntity.savetotable()
	EditEntity.loadfromtable(tt)
end

function EditEntity . savetofile (file)
	local tab = EditEntity.savetotable()
	local ser = UtilFunctions.serialize(tab)
	local fp = assert(io.open(file,"w"))
	fp:write(ser)
	fp:close()
end

function EditEntity . loadfromfile (file)
	local fn,err = loadfile(file)
	if fn then
		EditEntity.loadfromtable(fn())
	else 
		print("Error",err)
		EditEntity.loadfromtable({})
	end
end


function EditEntity . savetotable ()
	local out = {}
	for e in pairs(EditEntity.list) do 
		local tab = {}
		table.insert(out,tab)
		for i,key in ipairs(EditEntity . getPropertyOrder ()) do
			local v = e:getProperty(key)
			tab[key] = type(v)=="table" and table.copy(v) or v
		end
	end
	return out 
end

function EditEntity . setShowPickers (state)
	EditEntity.hidepickers = not state
	for e in pairs(EditEntity.list) do 
		e.pick.l3d:rfNodraw(not state or e.drawpicker==true)
	end
end

function EditEntity . deleteAll()
	for e in pairs(EditEntity.list) do 
		e:delete() 
	end
end

function EditEntity . loadfromtable (tab)
	EditEntity.simtime = 0
	for e in pairs(EditEntity.list) do 
		e:delete() 
	end
	
	for i,props in ipairs(tab) do
		local e = new(EditEntity)
		for k,v in pairs(props) do
			e:setProperty(k,v)
		end
	end
end

function EditEntity : EditEntity ()
	EditEntity.list[self] = true
	self.actor = actornode.new(tostring(self))	
	self.rot = {0,0,0} -- degreess
	self.pos = {0,0,0} 
	self.renderflags = {
		color = {1,1,1,1},
		scale = {1,1,1}
	}
	self.proxies = {}
	
	self.pick = {}
	self.pick.geom = dgeombox.new(100,100,100,self.pickspace)
	self.pick.geom.EditEntity = self
	self.pick.l3d = l3dprimitive.newbox("p",100,100,100)
	self.pick.l3d:color(1,1,0,1)
	self.pick.l3d:rfWireframe(true)
	self.pick.l3d:linkinterface(self.actor)
	self.pick.l3d:rfNodraw(self.hidepickers==true)
	
	setmetatable(self.proxies,weakmt)
end

function EditEntity : delete ()
	if self.deleted then return end
	self.deleted = true
	self.actor:delete()
	self.pick.geom : delete()
	self.pick.l3d:delete()
	if self.mdl then self.mdl : delete() end
	EditEntity.list[self] =nil
end

function EditEntity : setPos (x,y,z)
	x,y,z = x or self.pos[1], y or self.pos[2], z or self.pos[3]
	self.pos[1],self.pos[2],self.pos[3] = x,y,z
	self.actor:pos(x,y,z)
	self.pick.geom:pos(x,y,z)
	self : propertyChanged ("location.position",self.pos)
end

function EditEntity : getPos ()
	return unpack(self.pos)
end

function EditEntity : setRotDeg (x,y,z)
	x,y,z = x or self.pos[1], y or self.pos[2], z or self.pos[3]
	local function flip (x)
		return (x+180) % 360 - 180
	end 
	x,y,z = flip(x),flip(y),flip(z)
	self.rot[1],self.rot[2],self.rot[3] = x,y,z
	self.actor:rotdeg(x,y,z)
	self.pick.geom:rotdeg(x,y,z)
	self : propertyChanged ("location.rotation",self.rot)
end

function EditEntity : setRotRad (x,y,z)
	if x then x = x*180/math.pi end
	if y then y = y*180/math.pi end
	if z then z = z*180/math.pi end
	self : setRotDeg(x,y,z)
end

function EditEntity : getVisualSize ()
	if self.renderflags.model then 
		local m = mdl[self.renderflags.model]
		local xx,yy,zz,XX,YY,ZZ = m : bbox()
		local sx,sy,sz = XX-xx,YY-yy,ZZ-zz
		return sx * self.renderflags.scale[1],
						sy* self.renderflags.scale[2],
						sz* self.renderflags.scale[3]
	end
	return unpack(self.renderflags.scale)
end

function EditEntity : getRotDeg ()
	return self.rot[1],self.rot[2],self.rot[3]
end

function EditEntity : getRotRad ()
	return self.rot[1]/180*math.pi,self.rot[2]/180*math.pi,self.rot[3]/180*math.pi
end

function EditEntity : addPropertyProxy (proxy)
	self.proxies[proxy] = true
end

function EditEntity : removePropertyProxy (proxy)
	self.proxies[proxy] = nil
end

function EditEntity : copyfrom (e)
	for name in pairs(self : getPropertyKeys()) do
		local v = e : getProperty(name)
		if v~=nil then self : setProperty (name, v) end
	end
end

function EditEntity . getPropertyKeys ()
	local models,billboards = {"<none>"},{"<none>"}
	for name in UtilFunctions.pairsByKeys(mdl) do
		models[#models+1] = name
	end
	for name in UtilFunctions.pairsByKeys(tex) do
		billboards[#billboards+1] = name
	end
	
	return {
		name=new (Propertycontrol.Text),
		["hidegrip"] = new(Propertycontrol.Checkbox),
		["location.rotation"] = new(Propertycontrol.Sliderbox,3,-180,180),
		["location.position"] = new(Propertycontrol.Vectorbox,3),
		["rendering.scale"] = new(Propertycontrol.Vectorbox,3),
		["rendering.blendmode"] = new(Propertycontrol.Combobox,{
			"<no blendmode>","additive","amodadd","decal","modulate"
		}),
		["rendering.layer"] = new(Propertycontrol.Combobox,{
			"Layer 0", "Layer 1", "Layer 2", "Layer 3", "Layer 4", "Layer 5"
		}),
		["rendering.no backfaceculling"] = new(Propertycontrol.Checkbox),
		["rendering.no depthmask"] = new(Propertycontrol.Checkbox),
		["rendering.no depthtest"] = new(Propertycontrol.Checkbox),
		["light.active"] = new(Propertycontrol.Checkbox),
		["light.range"] = new(Propertycontrol.Vectorbox,1),
		["rendering.lit sun"] = new(Propertycontrol.Checkbox),
		["rendering.lit fx"] = new(Propertycontrol.Checkbox),
		["rendering.model"] = new(Propertycontrol.Combobox,models),
		["rendering.billboard"] = new(Propertycontrol.Combobox,billboards),
		["rendering.color"] = new(Propertycontrol.Sliderbox,4,0,1),
		["light.color"] = new(Propertycontrol.Sliderbox,4,0,1),
		["trigger.type"] = new(Propertycontrol.Combobox,{"<none>","Scriptfile"}),
		["trigger.file"] = new(Propertycontrol.Text),
		["game.enemytype"] = new(Propertycontrol.Combobox,{
			"<none>","Small fighter","Medium fighter", "Heavy fighter",
			"Bomber","Transporter"
		}),
		["game.movementx"] = new(Propertycontrol.Text),
		["game.movementy"] = new(Propertycontrol.Text),
	}
end
function EditEntity . getPropertyOrder ()
	return {"name","hidegrip",
		"location.rotation",
		"location.position",
		"rendering.model",
		"rendering.billboard",
		"rendering.layer",
		"rendering.blendmode",
		"rendering.no backfaceculling",
		"rendering.no depthmask",
		"rendering.no depthtest",
		"rendering.lit sun",
		"rendering.lit fx",
		"rendering.scale",
		"rendering.color",
		"light.active",
		"light.range",
		"light.color",
		"game.enemytype",
		"game.movementx",
		"game.movementy",
		"trigger.type",
		"trigger.file",
		}
end

function EditEntity : getProperty (key)
	if not self then return end
	if key == 'name' then
		return self.actor:name()
	elseif key == 'hidegrip' then
		return self.drawpicker
	elseif key == 'trigger.file' then
		return self.trigger and self.trigger.file or ""
	elseif key == 'trigger.type' then
		return self.trigger and self.trigger.type or "<none>"
	elseif key == "game.enemytype" then
		return self.game and self.game.enemytype or "<none>"
	elseif key == "game.movementx" then
		return self.game and self.game.movementx or "0"
	elseif key == "game.movementy" then
		return self.game and self.game.movementy or "age"
	elseif key:match "^light%." then
		if key == "light.range" then return self.light and self.light.range or {10000} end
		if key == "light.color" then return self.light and self.light.color or {1,1,1,1} end
		return self.light and self.light[key:gsub("^light%.","")]
	elseif key:match "^rendering%." then
		return self.renderflags[key:gsub("^rendering.","")]
	elseif key == 'location.rotation' then
		return self.rot
	elseif key == 'location.position' then
		return self.pos
	end
end

function EditEntity : updateLight ()
	if not self.light or not self.light.active then return end
	local r = (self.light.range or {10000})[1]
	local R,G,B,A = unpack(self.light and self.light.color or {1,1,1,1})
	self.light.l3d:deactivate()
	self.light.l3d:activate(0,r*4,0)
	--print("attenuation: ",a,b)
	local c = r^3
	self.light.l3d:diffuse(c*R,c*G,c*B,c*A)
	--c = -c * .1
	--self.light.l3d:ambient(c,c,c,c)
	self.light.l3d:attenuateconst(0)
	self.light.l3d:attenuatelinear(r)
	self.light.l3d:attenuatequadratic(r*4)
end

function EditEntity : updateRenderflags () 
	if not self.mdl then return end
	
	self.mdl:rfBlend(true)
	if self.renderflags.blendmode == "additive" then
		self.mdl:rsBlendmode(blendmode.add())
	elseif self.renderflags.blendmode == "amodadd" then
		self.mdl:rsBlendmode(blendmode.amodadd())
	elseif self.renderflags.blendmode == "decal" then
		self.mdl:rsBlendmode(blendmode.decal())
	elseif self.renderflags.blendmode == "modulate" then
		self.mdl:rsBlendmode(blendmode.modulate())
	else 
		self.mdl:rsBlendmode(blendmode.replace())
		self.mdl:rfBlend(false)
	end
	local layer = tonumber((self.renderflags.layer or "Layer 0"):match("%d+$"))
	self.mdl:setlayer(l3dset.default():layer(layer))
	self.mdl:color(unpack(self.renderflags.color))
	self.mdl:renderscale(unpack(self.renderflags.scale))
	self.mdl:rfLitSun(self.renderflags["lit sun"] == true)
	self.mdl:rfLitFX(self.renderflags["lit fx"] == true)
	self.mdl:rfNodepthmask(self.renderflags["no depthmask"] == true)
	self.mdl:rfNodepthtest(self.renderflags["no depthtest"] == true)
	self.mdl:rfNocull(self.renderflags["no backfaceculling"] == true)
	
	local sx,sy,sz = unpack(self.renderflags.scale)
	if self.renderflags.model then
		local a,b,c,d,e,f = mdl[self.renderflags.model]:bbox()
		a,b,c,d,e,f = a*sx,b*sy,c*sz,d*sx,e*sy,f*sz
		a,b,c,d,e,f = math.min(a,-100),math.min(b,-100),math.min(c,-100),
			math.max(d,100),math.max(e,100),math.max(f,100)
		self.actor:vistestbbox(a,b,c,d,e,f)
	else
		self.actor:vistestbbox(math.min(-sx,-100),math.min(-sy,-100),math.min(-sz,-100),
			math.max(sx,100),math.max(sy,100),math.max(sz,100))
	end
end

function EditEntity : updateGame (param)
	self.game = self.game or {}
	param = param or {}
	self.game.movementx = param.x or self.game.movementx or "0"
	self.game.movementy = param.y or self.game.movementy or "age"
	self.game.enemytype = param.type or self.game.enemytype or "<none>"
	
	if self.game.l3d and param.type then 
		self.game.l3d.mdl : delete()
		self.game.l3d.mdl = nil
	end
	

	if self.game.enemytype == "<none>" then
		if self.game.l3d and self.game.l3d.trail then 
			self.game.l3d.trail : delete()
		end
		self.game.l3d = nil
		return
	end
	local mdls = {
		["Small fighter"] = mdl.enemy1,
		["Medium fighter"] = mdl.enemy4,
		["Heavy fighter"] = mdl.enemy3,
		["Bomber"] = mdl.enemy2,
		["Transporter"] = mdl.enemy5
	}
	local layer = l3dset.default():layer(3)
	self.game.l3d = self.game.l3d or {} 
	if not self.game.l3d.mdl then 
		self.game.l3d.mdl = l3dmodel.new("g",layer,mdls[self.game.enemytype])
		--self.game.l3d.mdl:setlayer(layer)
		self.game.l3d.mdl:linkinterface(self.actor)
		self.game.l3d.mdl:rfLitSun(true)
		self.game.l3d.mdl:rfLitFX(true)
	end
	if true then return end
		
	if not self.game.l3d.trail then
		self.game.l3d.trail = l3dtrail.new("t",layer,100)
		--self.game.l3d.trail:setlayer(layer)
		self.game.l3d.trail:linkinterface(self.actor)
		self.game.l3d.trail:typelocal()
		self.game.l3d.trail:spawnstep(0)
		self.game.l3d.trail:size(-1,2)
		self.game.l3d.trail:rfNocull(true)
	end
	local env = {sin=math.sin,cos=math.cos}
	setmetatable(env,{__index = _G})
	local fnx = loadstring("return "..self.game.movementx) or function() return 0 end
	local fny = loadstring("return "..self.game.movementy) or function() return age end
	setfenv(fnx,env)
	setfenv(fny,env)
	for i=1,100 do
		env.age = i*20
		local function get(a,b,c) return b and type(c)=='number' and c or a end
		local x = get(0,pcall(fnx))
		local y = get(env.age,pcall(fny))
		self.game.l3d.trail:spawn(x,-y,0,0,0,1)
		--print(i,x,y)
	end
end

function EditEntity : setProperty (key, value)
	if not self then return end
	if key == 'name' then
		self.actor:name(value)
	elseif key == 'hidegrip' then
		self.drawpicker = value == true
		self.pick.l3d:rfNodraw(self.drawpicker or self.hidepickers==true)
	elseif key == 'trigger.file' then	
		self.trigger = self.trigger or {}
		self.trigger.file = value
	elseif key == 'trigger.type' then
		self.trigger = self.trigger or {}
		self.trigger.type = value
		self.pick.l3d:rfWireframe(value=='<none>')
	elseif key == 'game.enemytype' then
		self : updateGame {type = value}
	elseif key == 'game.movementx' then
		self : updateGame {x = value}
	elseif key == 'game.movementy' then
		self : updateGame {y = value}
	elseif key == 'location.rotation' then
		assert(type(value) == 'table' and #value==3 and value)
		self.rot[1],self.rot[2],self.rot[3] = value[1],value[2],value[3]
		self.actor:rotdeg(unpack(self.rot))
	elseif key == "location.position" then
		assert(type(value) == 'table' and #value==3 and value)
		self.pos[1],self.pos[2],self.pos[3] = value[1],value[2],value[3]
		self.pick.geom:pos(unpack(self.pos))
		self.actor:pos(unpack(self.pos))
	elseif key:match("^light%..*") then
		if not self.light then self.light = {} end
		
		if key == "light.active" then
			if not value and self.light.l3d then
				self.light.l3d:delete()
				self.light.l3d = nil
			end
			if value and not self.light.l3d then
				self.light.l3d = l3dlight.new("fxlight")
				self.light.l3d:linkinterface(self.actor)				
				self.light.l3d:activate(0,(self.light.range or {10000})[1],0)
			end
		end
		self.light[key:gsub("^light%.","")] = value
		self:updateLight()
	elseif key:match "^rendering%." then
		if key == "rendering.model" then
			if self.mdl then self.mdl : delete() self.mdl = nil end
			self.renderflags.model = nil
			if value and value~="<none>" then
				self.renderflags.model = value
				self : setProperty("rendering.billboard","<none>")
				self.mdl = l3dmodel.new("m",mdl[value])
				self.mdl:linkinterface(self.actor)
			end
		elseif key == "rendering.billboard" then
			if self.mdl then self.mdl : delete() self.mdl = nil end
			self.renderflags.billboard = nil
			if value and value~="<none>" then
				self : setProperty("rendering.model","<none>")
				self.renderflags.billboard = value
				self.mdl = l3dprimitive.newquadcentered("b",1,1)
				self.mdl:linkinterface(self.actor)
				self.mdl:matsurface(tex[value])
			end
		elseif key == "rendering.scale" then
			self.renderflags.scale[1],self.renderflags.scale[2],self.renderflags.scale[3] = 
				value[1],value[2],value[3]
		elseif key == "rendering.color" then
			self.renderflags.color[1],self.renderflags.color[2],self.renderflags.color[3],self.renderflags.color[4] = 
				value[1],value[2],value[3],value[4]
		else
			self.renderflags[key:gsub("^rendering.","")] = value
		end
		self:updateRenderflags()
	end
	self : propertyChanged ( key, value )
end

function EditEntity : propertyChanged (key,value)
	for proxy in pairs(self.proxies) do
		proxy:propertyValueChanged (key, value)
	end
end


