World = class("World")

local SYSTEM_HOOKS = {
	"step",
	"interpolate",
	"render",
	"render_hud",
	"wants",
	"dynamic_wants",
	"ent_leaving_team",
	"ent_joined_team",
}

function World:new(universe, rules)
	local world = instance(self)
	world.total_time = 0
	world.systems_map = {}
	world.systems_key_map = {}
	world.system_hooks = {}
	for index, hook in pairs(SYSTEM_HOOKS) do
		world.system_hooks[hook] = {}
	end
	world.universe = universe
	world.rules = rules
	world.ents = {}
	world.ents_to_remove = {}	
	return world
end

function World:add_system(sys)
	assert(not sys.world)
	sys.world = self
	self.systems_map[sys] = true
	if sys.key then
		self.systems_key_map[sys.key] = sys
	end

	--assert(sys.wants, getClassNameOf(sys))

	for hook, list in pairs(self.system_hooks) do
		if sys[hook] then
			table.insert(list, sys)

			for index, ent in pairs(self.ents) do
				if sys.wants then
					if sys:wants(ent) then
						sys:add_ent(ent)
					end
				end
			end
		end
	end
end

function World:check_dynamic_systems(ent)
	for index, sys in pairs(self.system_hooks.dynamic_wants) do
		if sys:dynamic_wants(ent) then
			if not sys:has_ent(ent) then
				sys:add_ent(ent)
			end
		elseif sys:has_ent(ent) then
			sys:remove_ent(ent)
		end
	end	
end

function World:get_system_by_key(key)
	return self.systems_key_map[key]
end

function World:remove_system(sys)
	for hook, list in pairs(self.system_hooks) do
		if sys[hook] then
			for index, cur_sys in pairs(list) do
				if cur_sys == sys then
					table.remove(list, index)
					break
				end
			end
		end
	end
	self.systems_map[sys] = nil
	if sys.key then
		self.systems_key_map[sys.key] = nil
	end
	sys:remove_all_ents()
	sys.world = nil
end

function World:add_ent(ent)
	ent:setting_world(self)	
	ent.world = self
	self.ents[ent] = true
	for index, sys in pairs(self.system_hooks.wants) do
		if sys:wants(ent) then
			sys:add_ent(ent)
			--print("added to", getClassNameOf(sys))
		end
	end	
end

function World:remove_ent(ent)
	self.ents_to_remove[ent] = true	
end

function World:extract_ent(ent)
	self.ents[ent] = nil
	for sys, _ in pairs(self.systems_map) do
		if sys:has_ent(ent) then
			sys:remove_ent(ent)				
		end
	end
	ent:removing_world(self)
	ent.world = nil
end

function World:interpolate(prg)
	for index, sys in pairs(self.system_hooks.interpolate) do
		sys:interpolate(prg)
	end	
end

function World:ent_leaving_team(ent, team)
	for index, sys in pairs(self.system_hooks.ent_leaving_team) do
		if sys:has_ent(ent) then
			sys:ent_leaving_team(ent, team)
		end
	end	
end

function World:ent_joined_team(ent, team)
	for index, sys in pairs(self.system_hooks.ent_joined_team) do
		if sys:has_ent(ent) then
			sys:ent_joined_team(ent, team)
		end
	end	
end

function World:step(dt)
	self.total_time = self.total_time + dt
	for index, sys in pairs(self.system_hooks.step) do
		sys:step(dt)
	end
	for ent, _ in pairs(self.ents_to_remove) do
		self.ents[ent] = nil
		for sys, _ in pairs(self.systems_map) do
			if sys:has_ent(ent) then
				sys:remove_ent(ent)				
			end
		end
		ent:removing_world(self)
		ent.world = nil
		if ent.chunk then
			ent.chunk:remove_ent(ent)
			ent.chunk = nil
		end
		self.ents_to_remove[ent] = nil
	end
end

function World:screen_to_camera(x,y, camera)
	local cam_x, cam_y = camera:get_position()	
	local rx,ry = x+cam_x, y+cam_y
	return rx,ry
end

function World:camera_to_screen(x,y, camera)
	local cam_x, cam_y = camera:get_position()	
	return x-cam_x,y-cam_y
end


function World:screen_to_camera_offset(x,y, camera)
	local cam_x, cam_y = camera:get_position()
	local scale = camera.zoom
	local perspective = camera.perspective
	local rx,ry = x-cam_x*scale, y-cam_y*scale*perspective
	return math.round(rx),math.round(ry)
end

function World:render(camera,x,y,a,r,g,b)	
	local rx,ry = self:screen_to_camera_offset(x,y, camera)
	local scale = camera.zoom
	local perspective = camera.perspective
	local angle = 0
	local universe = self.universe
	local startRow, endRow = self.universe:get_chunk_range_rows(camera)
	local startCol, endCol = self.universe:get_chunk_range_cols(camera)

	--print("x,y", rx,ry)
	--print("col",startCol, endCol)
	--print("row", startRow, endRow)

	for row=startRow, endRow do
		for col=startCol, endCol do
			local map = universe:get_map(col, row)
			if map then
				local mx = rx + universe:colToPixels(col)*scale
				local my = ry + universe:rowToPixels(row)*scale*perspective
				map:render(camera, mx, my, scale, angle, a, r, g, b)
			end
		end
	end


	
	for index, sys in pairs(self.system_hooks.render) do
		sys:render(rx,ry,scale,perspective,angle,a,r,g,b)
	end
end

function World:render_hud(camera,x,y,a,r,g,b)	
	local rx,ry = self:screen_to_camera_offset(x,y, camera)
	local scale = camera.zoom
	local angle = 0
	local perspective = camera.perspective
	for index, sys in pairs(self.system_hooks.render_hud) do
		sys:render_hud(rx,ry,scale,perspective,angle,a,r,g,b)
	end
end


function World:move_ent_to_chunk(ent, chunk)
	local cur_chunk = ent.chunk
	if cur_chunk then
		cur_chunk:remove_ent(ent)
	end

	if not chunk:is_active() then
		--chunk:add_ent_to_add(ent)
		self:extract_ent(ent)
		--print("removed ent from world to chunk")
	else
		
		--print("moved ent to new chunk")
	end	
	if chunk then
		chunk:add_ent(ent)
	end	
end

function World:watch_universe(camera,dt)
	self.watching_camera = camera
	local universe = self.universe
	local startRow, endRow = universe:get_chunk_range_rows(camera)
	local startCol, endCol = universe:get_chunk_range_cols(camera)
	local total_time = self.total_time
	for row=startRow, endRow do
		for col=startCol, endCol do
			local chunk = universe:get_chunk(col, row)

			if universe:activate_chunk_at(chunk, total_time) then
				--print("act")
				for ent,_ in pairs(chunk.ents) do
					self:add_ent(ent)
					--print("Readded ent from world movement")
				end
				universe:activate_chunk(chunk)				
				--activate
				--print("Activating chunk", col, row)
			end
		end
	end

	for chunk, _ in pairs(universe:get_active_chunk_map()) do
		if not chunk:was_activated_at(total_time) then
			
			--print("de")
			for ent,_ in pairs(chunk.ents) do
				self:extract_ent(ent)
				--print("Removed ent from world movement")
			end

			universe:deactivate_chunk(chunk)
			--print("De-activating chunk", chunk.col, chunk.row)
			--deactivate
		else			
			local to_add = universe:fetch_to_add(chunk)
			if to_add then				
				for index, ent in pairs(to_add) do
					chunk:add_ent(ent)
					self:add_ent(ent)
				end
				chunk:clear_to_add()
			end
			chunk:update_lights(dt)
		end
	end

	universe:check_areas()
end