-- LUALOCALS < ---------------------------------------------------------
local get_mod_api, math, minetest, pairs, piredo_player, posrel,
      startswith, string, tonumber, vector
    = get_mod_api, math, minetest, pairs, piredo_player, posrel,
      startswith, string, tonumber, vector
local math_random, math_randomseed, string_format, string_sub
    = math.random, math.randomseed, string.format, string.sub
-- LUALOCALS > ---------------------------------------------------------

local api = get_mod_api()
local statedb = api.statedb

-- First integer that starts players in a "normal" domestic room
-- (bedroom) instead of halls or more exotic rooms.
local rngseed = 8

local function withrand(rng, func)
	local oldrng = math_random(1, 0x7FFFFFFF)
	local function helper(...)
		math_randomseed(oldrng)
		return ...
	end
	math_randomseed(rng)
	return helper(func())
end

local function chooseroom()
	if statedb.moves == "N" then
		return "special_blocked"
	end

	for k, v in pairs({
			SNSN = "large_forrest_maze",
			WEWE = "large_forrest_maze",
			WSEN = "large_forge",
			ESWN = "large_throneroom",
			EEEESWN = "large_tower",
			NNNESWN = "large_lake",
			WWWESWN = "large_graveyard",
			SSSESWN = "special_final_room"
			.. (statedb.final_done and "_2" or ""),
			ENWEN = "special_candle_room",
			SWENWSE = "large_clock_room"
		}) do
		if startswith(statedb.moves, k) then
			return v
		end
	end

	statedb.moveqty = (statedb.moveqty or 0) + 1
	statedb.rng = minetest.sha1((statedb.rng or rngseed)
		.. statedb.moveqty .. statedb.moves)

	local chosen = withrand(tonumber(string_sub(statedb.rng, 1, 7), 16), function()
			if statedb.moveqty > 5 and math_random(1, 5) == 1 then
				local halls = api.find_schematics("quad_hall")
				return halls[math_random(1, #halls)]
			end

			if not (statedb.queue and statedb.queue[1]) then
				statedb.queue = api.find_schematics("normal_")

				-- Secret bonus for people who stick around after
				-- uncovering the exit.
				if api.statedb.final_done then
					statedb.queue[#statedb.queue + 1] = "mystery_gold"
				end
			end
			local pickid = math_random(1, #statedb.queue)
			local pick = statedb.queue[pickid]
			statedb.queue[pickid] = statedb.queue[#statedb.queue]
			statedb.queue[#statedb.queue] = nil
			return pick
		end)
	api.savedb()
	return chosen
end

local putschem = api.putschem
local function moveroom(movekey, ppos, roompos)
	if ppos then piredo_player.save_player_pos(ppos) end

	statedb.lastmove = movekey
	statedb.lastpos = roompos
	statedb.moves = movekey .. (statedb.moves
		and string_sub(statedb.moves, 1, 20) or "")
	api.savedb()

	local room = chooseroom()
	local big = startswith(room, "large_")
	minetest.log("action", string_format("entering room %q%s",
			room, big and " (large)" or ""))

	do
		local dt = api.allschematics[room] and api.allschematics[room].daytime
		if dt and dt >= 0.3 and dt <= 0.7 then
			dt = dt - 0.05 + math_random() * 0.1
		end
		statedb.daytime = dt
	end

	local rel = posrel(roompos)
	if big and (movekey == "W" or movekey == "S") then
		roompos = rel(-20, 0, -20)
		rel = posrel(roompos)
	end

	putschem(roompos, big and "clear_40" or "clear_20")
	putschem(roompos, room)

	putschem(rel(-20, 0, 0), "hall_x_minus")
	putschem(rel(0, 0, -20), "hall_z_minus")
	if big then
		putschem(rel(-20, 0, 20), "hall_x_minus")
		putschem(rel(20, 0, -20), "hall_z_minus")
		putschem(rel(40, 0, 0), "hall_x_plus")
		putschem(rel(40, 0, 20), "hall_x_plus")
		putschem(rel(0, 0, 40), "hall_z_plus")
		putschem(rel(20, 0, 40), "hall_z_plus")
	else
		putschem(rel(20, 0, 0), "hall_x_plus")
		putschem(rel(0, 0, 20), "hall_z_plus")
	end

	return api.allschematics[room]
	and api.allschematics[room].disallow_changes
	or api.set_current_room(roompos, room)
end

do
	local old = piredo_player.restore_player_pos
	function piredo_player.restore_player_pos(...)
		if statedb.lastmove and statedb.lastpos then
			moveroom(statedb.lastmove, nil, statedb.lastpos)
		end
		return old(...)
	end
end

local function triggernode(pos)
	local rel = posrel(pos)
	local node = minetest.get_node_or_nil(pos)
	local def = node and minetest.registered_nodes[node.name]
	if not def then return end
	local pend = statedb.pendmove
	if def.piredo_map_trigger_move and not pend then
		minetest.log("action", "entered trigger "
			.. def.piredo_map_trigger_move.dir)
		statedb.pendmove = {
			dir = def.piredo_map_trigger_move.dir,
			from = pos,
			pos = rel(
				def.piredo_map_trigger_move.x,
				def.piredo_map_trigger_move.y,
				def.piredo_map_trigger_move.z
			)
		}
		api.savedb()
	elseif statedb.pendmove and not def.piredo_map_trigger_move then
		minetest.log("action", "exited trigger")
		if (pend.dir == "N" and pos.z > pend.from.z)
		or (pend.dir == "S" and pos.z < pend.from.z)
		or (pend.dir == "E" and pos.x > pend.from.x)
		or (pend.dir == "W" and pos.x < pend.from.x)
		then moveroom(pend.dir, pos, pend.pos) end
		statedb.pendmove = nil
		api.savedb()
	end
end

do
	local oldpos
	piredo_player.register_playerstep(function(player)
			local pos = player:get_pos()
			pos.y = pos.y + 0.25
			pos = vector.round(pos)
			if not oldpos then
				oldpos = pos
				return
			end
			if vector.equals(pos, oldpos) then return end

			local diff = vector.subtract(pos, oldpos)
			local len = vector.length(diff)
			local dir = vector.multiply(diff, 1 / len)

			local spos = oldpos
			for i = 0.25, len, 0.25 do
				local npos = vector.round(vector.add(oldpos, vector.multiply(dir, i)))
				if not vector.equals(spos, npos) then
					spos = npos
					if triggernode(npos) then break end
				end
			end

			oldpos = pos
		end)
end
