edit = {}
edit.player_data = {}

edit.paste_preview_max_entities = tonumber(minetest.settings:get("edit_paste_preview_max_entities") or 2000)	-- Original 2000
edit.max_operation_volume = tonumber(minetest.settings:get("edit_max_operation_volume") or 2000)				-- Original 2000
edit.fast_node_fill_threshold = tonumber(minetest.settings:get("edit_fast_node_fill_threshold") or 2000)		-- Original 2000
edit.polygon_preview_wire_frame_threshold = tonumber(minetest.settings:get("edit_polygon_preview_wire_frame_threshold") or 40)

minetest.register_privilege("edit", {
	description = "Allows usage of edit mod nodes",
	give_to_singleplayer = true,
	give_to_admin = true,
})

function edit.has_privilege(player)
	local name = player:get_player_name()
	if minetest.check_player_privs(name, {edit = true}) then
		return true
	else
		minetest.chat_send_player(name, "Using edit nodes requires the edit privilege.")
		return false
	end
end

function edit.display_size_error(player)
	local msg = "Operation too large. The maximum operation volume can be changed in Minetest settings."
	minetest.chat_send_player(player:get_player_name(), msg)
end

function edit.on_place_checks(player)
	return player and
		player:is_player() and
		edit.has_privilege(player)
end

function edit.reliable_show_formspec(player, name, formspec)
	-- We need to do this nonsense because there is bug in Minetest
	-- Sometimes no formspec is shown if you call minetest.show_formspec
	-- from minetest.register_on_player_receive_fields
	minetest.after(0.1, function()
		if not player or not player:is_player() then return end
		minetest.show_formspec(player:get_player_name(), name, formspec)
	end)
end

minetest.register_on_joinplayer(function(player)
	edit.player_data[player] = {
		schematic_offset = vector.new(0, 0, 0),
		mirror_mode = "x",
	}
end)

minetest.register_on_leaveplayer(function(player)
	for key, value in pairs(edit.player_data[player]) do
		if type(value) == "table" and value.object then
			value.object:remove()
		end
	end
	edit.player_data[player] = nil
end)

function edit.get_pointed_thing_node(player)
	local look_dir = player:get_look_dir()
	local pos1 = player:get_pos()
	local eye_height = player:get_properties().eye_height
	pos1.y = pos1.y + eye_height
	local pos2 = vector.add(pos1, vector.multiply(look_dir, 10))
	local ray = minetest.raycast(pos1, pos2, false, false)
	for pointed_thing in ray do
		if pointed_thing.under then
			return pointed_thing
		end
	end
	local pos = vector.round(pos2)
	return { type = "node", under = pos, above = pos, intersection_point = pos}
end

function edit.pointed_thing_to_pos(pointed_thing)
	local pos = pointed_thing.under
	local node = minetest.get_node_or_nil(pos)
	local def = node and minetest.registered_nodes[node.name]
	if def and def.buildable_to then
		return pos
	end

	pos = pointed_thing.above
	node = minetest.get_node_or_nil(pos)
	def = node and minetest.registered_nodes[node.name]
	if def and def.buildable_to then
		return pos
	end
end

function edit.get_half_node_pointed_pos(player)
	local intersection_point = edit.get_pointed_thing_node(player).intersection_point

	local pos = vector.round(intersection_point)
	local pos_list = {
		pos,
		vector.add(pos, vector.new(0.5, 0, 0.5)),
		vector.add(pos, vector.new(-0.5, 0, -0.5)),
		vector.add(pos, vector.new(0.5, 0, -0.5)),
		vector.add(pos, vector.new(-0.5, 0, 0.5))
	}

	local shortest_length = 1
	local pos
	for i, p in pairs(pos_list) do
		local length = vector.length(vector.subtract(intersection_point, p))
		if length < shortest_length then
			shortest_length = length
			pos = p
		end
	end
	return pos
end

local old_register_on_dignode = minetest.register_on_dignode
local registered_on_dignode = {}
minetest.register_on_dignode = function(func)
	table.insert(registered_on_dignode, func)
	return old_register_on_dignode(func)
end

local old_register_on_placenode = minetest.register_on_placenode
local registered_on_placenode = {}
minetest.register_on_placenode = function(func)
	table.insert(registered_on_placenode, func)
	return old_register_on_placenode(func)
end

function edit.place_item_like_player(player, item, pos)
	local node = table.copy(item)
	local itemstack = ItemStack(item.name)
	node.name = itemstack:get_name()
	local def = minetest.registered_items[node.name]
	if not def then return end
	local is_node = minetest.registered_nodes[node.name] ~= nil
	local pointed_thing = {
		type = "node",
		above = pos,
		under = pos,
	}
	minetest.remove_node(pos)
	def.on_place(itemstack, player, pointed_thing)

	local new_node = minetest.get_node(pos)
	if
		is_node and new_node.name == "air"
		and minetest.get_item_group(node.name, "falling_node") == 0
	then
		new_node.name = node.name
	end

	new_node.param2 = node.param2 or new_node.param2
	minetest.swap_node(pos, new_node)

	if node.name == "air" then
		local oldnode = {name = "air"}
		for i, func in pairs(registered_on_dignode) do
			func(pos, oldnode, player)
		end
	elseif is_node then
		local oldnode = {name = "air"}
		for i, func in pairs(registered_on_placenode) do
			func(pos, node, player, oldnode, itemstack, pointed_thing)
		end
	end
end

function edit.add_marker(id, pos, player)
	local obj_ref = minetest.add_entity(pos, id)
	if not obj_ref then return end
	local luaentity = obj_ref:get_luaentity()
	luaentity._pos = pos
	luaentity._placer = player
	return luaentity
end

local function player_select_item_formspec(player)
	local d = edit.player_data[player]
	local search_value = d.player_select_item_search_value
	local doing_search = #search_value > 0
	local inv = minetest.get_inventory({type = "player", name = player:get_player_name()})
	local size = doing_search and 12 * 8 or inv:get_size("main")
	local width = doing_search and 12 or inv:get_width("main")
	if width <= 0 then width = 8 end

	local formspec_width = math.max(width, 8) + 0.4
	local formspec_height = math.ceil(size / width) + 3.4

	local search_results = {}
	if doing_search then
		local search_words = {}
		for word in search_value:gmatch("([^%s]+)") do
			table.insert(search_words, word:lower())
		end

		local search_results_done = false
		for id, def in pairs(minetest.registered_items) do
			if minetest.get_item_group(id, "not_in_creative_inventory") == 0 then
				local add_node_to_results = true
				for i, word in pairs(search_words) do
					local description = def.description:lower() or ""
					if not description:find(word) and not id:find(word) then
						add_node_to_results = false
						break
					end
				end
				if add_node_to_results then
					table.insert(search_results, id)
					if #search_results > size then
						search_results_done = true
						break
					end
				end
			end
			if search_results_done then
				break
			end
		end
	end

	local title = doing_search and "Search Results:" or "Inventory:"

	if #search_results > size then title = title .. " (some omited)" end

	local formspec = "formspec_version[4]size[" .. formspec_width .. "," .. formspec_height .. "]" ..
		"button[0,0;0,0;minetest_sucks;" .. math.random() .. "]" .. -- Force Minetest to show this formspec
		"label[0.5,0.7;" .. d.player_select_item_message .. "]" ..
		"button_exit[" .. formspec_width - 1.2 .. ",0.2;1,1;quit;X]" ..
		"field_close_on_enter[search_field;false]" ..
		"label[0.2,1.7;Search all items]" ..
		"field[0.2,2;3.5,0.8;search_field;;" .. search_value .. "]" ..
		"image_button[3.7,2;0.8,0.8;search.png^[resize:48x48;search_button;]" ..
		"button[4.5,2;0.8,0.8;cancel_search;X]" ..
		"label[5.6,2.8;" .. title .. "]"

	for i = 1, size do
		local name
		if doing_search then
			name = search_results[i]
		else
			name = inv:get_stack("main", i):to_string()
		end

		if not name then break end

		if name == "" then name = "air" end

		local index = i - 1
		local x = 0.2 + index % width
		local y = 3.2 + math.floor(index / width)
		formspec =
			formspec ..
			"item_image_button[" ..
			x .. "," ..
			y .. ";1,1;" ..
			name .. ";" ..
			name .. ";]"
	end
	edit.reliable_show_formspec(player, "edit:player_select_item", formspec)
end

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname ~= "edit:player_select_item" then return false end
	local d = edit.player_data[player]

	for key, val in pairs(fields) do
		if key:find(":") or key == "air" then
			if d.player_select_item_callback then
				d.player_select_item_callback(player, key)
				d.player_select_item_callback = nil
				minetest.close_formspec(player:get_player_name(), "edit:player_select_item")
			end
			return true
		end
	end

	if fields.quit then
		if d.player_select_item_callback then
			d.player_select_item_callback(player, nil)
			d.player_select_item_callback = nil
		end
		return true
	elseif fields.cancel_search then
		fields.search_field = ""
	end

	if
		fields.search_field and 
		fields.search_field ~= d.player_select_item_search_value
	then
		d.player_select_item_search_value = fields.search_field
		player_select_item_formspec(player)
		return true
	end
	return true
end)

function edit.player_select_item(player, message, callback)
	local d = edit.player_data[player]
	if d.player_select_item_callback then
		d.player_select_item_callback(player, nil)
	end
	d.player_select_item_callback = callback
	d.player_select_item_search_value = d.player_select_item_search_value or ""
	d.player_select_item_message = message
	player_select_item_formspec(player)
end

edit.modpath = minetest.get_modpath("edit")
dofile(edit.modpath .. "/copy.lua")
dofile(edit.modpath .. "/fill.lua")
dofile(edit.modpath .. "/open.lua")
dofile(edit.modpath .. "/paste.lua")
dofile(edit.modpath .. "/preview.lua")
dofile(edit.modpath .. "/save.lua")
dofile(edit.modpath .. "/schematic.lua")
dofile(edit.modpath .. "/undo.lua")
dofile(edit.modpath .. "/circle.lua")
dofile(edit.modpath .. "/mirror.lua")
dofile(edit.modpath .. "/screwdriver.lua")
dofile(edit.modpath .. "/replace.lua")
dofile(edit.modpath .. "/polygon.lua")
dofile(edit.modpath .. "/line.lua")
dofile(edit.modpath .. "/bag.lua")
