map_master.registered_maps = {}

local storage = minetest.get_mod_storage()
local current_map = storage:get_string("current_map")
map_master.current_map = current_map

local function set_current_map(new)
	current_map = new
	map_master.current_map = new
	storage:set_string("current_map", new)
end

local function is_inside_box(pos, box_minpos, box_size)
	for k, v in pairs(pos)
	do
		if v < box_minpos[k]
		then
			return false
		end
	end
	local maxpos = vector.add(box_minpos,
		vector.multiply(box_size, 16))
	for k, v in pairs(pos)
	do
		if v >= maxpos[k]
		then
			return false
		end
	end
	return true
end

function map_master.is_inside_map(pos)
	local map = map_master.registered_maps[current_map]
	if not map
	then
		--no map set yet
		return false
	end
	if map.sectors
	then
		for name, sector in pairs(map.sectors)
		do
			if is_inside_box(pos, sector.minpos, sector.size)
			then
				return true
			end
		end
	else
		return is_inside_box(pos, map.minpos, map.size)
	end
	return false
end

function map_master.get_sector_with_pos(pos)
	local map = map_master.registered_maps[current_map]
	if not map
	then
		--no map set yet
		return nil
	end
	if map.sectors
	then
		for name, sector in pairs(map.sectors)
		do
			if is_inside_box(pos, sector.minpos, sector.size)
			then
				return sector
			end
		end
	else
		if is_inside_box(pos, map.minpos, map.size)
		then
			return map
		end
	end
	return nil
end



local default_minpos = {x = 0, y = 0, z = 0}


local function assert_vector(vec, msg)
	assert(type(vec.x) == "number" and
		type(vec.y) == "number" and
		type(vec.z) == "number", msg)
end

function map_master.register_map(name, def)
	assert(type(name) == "string",
		"Error registering map: name must be a string")
	def.minpos = def.minpos and def.minpos or
		(def.sectors and nil or default_minpos)
	
	if def.sectors
	then
		--make sure that every sector has a size, path and minpos
		for name, sector in pairs(map.sectors)
		do
			--setting map.minpos to minimal position of all sectors
			if not def.minpos or
				def.minpos.x > sector.minpos.x or
				def.minpos.y > sector.minpos.y or
				def.minpos.z > sector.minpos.z
			then
				def.minpos = sector.minpos
			end
			
			--assertions
			--size
			assert_vector(sector.size,
				"Error registering map: size must be a vector")
			assert(sector.size.x > 0 and
				sector.size.y > 0 and
				sector.size.z > 0,
				"Error registering map: x, y and z of size must be positive")
			--minpos
			assert_vector(sector.minpos,
				"Error registering map: minpos must be a vector")
			for k, v in pairs(sector.minpos)
			do
				assert(v % 16 == 0, "Error registering map: dimensions of minpos must be divisible by 16")
			end
			
			--path
			assert(type(sector.path) == "string",
				"Error registering map: Path must be a string")
		end
	else
		--for maps that only consist of a single sector
		assert_vector(def.size,
			"Error registering map: size must be a vector")
		assert(def.size.x > 0 and
			def.size.y > 0 and
			def.size.z > 0,
			"Error registering map: x, y and z of size must be positive")
		assert(type(def.path) == "string",
			"Error registering map: Path must be a string")
	end
	assert_vector(def.spawnpoint,
		"Error registering map: spawnpoint must be a vector")
	
	for k, v in pairs(def.minpos)
	do
		assert(v % 16 == 0, "Error registering map: dimensions of minpos must be divisible by 16")
	end
	def.name = name
	
	map_master.registered_maps[name] = def
end




--TODO: make work with sectors
local function set_spawnpoint(map)
	local spawn = vector.add(map.minpos, map.spawnpoint)
	movable_spawnpoint.set_spawnpoint(spawn)
end

local function place_schematics(sector, done_callback)
	
	--defining variables that persist throughout iteration
	--by having them outside the iterating function
	local minpos = sector.minpos
	
	local i, ii, iii = 0, 0, 0
	local ix = minpos.x
	local iy = minpos.y
	local iz = minpos.z
	
	--iterating funciton that will run asynchronously
	local function place_async()
		local success
		for I = 1, 10
		do
			local pos1 = vector.new(ix, iy, iz)
			local filename = sector.path..i.."_"..ii.."_"..iii..".mts"
			success = minetest.place_schematic(
				pos1,
				filename,
				nil,
				nil,
				true)
		
			iii = iii + 1
			iz = iz + 16
			if iii == sector.size.z
			then
				iii = 0
				iz = minpos.z
				ii = ii + 1
				iy = iy + 16
				
				if ii == sector.size.y
				then
					ii = 0
					iy = minpos.y
					i = i + 1
					ix = ix + 16
				end
			end
			if i >= sector.size.x or not success
			then
				break
			end
		end
		if i < sector.size.x and success
		then
			--asynchronous looping
			minetest.after(0.05, place_async)
		else
			if done_callback
			then
				done_callback(success)
			end
		end
	end
	place_async()
end

local function place_map(map, done_callback)
	if map.sectors
	then
		local sectors_done = 0
		local overall_success = true
		local function on_sector_done(success)
			
			sectors_done = sectors_done + 1
			overall_success = overall_success and success
			
			if sectors_done == #map.sectors
			then
				done_callback(overall_success)
				
				if overall_success
				then
					set_current_map(map.name)
				end
			end
		end
		
		--place each sector
		for name, sector in pairs(map.sectors)
		do
			place_schematics(sector, on_sector_done)
		end
		
	else
		local function on_map_done(success)
			done_callback(success)
			if success
			then
				set_current_map(map.name)
			end
		end
		place_schematics(map, on_map_done)
	end
end




--in map blocks
local clear_width = 0

--TODO: make work with sectors
local function demolish_sector(sector)
	local min = vector.subtract(sector.minpos,
		vector.multiply({x = clear_width, y = clear_width, z = clear_width}, 16))
		
	local max = vector.add(
		vector.add(sector.minpos,
		vector.multiply(sector.size, 16)),
		vector.multiply({x = clear_width, y = clear_width, z = clear_width}, 16))
		
	minetest.delete_area(min, max)
end

local function demolish_old_map()
	if current_map == "" --no map loaded yet, no need to demolish anything
	then
		return
	end
	local map = map_master.registered_maps[current_map]
	
	if map.sectors
	then
		for name, sector in pairs(map.sectors)
		do
			demolish_sector(sector)
		end
	else
		demolish_sector(map)
	end
end



--
function map_master.load_map(name, done_callback)
	local map = map_master.registered_maps[name]
	if not map
	then
		return false
	end
	
	demolish_old_map()
	minetest.chat_send_all("done demolishing old map")
	place_map(map, done_callback)
	set_spawnpoint(map)
	
	return true
end
