PropertyFrame = {}
setmetatable(PropertyFrame,{__index = TitleFrame})
function PropertyFrame:new(x,y,title,minw,moveable)
	local self = TitleFrame.new(PropertyFrame,x,y,0,0,nil,title)
	self.props = {}
	self.minw = minw or 0
	self.moveable = moveable
	return self
end

function PropertyFrame.mousePressed(self,me,contains)
	TitleFrame.mousePressed(self,me,contains)
	if not self.moveable then return end
  if not contains then return end
  self:getParent():moveZ(self,1) -- move to front
  if me.y<25 then
    self:lockMouse()
    self.mouselockpos = {me.x,me.y}
  end
end

function PropertyFrame : toggleMinimize ()
	if self.minimized then
		self:setSize(self:getWidth(),self.minimized)
		self.minimized = nil
	else
		self.minimized = self:getHeight()
		self:setSize(self:getWidth(),25)
	end
end

function PropertyFrame.mouseClicked(self,me)
	TitleFrame.mouseClicked(self,me)
	if me.y<25 then 
		self:toggleMinimize()
		if self:getParent().pack then self:getParent():pack() end
	end
end

function PropertyFrame.mouseReleased(self,me)
	if self:isMouseLocker() then self:unlockMouse() else
		TitleFrame.mouseReleased(self,me)
	end
end

function PropertyFrame.mouseMoved(self,me)
  if self:isMouseLocker() then
    local x,y = self:getLocation()
    local dx,dy = 
      me.x-self.mouselockpos[1], 
      me.y-self.mouselockpos[2]
    self:setLocation(x+dx,y+dy)
  else
  	TitleFrame.mouseMoved(self,me)
  end
end


function PropertyFrame:getMinSize()
	local width,height = self.minw,30
	
	for i,v in ipairs(self.props) do
		local w,h = v.gui:getMinSize()
		if v.title then 
			local tw,th = v.title:getMinSize()
			h = math.max(th,h)
			w = tw+w
		end
		width = math.max(w+20,width)
		height = height + h
	end
	return width,self.minimized and 25 or height
end

function PropertyFrame:pack(fixwidth)
	local width,height = self:getMinSize()
	if fixwidth then width = fixwidth end
	self:setSize(width,height)
	local py = 25
	for i,v in ipairs(self.props) do
		local w,h = v.gui:getMinSize()
		if v.gui.pack then v.gui:pack(width-20) end
		local x,y = 10,py
		local width = width
		if v.title then 
			local tw,th = v.title:getMinSize()
			v.title:setBounds(x,y+math.floor(math.max(0,h-th)*.5),tw,th)
			x = x + tw
			width = width - tw
		end
		v.gui:setBounds(x,y,width-20,h)
		py = py + h
	end
end

function PropertyFrame:append(obj,label)
	local o = {gui = obj}
	self.props[#self.props+1] = o
	if label then 
		o.title = self:add(Label:new(0,0,0,0,label))
	end
	self:add(obj)
	
	return obj
end

function PropertyFrame:clearAll ()
	for i,o in ipairs(self.props) do
		o.gui:delete()
		if o.title then o.title:delete() end
	end
	self.props = {}
end

function PropertyFrame:setPropertyProxy (proxy,keyset)
	--print("new propsheet: ",keyset)
	self:clearAll()
	keyset = keyset or ""
	local subprops = {}
	local keys,keyorder = proxy:getKeys()
	for i,name in ipairs(keyorder) do
		local ptype = assert(keys[name],"missing keytype: "..name)
		local subname = name:sub(1,#keyset)
		if subname == keyset then
			subname = name:sub(#keyset+1)
			--print("sname",subname)
			local title,sub = subname:match("^([^%.]+)(%.?)")
			if #sub>0 then
				local p = subprops[title]
				if not p then
					p = PropertyFrame:new(0,0,title)
					self:append(p)
					p:setPropertyProxy(proxy,keyset..title..".")
					subprops[title] = p
				end
			else
				local title,con = ptype : getControl (name,title, proxy)
				self:append(con,title)
			end
		end
	end
	proxy:allPropertiesChanged()
	self:pack()
end

class "PropertyProxy"
function PropertyProxy : PropertyProxy (keys,keyorder, getfn, setfn, upvalue)
	self.keys = keys
	if not keyorder then 
		keyorder = {}
		for name in pairs(keys) do keyorder[#keyorder+1] = name end
		table.sort(keyorder)
	end
	self.keyorder = keyorder
	self.setfn,self.getfn = setfn,getfn
	self.proxies = {}
	self:setUpvalue(upvalue)
end

function PropertyProxy : getKeys ()
	return self.keys,self.keyorder
end

function PropertyProxy : getValue (key)
	assert(self.keys[key], "Unknown key")
	return self.getfn(self.upvalue,key),self.keys[key]
end


function PropertyProxy : setValue (key, value)
	self.lastset = {key,value}
	assert(self.keys[key], "Unknown key")
	self.setfn(self.upvalue,key,value)
end
local weakmt = {__mode='k'}
function PropertyProxy : addListener (key,proxyfn)
	local weak = {}
	setmetatable(weak,weakmt)
	local t = self.proxies[key] or weak
	self.proxies[key] = t
	t[proxyfn] = true
	--print("add li",proxyfn,key)
end

function PropertyProxy : propertyValueChanged (key, value)
	assert(self.keys[key], "Unknown key")
	for fn in pairs(self.proxies[key] or {}) do
		fn(self,self.keys[key],key,value)
	end	
end

function PropertyProxy : allPropertiesChanged ()
	for key,type in pairs(self.keys) do 
		self:propertyValueChanged(key,self:getValue(key))
	end
end

function PropertyProxy : setUpvalue (nval)	
	if self.upvalue ~=nval then 
		if self.upvalue and self.upvalue.removePropertyProxy then
			self.upvalue:removePropertyProxy(self)
		end
		if nval and nval.addPropertyProxy then
			nval:addPropertyProxy(self)
		end
		self.upvalue = nval
		self:allPropertiesChanged()
	end
end

Propertycontrol = {}

class ("Text", Propertycontrol) 
function Propertycontrol.Text : getControl (name,title,proxy)
	local tf = TextField:new(0,0,0,22)
	function tf.listener(pp,tname,key,value)
		if value == tf:getText() then return end
		tf:setText(tostring(value or ""),true)
	end
	proxy:addListener(name,tf.listener)
	function tf:onTextChanged()
		proxy:setValue(name,self:getText())
	end
	return title,tf
end

class ("Checkbox",Propertycontrol)
function Propertycontrol.Checkbox : getControl (name,title,proxy)
	local cb = Checkbox:new(0,0,0,0,title)
	function cb.listen(pp,tname,key,value)
		cb:setPushState(value==true)
	end
	proxy:addListener(name,cb.listen)
	function cb:onClicked(state)
		proxy:setValue(name,state)
	end
	return nil,cb
end

class ("Combobox",Propertycontrol) 
function Propertycontrol.Combobox : Combobox (values)
	self.values = values
end

function Propertycontrol.Combobox : getControl (name, title, proxy)
	local cb = ComboBox:new(0,0,0,18)
	for i,item in ipairs(self.values) do
		cb:addItem(item)
	end
	function cb.listen (pp,tname,key,value)
		local id,cap = cb:getSelected()
		if cap == value or id == (value or 1) then return end
		cb:select(value or 1)
	end
	proxy:addListener(name,cb.listen)
	function cb:onSelected(id,cap)
		proxy : setValue(name,cap)
	end
	return title,cb
end

class ("Vectorbox",Propertycontrol)
function Propertycontrol.Vectorbox : Vectorbox (n,numberfmt,cols,minwidth,minheight)
	self.n = n or 1
	self.cols = cols or self.n
	self.minwidth = minwidth or 45
	self.minheight = minheight or 22
	self.rows = math.ceil(self.cols / self.n)
	self.numberfmt = numberfmt or "%.1f"
end

function Propertycontrol.Vectorbox : getControl (name, title, proxy)
	local con = Container:new(0,0,0,0)
	function con.getMinSize()
		return self.cols * self.minwidth, self.rows * self.minheight
	end
	local edits = {}
	function con.setBounds(c,x,y,w,h)	
		Component.setBounds(c,x,y,w,h)
		for i=1,#edits do 
			local x = ((i-1) % self.cols) * self.minwidth
			local y = math.floor((i-1) / self.cols) * self.minheight
			edits[i]:setBounds(x,y,self.minwidth,self.minheight)
		end
	end
	
	for i=1,self.n do
		local box = con:add(TextField:new(0,0,0,0))
		edits[i] = box
		function box.listener (pp,tname,key,value)
			if not value then 
				box.lastvalue = nil
				box:setText("")
				return 
			end
			if box.lastvalue == value[i] then return end
			box.lastvalue = value[i]
			box:setText(("%.4f"):format(value[i]),true)
		end
		proxy:addListener(name,box.listener)
		function box:onTextChanged ()
			local tab = proxy:getValue(name) or {}
			local n = tonumber(self:getText()) or tab[i]
			tab[i] = n
			proxy:setValue(name,tab)
		end
		
		function box:mouseClicked(me,over)
			if me.button~=0 and over then
				local tab = proxy:getValue(name) or {}
				local w,h = window.refsize()
				local popup = Container.getRootContainer():add(Container:new(0,0,w,h),1)
				function popup:mouseClicked(me,on) if on then popup:delete() end end
				local v = tab[i]==0 and 1 or tab[i]
				local items = {v*.01,v*.1,v*.5,v*5,v*10,v*100,v*1000,v*10000}
				local x,y = self:local2world(0,self:getHeight())
				local list = popup:add(ListBox:new(x,y,self:getWidth(),#items*22))
				for i,v in ipairs(items) do
					list:addItem(tostring(v))
				end
				function list:onSelected(idx,cap)
					box:setText(cap)
					popup:delete()
				end
			else return TextField.mouseClicked(self,me,over) end
		end
		
		function box.mouseWheeled(box,me)	
			local tab = proxy:getValue(name) or {}
			if not tab then return end
			local n = tab[i] == 0 and me.wheelmove or (tab[i]*me.wheelmove*.1 + tab[i])
			tab[i] = tonumber((self.numberfmt):format(n))
			proxy:setValue(name,tab)
			
		end
		
	end
	return title,con
end

class ("Sliderbox",Propertycontrol)
function Propertycontrol.Sliderbox : Sliderbox (n,min,max)
	self.n = n
	self.min = min
	self.max = max
end

function Propertycontrol.Sliderbox : getControl (name,title, proxy)
	local n,min,max = self.n, self.min, self.max
	local con = Container:new(0,0,0,0)
	function con:getMinSize()
		return 100,n*14
	end
	local labels = {}
	local sliders = {}
	function con:setBounds(x,y,w,h)
		Component.setBounds(self,x,y,w,h)
		for i=1,n do 
			local yy = (i-1)*14
			labels[i]:setBounds(w-30,yy,30,16)
			sliders[i]:setBounds(0,yy,w-32,14)
		end
	end
	for i=1,n do 
		local sli = con:add(Slider:new(0,0,0,0))
		local lab = con:add(Label:new(0,0,0,0))
		labels[i] = lab
		sliders[i] = sli
		function sli.listen (pp,tname,key,value)
			local val = tonumber((value or {})[i] or 0) or 0
			lab:setText(("%.1f"):format(val))
			local val = (val-min)/(max-min)
			sli:setSliderPos(val,true)
		end
		proxy:addListener(name,sli.listen)
		function sli:onValueChanged(val)
			local vals = proxy:getValue(name) or {}
			vals[i] = (val)*(max-min)+min
			lab:setText(("%.1f"):format(vals[i]))
			
			proxy:setValue(name,vals)
		end
	end

	return title, con
end

dofile "world.lua"
loadworld = World.load
saveworld = World.save



-- testing

local function printtablesizes (name,tab,threshold)
	local visited = {}
	local function rec (name,tab)
		if visited[tab] then return end
		visited[tab] = true
		local n = 0
		for i,v in pairs(tab) do
			n = n + 1
			if type(i) == "table" then
				rec(name..".<"..tostring(i),i)
			end
			if type(v) == "table" then
				rec(name..".>"..tostring(i),v)
			end
		end
		if not threshold or n>=threshold then
			print(name,n)
		end
	end
	rec(name,tab)
end

postfxinit(0)


level = {}

function level.think ()
	local proxy = new (PropertyProxy,EditEntity.getPropertyKeys(),EditEntity.getPropertyOrder(),EditEntity.getProperty,EditEntity.setProperty,nil)
	local psheet = Container.getRootContainer():add(PropertyFrame:new(200,0,"Properties",200,true))
	psheet:setPropertyProxy(proxy)
	
	MouseCursor.showMouse(true)
	
	local function kdf (which) -- returns a function which 
		-- returns the state of the given key
		return function () return Keyboard.isKeyDown(which) end 
	end

	local up,down,del,ctrl,alt,shift =  
		kdf "PAGEUP",kdf "PAGEDOWN", kdf "DEL", kdf "CTRL",kdf "alt",kdf "shift"
	
	level.sun = actornode.new("sun",1000,200,10000)
	level.sun.l3d = l3dlight.new("sun")
	level.sun.l3d:makesun()
	level.sun.l3d:linkinterface(level.sun)
	level.sun.l3d:ambient(.2,.2,.2)
	level.sun.l3d:diffuse(.3,.3,.3)
	
	level.cam = actornode.new("cam")
	--level.con = Container.getRootContainer():add(GroupFrame:new(0,0,100,30))
	level.cam.camera = l3dcamera.default()
	level.cam.camera:fov(40)
	level.cam.camera:linkinterface(level.cam)
	level.cam:pos(200,-400,2000)
	level.cam:lookat(0,0,0,0,0,1)
	level.cam.camera:backplane(2048*16)
	level.left = actornode.new("left",levelw+250,0,0)
	level.right = actornode.new("right",-levelw-250,0,0)
	level.left.l3d = l3dprimitive.newbox("left",500,9000,100)
	level.right.l3d = l3dprimitive.newbox("left",500,9000,100)
	level.left.l3d:linkinterface(level.left)
	level.right.l3d:linkinterface(level.right)
	level.left.l3d:rfLitSun(true)
	level.right.l3d:rfLitSun(true)
	level.left.l3d:rfWireframe(true)
	level.right.l3d:rfWireframe(true)
	
	level.plane = dgeomplane.new(0,0,1,0)
	level.cursor = actornode.new("c")
	level.cursor.l3d = l3dprimitive.newbox("c",100,100,100)
	level.cursor.l3d:color(1,0,0,0)
	level.cursor.l3d:linkinterface(level.cursor)
	level.cursor.l3d:rfWireframe(true)
	
	level.pstart = actornode.new("pstart")
	level.pstart.l3d = l3dprimitive.newbox("pstart",levelw,levelw/4,0.1)
	level.pstart.l3d:linkinterface(level.pstart)
	level.pstart.l3d:matsurface(tex["edit.start"])
	local pitch,head = 0,0
	
	local presets = {}
	local w,h = window.refsize()
	local hbar = Container.getRootContainer():add(Slider:new(20,h-20,w-40,20))
	local vbar = Container.getRootContainer():add(Slider:new(w-20,20,20,h-40,true))
	function vbar:onValueChanged(val)	pitch = val	end
	function hbar:onValueChanged(val)	head = (val-.5) end
	local root = Container.getRootContainer()
	
	
	
	local function autolayout()
		local w,h = window.refsize()
		hbar:setBounds(20,h-20,w-40,20)
		vbar:setBounds(w-20,20,20,h-40)
	end
	
	window.resizemode(2)
	UtilFunctions.addWindowResizeListener(autolayout)
	
	
	local space = dspacehash.new()
	space:add(level.plane)
	local currentwired
	local lb = level
	local lastt = os.clock()
	while level==lb do		
		local t = os.clock()
		local td = t-lastt
		lastt = t
		
		local x,y,z = level.cam:pos()
		local fy = (up() and 1 or 0) - (down() and 1 or 0)
		y = y + fy*td*1000
		level.sun:pos(x-9000,y-2000,20000)
		level.left:pos(-levelw-250,y,0)
		level.right:pos(levelw+250,y,0)
		level.cam:pos(head*levelw,y,2000-1000*pitch)
		level.cam:lookat(0,y+1000,0,0,0,1)
		if root:getComponentAt(MouseCursor:pos()) == root then	
			local g,p = UtilFunctions.picktest(EditEntity.pickspace)
			if not g then 
				if Component.getMouseLock() == nil then
					g,p = UtilFunctions.picktest(level.plane) 
					if input.ismousedown(0) and not shift() and not alt() and not ctrl() then
						local e = new(EditEntity)
						e:setPos(p[1],p[2],p[3])
					end
					level.cursor.l3d:renderscale(100,100,100)
				end
				if p then level.cursor:pos(unpack(p)) end
				
			else
				level.cursor.l3d:renderscale(g.EditEntity:getVisualSize())
				level.cursor:pos(g:pos())
				
				if (input.ismousedown(0) or input.ismousedown(1)) then
					proxy : setUpvalue(g.EditEntity)
				end
				if Component.getMouseLock() == nil then
					local o = g.EditEntity
					
					
					local mx,my = input.mousepos()
					
					if shift () and input.ismousedown(0) then
						local e = new(EditEntity)
						e:copyfrom(o)
						o = e
					end
					
					while input.ismousedown(0) do
						local dx,dy = input.mousepos()
						dx,dy = dx - mx, dy - my
						mx,my = input.mousepos()
						local x,y,pz = o:getPos()
						if alt() then
							
							pz = pz - dy*10
							o:setPos(x,y,pz)
						elseif ctrl() then
							local x,y,rz = o:getRotDeg()
							rz = rz + dx
							o:setRotDeg(x,y,rz)
						else
							level.plane:distance(pz)
							local _,p = UtilFunctions.picktest(level.plane) 
							level.plane:distance(0)
							p[3] = pz
							o:setPos(unpack(p))
						end
						level.cursor:pos(g:pos())
						coroutine.yield(1)
						
					end
					if del() and g and g.EditEntity then
						proxy : setUpvalue(nil)
						g.EditEntity:delete()
						while del() do coroutine.yield(1) end
					end
				end
			end
			
		end
		coroutine.yield(1)
	end
	EditEntity.deleteAll()
	psheet:delete()
	hbar:delete()
	vbar:delete()
	UtilFunctions.removeWindowResizeListener(autolayout)
end
