local ceiling_height = 15
local c_ceiling = minetest.get_content_id("ikea:ceiling")
local c_skylight = minetest.get_content_id("ikea:skylight")

-- Department Map Generation Logic
-- Originally Written By Warr1024

-- Wait until mapgen initializes to get the noise
local Perlin
minetest.after(0, function()
	Perlin = minetest.get_perlin(0, 1, 0, 1)
end)

-- Store functions in a table so that they're all in scope of each other without being made global
local bsp = {}

function bsp.split(r, min, max, val, bd)
	local div = min + bd + math.floor((max - min + 1 - bd * 2) * r)
	if max - min <= 1 then
		div = max
	end
	if val < div then
		return min, div - 1
	end
	return div, max
end

function bsp.pick_department(options, x_min, x_max, z_min, z_max, x, z, check_inner)
	local department = options.departments[util.bound_perlin(Perlin, #options.departments, x_min, 2, z_min)]
	local edges = {w = x <= x_min, e = x >= x_max, s = z <= z_min, n = z >= z_max}
	edges.inner = {w = false, e = false, n = false, s = false}

	-- Check for inner edges
	if check_inner then
		if edges.n then
			local adjacent_dept, _ = bsp.get_department(options, x, z + 1, false)
			edges.inner.n = adjacent_dept.name == department.name
		end
		if edges.s then
			local adjacent_dept, _ = bsp.get_department(options, x, z - 1, false)
			edges.inner.s = adjacent_dept.name == department.name
		end
		if edges.e then
			local adjacent_dept, _ = bsp.get_department(options, x + 1, z, false)
			edges.inner.e = adjacent_dept.name == department.name
		end
		if edges.w then
			local adjacent_dept, _ = bsp.get_department(options, x - 1, z, false)
			edges.inner.w = adjacent_dept.name == department.name
		end
	end

	return department, edges
end

function bsp.subdivide(options, x_min, x_max, z_min, z_max, x, z, check_inner)
	local width = x_max - x_min + 1
	local height = z_max - z_min + 1

	if (width > options.max_size) or (height > options.max_size) or (util.bound_perlin(Perlin, 2, x_min, 0, z_min) == 1) then
		if height > width then
			local z_min2, z_max2 = bsp.split(util.tail_perlin(Perlin, x_min, 1, z_min), z_min, z_max, z, options.min_size)
			local height2 = z_max2 - z_min2 + 1

			if (height2 < options.min_size) or ((height - height2) < options.min_size) then
				return bsp.pick_department(options, x_min, x_max, z_min, z_max, x, z, check_inner)
			end

			return bsp.subdivide(options, x_min, x_max, z_min2, z_max2, x, z, check_inner)
		else
			local x_min2, x_max2 = bsp.split(util.tail_perlin(Perlin, x_min, 1, z_min), x_min, x_max, x, options.min_size)
			local width2 = x_max2 - x_min2 + 1

			if (width2 < options.min_size) or ((width - width2) < options.min_size) then
				return bsp.pick_department(options, x_min, x_max, z_min, z_max, x, z, check_inner)
			end

			return bsp.subdivide(options, x_min2, x_max2, z_min, z_max, x, z, check_inner)
		end
	end

	return bsp.pick_department(options, x_min, x_max, z_min, z_max, x, z, check_inner)
end

function bsp.get_department(options, x, z, check_inner)
	return bsp.subdivide(options, -4096, 4095, -4096, 4095, x, z, check_inner)
end

minetest.register_on_generated(function(minp, maxp, seed)
	local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
	local data = vm:get_data()
	local param2s = vm:get_param2_data()
	local va = VoxelArea:new{MinEdge = emin, MaxEdge = emax}

	for z = minp.z, maxp.z, 16 do
		for x = minp.x, maxp.x, 16 do
			if va:contains(x, 0, z) then
				local mapblock_x = x / 16
				local mapblock_z = z / 16

				local department, edges = bsp.get_department(ikea.mapgen_options, mapblock_x, mapblock_z, true)
				data, param2s = department.get_data(data, param2s, va, x, z, edges)

				data = util.fill_vmanip_area(data, va, {x = x, y = ceiling_height, z = z},
					{x = x + 15, y = ceiling_height, z = z + 15}, c_ceiling)

				if util.bound_perlin(Perlin, 8, mapblock_x, 42, mapblock_z) == 1 then
					data = util.fill_vmanip_area(data, va, {x = x + 4, y = ceiling_height, z = z + 1},
						{x = x + 15 - 4, y = ceiling_height, z = z + 15 - 1}, c_skylight)
				end
			end
		end
	end

	vm:set_data(data)
	vm:set_param2_data(param2s)
	minetest.generate_decorations(vm, minp, maxp)
	vm:update_liquids()
	vm:calc_lighting()
	vm:write_to_map()
end)

