---------------------------------------------------------------------------------------
-- simple anvil that can be used to repair tools
---------------------------------------------------------------------------------------
-- * can be used to repair tools
-- * the hammer gets damaged a bit at each repair step
---------------------------------------------------------------------------------------

anvil = {
	setting = {
		item_displacement = 2 / 16,
	}
}

minetest.register_alias("castle:anvil", "anvil:anvil")

local hammer_repairable = minetest.settings:get_bool("anvil_hammer_is_repairable", true)
local hud_timeout = 2  -- seconds

anvil.make_unrepairable = function(item_name)
	local item_def = minetest.registered_items[item_name]
	if item_def then
		local groups = table.copy(item_def.groups)
		groups.not_repaired_by_anvil = 1
		minetest.override_item(item_name, {groups = groups})
	end
end

if minetest.get_modpath("technic") then
	-- make rechargeable technic tools unrepairable
	anvil.make_unrepairable("technic:water_can")
	anvil.make_unrepairable("technic:lava_can")
	anvil.make_unrepairable("technic:flashlight")
	anvil.make_unrepairable("technic:battery")
	anvil.make_unrepairable("technic:vacuum")
	anvil.make_unrepairable("technic:prospector")
	anvil.make_unrepairable("technic:sonic_screwdriver")
	anvil.make_unrepairable("technic:chainsaw")
	anvil.make_unrepairable("technic:laser_mk1")
	anvil.make_unrepairable("technic:laser_mk2")
	anvil.make_unrepairable("technic:laser_mk3")
	anvil.make_unrepairable("technic:mining_drill")
	anvil.make_unrepairable("technic:mining_drill_mk2")
	anvil.make_unrepairable("technic:mining_drill_mk2_1")
	anvil.make_unrepairable("technic:mining_drill_mk2_2")
	anvil.make_unrepairable("technic:mining_drill_mk2_3")
	anvil.make_unrepairable("technic:mining_drill_mk2_4")
	anvil.make_unrepairable("technic:mining_drill_mk3")
	anvil.make_unrepairable("technic:mining_drill_mk3_1")
	anvil.make_unrepairable("technic:mining_drill_mk3_2")
	anvil.make_unrepairable("technic:mining_drill_mk3_3")
	anvil.make_unrepairable("technic:mining_drill_mk3_4")
	anvil.make_unrepairable("technic:mining_drill_mk3_5")
end

local S = minetest.get_translator(minetest.get_current_modname())

local shared_anvil_item = ItemStack({
	name = "anvil:anvil",
	meta = {
		shared = 1,
		description = S("Shared anvil")
	}
})

-- the hammer for the anvil

local hammer_def = {
	description = S("Steel blacksmithing hammer"),
	_doc_items_longdesc = S("A tool for repairing other tools at a blacksmith's anvil."),
	_doc_items_usagehelp = S("Use this hammer to strike blows upon an anvil bearing a damaged tool and you can repair it. "
		.. "It can also be used for smashing stone, but it is not well suited to this task."),
	image = "anvil_tool_steelhammer.png",
	inventory_image = "anvil_tool_steelhammer.png",

	tool_capabilities = {
		full_punch_interval = 0.8,
		max_drop_level = 1,
		groupcaps = {
			-- about equal to a stone pick (it's not intended as a tool)
			cracky = {times = {[2] = 2.00, [3] = 1.20}, uses = 30, maxlevel = 1},
		},
		damage_groups = {fleshy = 6},
	}
}

if not hammer_repairable then
	hammer_def.groups = {["not_repaired_by_anvil"] = 1}
end

minetest.register_tool("anvil:hammer", hammer_def)

local tmp = {}

minetest.register_entity("anvil:item", {
	hp_max = 1,
	visual = "wielditem",
	visual_size = {x = .33, y = .33},
	collisionbox = {0, 0, 0, 0, 0, 0},
	physical = false,
	textures = {"air"},
	on_activate = function(self, staticdata)
		if tmp.nodename ~= nil and tmp.texture ~= nil then
			self.nodename = tmp.nodename
			tmp.nodename = nil
			self.texture = tmp.texture
			tmp.texture = nil
		else
			if staticdata ~= nil and staticdata ~= "" then
				local data = staticdata:split(";")
				if data and data[1] and data[2] then
					self.nodename = data[1]
					self.texture = data[2]
				end
			end
		end
		if self.texture ~= nil then
			self.object:set_properties({textures = {self.texture}})
		end
	end,
	get_staticdata = function(self)
		if self.nodename ~= nil and self.texture ~= nil then
			return self.nodename .. ";" .. self.texture
		end
		return ""
	end,
	on_blast = function()
		return false, false, {}
	end,
})

local remove_item = function(pos, node)
	local npos = vector.new(pos.x, pos.y + anvil.setting.item_displacement, pos.z)
	local objs = minetest.get_objects_inside_radius(npos, .5)
	if objs then
		for _, obj in ipairs(objs) do
			if obj and obj:get_luaentity() and obj:get_luaentity().name == "anvil:item" then
				obj:remove()
			end
		end
	end
end

local update_item = function(pos, node)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	if not inv:is_empty("input") then
		pos.y = pos.y + anvil.setting.item_displacement
		tmp.nodename = node.name
		tmp.texture = inv:get_stack("input", 1):get_name()
		local e = minetest.add_entity(pos, "anvil:item")
		local yaw = math.pi * 2 - node.param2 * math.pi / 2
		e:set_rotation({x = -1.5708, y = yaw, z = 0}) -- x is pitch, 1.5708 is 90 degrees.
	end
end


local function has_access(pos, player)
	local name = player:get_player_name()
	local meta = minetest.get_meta(pos)
	local owner = meta:get_string("owner")
	local shared = meta:get_int("shared") == 1
	if shared or name == owner then
		return true
	else
		return false
	end
end

local hud_info_by_puncher_name = {}

minetest.register_globalstep(function()
	local now = os.time()

	for puncher_name, hud_info in pairs(hud_info_by_puncher_name) do
		local hud2, hud3, hud_expire_time = unpack(hud_info)
		if now > hud_expire_time then
			local puncher = minetest.get_player_by_name(puncher_name)
			if puncher then
				local hud2_def = puncher:hud_get(hud2)
				if hud2_def and hud2_def.name == "anvil_background" then
					puncher:hud_remove(hud2)
				end

				local hud3_def = puncher:hud_get(hud3)
				if hud3_def and hud3_def.name == "anvil_foreground" then
					puncher:hud_remove(hud3)
				end
			end

			hud_info_by_puncher_name[puncher_name] = nil
		end
	end
end)

local function anvil_rightclick(pos, node, clicker, itemstack)
	if not clicker or not itemstack then
		return
	end
	local meta = minetest.get_meta(pos)
	local name = clicker:get_player_name()
	local owner = meta:get_string("owner")
	local shared = meta:get_int("shared") == 1

	if name ~= owner and not shared then
		return itemstack
	end

	if itemstack:get_count() == 0 then
		local inv = meta:get_inventory()
		if not inv:is_empty("input") then
			local return_stack = inv:get_stack("input", 1)
			inv:set_stack("input", 1, nil)
			local wield_index = clicker:get_wield_index()
			clicker:get_inventory():set_stack("main", wield_index, return_stack)
			if shared then
				meta:set_string("infotext", S("Shared anvil"))
			else
				meta:set_string("infotext", S("@1's anvil", owner))
			end
			remove_item(pos, node)
			return return_stack
		end
	end

	local this_def = minetest.registered_nodes[node.name]
	if this_def.allow_metadata_inventory_put(pos, "input", 1, itemstack:peek_item(), clicker) > 0 then
		local s = itemstack:take_item()
		local inv = meta:get_inventory()
		inv:add_item("input", s)
		local meta_description = s:get_meta():get_string("description")
		if "" ~= meta_description then
			if shared then
				meta:set_string("infotext", S("Shared anvil"))
			else
				meta:set_string("infotext", S("@1's anvil", owner) .. "\n" .. meta_description)
			end
		end
		meta:set_int("informed", 0)
		update_item(pos, node)
	end
	return itemstack
end

local function anvil_rotate(pos, node, user, mode, new_param2)
	if minetest.get_modpath("screwdriver") ~= nil and mode == screwdriver.ROTATE_FACE then
		return
	end

	if not minetest.is_player(user) then
		return
	end

	local player_name = user:get_player_name()
	local wield_list = user:get_wield_list()
	local wield_index = user:get_wield_index()
	local wielded_original = user:get_inventory():get_stack(wield_list, wield_index)

	minetest.after(0,function()
		local player = minetest.get_player_by_name(player_name)
		if not player then
			return
		end

		local inv = player:get_inventory()
		local wielded = inv:get_stack(wield_list, wield_index)

		if wielded:get_name() ~= wielded_original:get_name() then
			return
		end

		local node_now = minetest.get_node(pos)
		if node_now.name ~= "anvil:anvil" then
			return
		end

		local tool_after_rightclicking = anvil_rightclick(pos, node_now, player, wielded)
		inv:set_stack(wield_list, wield_index, tool_after_rightclicking)
	end)

	return false
end

minetest.register_node("anvil:anvil", {
	drawtype = "nodebox",
	description = S("Anvil"),
	_doc_items_longdesc = S("A tool for repairing other tools in conjunction with a blacksmith's hammer."),
	_doc_items_usagehelp = S("Right-click on this anvil with a damaged tool to place the damaged tool upon it. " ..
		"You can then repair the damaged tool by striking it with a blacksmith's hammer. " ..
		"Repeated blows may be necessary to fully repair a badly worn tool. " ..
		"To retrieve the tool either punch or right-click the anvil with an empty hand."),
	tiles = {"default_stone.png"},
	paramtype = "light",
	paramtype2 = "facedir",
	groups = {cracky = 2},
	sounds = default.node_sound_metal_defaults(),
	-- the nodebox model comes from realtest
	node_box = {
		type = "fixed",
		fixed = {
			{-0.5, -0.5, -0.3, 0.5, -0.4, 0.3},
			{-0.35, -0.4, -0.25, 0.35, -0.3, 0.25},
			{-0.3, -0.3, -0.15, 0.3, -0.1, 0.15},
			{-0.35, -0.1, -0.2, 0.35, 0.1, 0.2},
		},
	},
	selection_box = {
		type = "fixed",
		fixed = {
			{-0.5, -0.5, -0.3, 0.5, -0.4, 0.3},
			{-0.35, -0.4, -0.25, 0.35, -0.3, 0.25},
			{-0.3, -0.3, -0.15, 0.3, -0.1, 0.15},
			{-0.35, -0.1, -0.2, 0.35, 0.1, 0.2},
		}
	},

	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		inv:set_size("input", 1)
	end,

	after_place_node = function(pos, placer, itemstack)
		local meta = minetest.get_meta(pos)
		local stackmeta = itemstack:get_meta()
		if stackmeta:get_int("shared") == 1 then
			meta:set_int("shared", 1)
			meta:set_string("infotext", S("Shared anvil"))
		else
			meta:set_string("owner", placer:get_player_name() or "")
			meta:set_string("infotext", S("@1's anvil", placer:get_player_name()))
		end
	end,

	preserve_metadata = function(pos, oldnode, oldmeta, drops)
		if drops[1] and tonumber(oldmeta.shared) == 1 then
			drops[1] = ItemStack(shared_anvil_item)
		end
		return drops
	end,

	can_dig = function(pos, player)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()

		if not inv:is_empty("input") then
			return false
		end
		return true
	end,

	allow_metadata_inventory_put = function(pos, listname, index, stack, player)
		local meta = minetest.get_meta(pos)
		if listname ~= "input" then
			return 0
		end

		if not has_access(pos, player) then
			return 0
		end

		local player_name = player:get_player_name()
		if stack:get_wear() == 0 then
			minetest.chat_send_player(player_name, S("This anvil is for damaged tools only."))
			return 0
		end

		local stack_name = stack:get_name()
		if minetest.get_item_group(stack_name, "not_repaired_by_anvil") ~= 0 then
			local item_def = minetest.registered_items[stack_name]
			minetest.chat_send_player(player_name, S("@1 cannot be repaired with an anvil.", item_def.description))
			return 0
		end

		if meta:get_inventory():room_for_item("input", stack) then
			return stack:get_count()
		end
		return 0
	end,

	allow_metadata_inventory_take = function(pos, listname, index, stack, player)
		if not has_access(pos, player) then
			return 0
		end

		if listname ~= "input" then
			return 0
		end
		return stack:get_count()
	end,

	on_rotate = anvil_rotate,
	on_rightclick = anvil_rightclick,

	on_punch = function(pos, node, puncher)
		if not pos or not node or not puncher then
			return
		end

		local wielded = puncher:get_wielded_item()
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		local owner = meta:get_string("owner")
		local shared = meta:get_int("shared") == 1
		local puncher_name = puncher:get_player_name()
		if not shared and owner ~= puncher_name then
			return
		end

		if wielded:get_count() == 0 then
			if not inv:is_empty("input") then
				local return_stack = inv:get_stack("input", 1)
				inv:set_stack("input", 1, nil)
				local wield_index = puncher:get_wield_index()
				puncher:get_inventory():set_stack("main", wield_index, return_stack)
				if shared then
					meta:set_string("infotext", S("Shared anvil"))
				else
					meta:set_string("infotext", S("@1's anvil", owner))
				end
				remove_item(pos, node)
			end
		end

		-- only punching with the hammer is supposed to work
		if wielded:get_name() ~= "anvil:hammer" then
			return
		end
		local input = inv:get_stack("input", 1)

		-- only tools can be repaired
		if not input or input:is_empty() then
			return
		end

		-- 65535 is max damage
		local damage_state = 40 - math.floor(input:get_wear() / 1638)

		local tool_name = input:get_name()

		if input:get_wear() > 0 then
			local hud2, hud3, hud3_def

			if hud_info_by_puncher_name[puncher_name] then
				hud2, hud3 = unpack(hud_info_by_puncher_name[puncher_name])
				hud3_def = puncher:hud_get(hud3)
			end

			if hud3_def and hud3_def.name == "anvil_foreground" then
				puncher:hud_change(hud3, "number", damage_state)

			else
				hud2 = puncher:hud_add({
					name = "anvil_background",
					hud_elem_type = "statbar",
					text = "default_cloud.png^[colorize:#ff0000:256",
					number = 40,
					direction = 0, -- left to right
					position = {x = 0.5, y = 0.65},
					alignment = {x = 0, y = 0},
					offset = {x = -320, y = 0},
					size = {x = 32, y = 32},
				})
				hud3 = puncher:hud_add({
					name = "anvil_foreground",
					hud_elem_type = "statbar",
					text = "default_cloud.png^[colorize:#00ff00:256",
					number = damage_state,
					direction = 0, -- left to right
					position = {x = 0.5, y = 0.65},
					alignment = {x = 0, y = 0},
					offset = {x = -320, y = 0},
					size = {x = 32, y = 32},
				})
			end

			hud_info_by_puncher_name[puncher_name] = {hud2, hud3, os.time() + hud_timeout}
		end

		-- tell the player when the job is done
		if input:get_wear() == 0 then
			-- but only once
			if 0 < meta:get_int("informed") then
				return
			end
			meta:set_int("informed", 1)
			local tool_desc
			local meta_description = input:get_meta():get_string("description")
			if "" ~= meta_description then
				tool_desc = meta_description
			elseif minetest.registered_items[tool_name] and minetest.registered_items[tool_name].description then
				tool_desc = minetest.registered_items[tool_name].description
			else
				tool_desc = tool_name
			end
			minetest.chat_send_player(puncher_name, S("Your @1 has been repaired successfully.", tool_desc))
			return
		else
			pos.y = pos.y + anvil.setting.item_displacement
			minetest.sound_play({name = "anvil_clang"}, {pos = pos})
			minetest.add_particlespawner({
				amount = 10,
				time = 0.1,
				minpos = pos,
				maxpos = pos,
				minvel = {x = 2, y = 3, z = 2},
				maxvel = {x = -2, y = 1, z = -2},
				minacc = {x = 0, y = -10, z = 0},
				maxacc = {x = 0, y = -10, z = 0},
				minexptime = 0.5,
				maxexptime = 1,
				minsize = 1,
				maxsize = 1,
				collisiondetection = true,
				vertical = false,
				texture = "anvil_spark.png",
			})
		end

		-- do the actual repair
		input:add_wear(-5000) -- equals to what technic toolshop does in 5 seconds
		inv:set_stack("input", 1, input)

		-- damage the hammer slightly
		wielded:add_wear(100)
		puncher:set_wielded_item(wielded)
	end,
	is_ground_content = false,

	on_blast = function(pos, intensity)
		local drops = {}
		local meta = minetest.get_meta(pos)
		if meta:get_int("shared") == 1 then
			drops[1] = ItemStack(shared_anvil_item)
		else
			drops[1] = ItemStack("anvil:anvil")
		end
		local inv = meta:get_inventory()
		local input = inv:get_stack("input", 1)
		if not input:is_empty() then
			drops[2] = input:to_string()
		end
		remove_item(pos)
		minetest.remove_node(pos)

		return drops
	end,
})

-- automatically restore entities lost due to /clearobjects or similar
minetest.register_lbm({
	name = "anvil:anvil_item_restoration",
	nodenames = {"anvil:anvil"},
	run_at_every_load = true,
	action = function(pos, node, active_object_count, active_object_count_wider)
		local test_pos = {x = pos.x, y = pos.y + anvil.setting.item_displacement, z = pos.z}
		if #minetest.get_objects_inside_radius(test_pos, 0.5) > 0 then
			return
		end
		update_item(pos, node)
	end
})

-- Transfer the hammer from the old hammer storage slot to the main slot, or else drop it in world
minetest.register_lbm({
	name = "anvil:hammer_ejection",
	nodenames = "anvil:anvil",
	run_at_every_load = false,
	action = function(pos, node)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		if not inv:is_empty("hammer") then
			local hammer = inv:get_stack("hammer", 1)
			inv:set_stack("hammer", 1, nil)
			inv:set_size("hammer", 0)
			if inv:is_empty("input") then
				inv:set_stack("input", 1, hammer) -- the abm will ensure there's an entity showing the hammer is here
			else
				minetest.add_item({x = pos.x, y = pos.y + 1, z = pos.z}, hammer)
			end
		end
	end
})

---------------------------------------------------------------------------------------
-- crafting receipes
---------------------------------------------------------------------------------------
minetest.register_craft({
	output = "anvil:anvil",
	type = "shapeless",
	recipe = {"anvil:anvil"}
})

minetest.register_craft({
	output = shared_anvil_item:to_string(),
	type = "shapeless",
	recipe = {"anvil:anvil", "default:paper"}
})

minetest.register_craft({
	output = "anvil:anvil",
	recipe = {
		{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
		{"", "default:steel_ingot", ""},
		{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"}
	}
})

minetest.register_craft({
	output = "anvil:hammer",
	recipe = {
		{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
		{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
		{"group:stick", "", ""}
	}
})
