-- Anvils
-- Breaking anvils is just (&^%($#$@!!!!!
local MAX_NAME_LENGTH = 35
local MAX_WEAR = 65535
local SAME_TOOL_REPAIR_BOOST = math.ceil(MAX_WEAR * 0.12) -- 12%
local MATERIAL_TOOL_REPAIR_BOOST = {
	math.ceil(MAX_WEAR * 0.25), -- 25%
	math.ceil(MAX_WEAR * 0.5), -- 50%
	math.ceil(MAX_WEAR * 0.75), -- 75%
	MAX_WEAR, -- 100%
}

-- Given a tool and material stack, returns how many items of the material stack
-- needs to be used up to repair the tool.
local function get_consumed_materials(tool, material)
	local wear = tool:get_wear()
	-- local health = (MAX_WEAR - wear)
	local matsize = material:get_count()
	local materials_used = 0
	for m = 1, math.min(4, matsize) do
		materials_used = materials_used + 1
		if (wear - MATERIAL_TOOL_REPAIR_BOOST[m]) <= 0 then break end
	end
	return materials_used
end

---Helper function to make sure update_anvil_slots NEVER overstacks the output slot
local function fix_stack_size(stack)
	if not stack or stack == "" then return "" end
	local count = stack:get_count()
	local max_count = stack:get_stack_max()

	if count > max_count then
		stack:set_count(max_count)
		count = max_count
	end
	return count
end

-- Given 2 input stacks, tells you which is the tool and which is the material.
-- Returns ("tool", input1, input2) if input1 is tool and input2 is material.
-- Returns ("material", input2, input1) if input1 is material and input2 is tool.
-- Returns nil otherwise.
local function distinguish_tool_and_material(input1, input2)
	local def1 = input1:get_definition()
	local def2 = input2:get_definition()
	local r1 = def1._repair_material
	local r2 = def2._repair_material
	if def1.type == "tool" and r1 and type(r1) == "table" and table.indexof(r1, input2) ~= -1 then
		return "tool", input1, input2
	elseif def2.type == "tool" and r2 and type(r2) == "table" and table.indexof(r1, input1) ~=
		-1 then
		return "material", input2, input1
	elseif def1.type == "tool" and r1 then
		return "tool", input1, input2
	elseif def2.type == "tool" and r2 then
		return "material", input2, input1
	else
		return nil
	end
end

-- Update the inventory slots of an anvil node.
-- meta: Metadata of anvil node
local function update_anvil_slots(meta)
	local inv = meta:get_inventory()
	local new_name = meta:get_string("set_name")
	local input1 = inv:get_stack("input", 1)
	local input2 = inv:get_stack("input", 2)
	-- local output = inv:get_stack("output", 1)
	local new_output, name_item
	local just_rename = false

	-- Both input slots occupied
	if not input1:is_empty() and not input2:is_empty() then
		-- Repair, if tool
		local def1 = input1:get_definition()
		local def2 = input2:get_definition()

		-- Repair calculation helper.
		-- Adds the “inverse” values of wear1 and wear2.
		-- Then adds a boost health value directly.
		-- Returns the resulting (capped) wear.
		local function calculate_repair(wear1, wear2, boost)
			local new_health = (MAX_WEAR - wear1) + (MAX_WEAR - wear2)
			if boost then new_health = new_health + boost end
			return math.max(0, math.min(MAX_WEAR, MAX_WEAR - new_health))
		end

		local can_combine = mcl_enchanting.combine(input1, input2)

		if can_combine then
			-- Add tool health together plus a small bonus
			if def1.type == "tool" and def2.type == "tool" then
				local new_wear = calculate_repair(input1:get_wear(), input2:get_wear(),
				                                  SAME_TOOL_REPAIR_BOOST)
				input1:set_wear(new_wear)
			end

			name_item = input1
			new_output = name_item
			-- Tool + repair item
		else
			-- Any tool can have a repair item. This may be defined in the tool's item definition
			-- as an itemstring in the field `_repair_material`. Only if this field is set, the
			-- tool can be repaired with a material item.
			-- Example: Iron Pickaxe + Iron Ingot. `_repair_material = mcl_core:iron_ingot`

			-- Big repair bonus
			-- TODO: Combine tool enchantments
			local distinguished, tool, material = distinguish_tool_and_material(input1, input2)
			if distinguished then
				local tooldef = tool:get_definition()
				local repair = tooldef._repair_material
				local has_correct_material = false
				local material_name = material:get_name()
				if type(repair) == "string" then
					if string.sub(repair, 1, 6) == "group:" then
						has_correct_material = core.get_item_group(material_name, string.sub(repair, 7)) ~= 0
					elseif material_name == repair then
						has_correct_material = true
					end
				else
					if table.indexof(repair, material_name) ~= -1 then
						has_correct_material = true
					else
						for _, r in pairs(repair) do
							if string.sub(r, 1, 6) == "group:" then
								if core.get_item_group(material_name, string.sub(r, 7)) ~= 0 then
									has_correct_material = true
								end
							end
						end
					end
				end
				if has_correct_material and tool:get_wear() > 0 then
					local materials_used = get_consumed_materials(tool, material)
					local new_wear = calculate_repair(tool:get_wear(), MAX_WEAR,
					                                  MATERIAL_TOOL_REPAIR_BOOST[materials_used])
					tool:set_wear(new_wear)
					name_item = tool
					new_output = name_item
				else
					new_output = ""
				end
			else
				new_output = ""
			end
		end
		-- Exactly 1 input slot occupied
	elseif (not input1:is_empty() and input2:is_empty()) or
		(input1:is_empty() and not input2:is_empty()) then
		-- Just rename item
		if input1:is_empty() then
			name_item = input2
		else
			name_item = input1
		end
		just_rename = true
	else
		new_output = ""
	end

	-- Rename handling
	if name_item then
		-- No renaming allowed with group no_rename=1
		if core.get_item_group(name_item:get_name(), "no_rename") == 1 then
			new_output = ""
		else
			if new_name == nil then new_name = "" end
			local meta = name_item:get_meta()
			local old_name = meta:get_string("name")
			-- Limit name length
			new_name = string.sub(new_name, 1, MAX_NAME_LENGTH)
			-- Don't rename if names are identical
			if new_name ~= old_name then
				-- Save the raw name internally
				meta:set_string("name", new_name)
				-- Rename item handled by tt
				tt.reload_itemstack_description(name_item)
				new_output = name_item
			elseif just_rename then
				new_output = ""
			end
		end
	end

	-- Set the new output slot
	if new_output then
		fix_stack_size(new_output)
		inv:set_stack("output", 1, new_output)
	end
end

-- Given 2 input stacks, tells you which is the tool and which is the material.
-- Returns ("tool", input1, input2) if input1 is tool and input2 is material.
-- Returns ("material", input2, input1) if input1 is material and input2 is tool.
-- Returns nil otherwise.
local function distinguish_tool_and_material(input1, input2)
	local def1 = input1:get_definition()
	local def2 = input2:get_definition()
	local r1 = def1._repair_material
	local r2 = def2._repair_material
	if def1.type == "tool" and r1 and type(r1) == "table" and table.indexof(r1, input2) ~= -1 then
		return "tool", input1, input2
	elseif def2.type == "tool" and r2 and type(r2) == "table" and table.indexof(r1, input1) ~=
		-1 then
		return "material", input2, input1
	elseif def1.type == "tool" and r1 then
		return "tool", input1, input2
	elseif def2.type == "tool" and r2 then
		return "material", input2, input1
	else
		return nil
	end
end

local function anvil_on_metadata_inventory_move(pos,
                                                from_list,
                                                from_index,
                                                to_list,
                                                to_index,
                                                count,
                                                player)
	local meta = core.get_meta(pos)
	if from_list == "output" and to_list == "input" then
		local inv = meta:get_inventory()
		for i = 1, inv:get_size("input") do
			if i ~= to_index then
				local istack = inv:get_stack("input", i)
				istack:set_count(math.max(0, istack:get_count() - count))
				inv:set_stack("input", i, istack)
			end
		end
	end
	update_anvil_slots(meta)
end

local function anvil_on_metadata_inventory_take(pos, listname, index, stack, player)
	local meta = core.get_meta(pos)
	if listname == "output" then
		local inv = meta:get_inventory()
		local input1 = inv:get_stack("input", 1)
		local input2 = inv:get_stack("input", 2)
		-- Both slots occupied?
		if not input1:is_empty() and not input2:is_empty() then
			-- Take as many items as needed
			local distinguished, tool, material = distinguish_tool_and_material(input1, input2)
			if distinguished then
				-- Tool + material: Take tool and as many materials as needed
				local materials_used = get_consumed_materials(tool, material)
				material:set_count(material:get_count() - materials_used)
				tool:take_item()
				if distinguished == "tool" then
					input1, input2 = tool, material
				else
					input1, input2 = material, tool
				end
				inv:set_stack("input", 1, input1)
				inv:set_stack("input", 2, input2)
			else
				-- Else take 1 item from each stack
				input1:take_item()
				input2:take_item()
				inv:set_stack("input", 1, input1)
				inv:set_stack("input", 2, input2)
			end
		else
			-- Otherwise: Rename mode. Remove the same amount of items from input
			-- as has been taken from output
			if not input1:is_empty() then
				input1:set_count(math.max(0, input1:get_count() - stack:get_count()))
				inv:set_stack("input", 1, input1)
			end
			if not input2:is_empty() then
				input2:set_count(math.max(0, input2:get_count() - stack:get_count()))
				inv:set_stack("input", 2, input2)
			end
		end
	elseif listname == "input" then
		update_anvil_slots(meta)
	end
end

local function anvil_on_metadata_inventory_put(pos, _, _, _, player)
	local meta = core.get_meta(pos)
	update_anvil_slots(meta, player)
end

core.override_item("mcl_anvils:anvil", {
	on_metadata_inventory_move = anvil_on_metadata_inventory_move,
	on_metadata_inventory_take = anvil_on_metadata_inventory_take,
	on_metadata_inventory_put = anvil_on_metadata_inventory_put,
})

core.override_item("mcl_anvils:anvil_damage_1", {
	on_metadata_inventory_move = anvil_on_metadata_inventory_move,
	on_metadata_inventory_take = anvil_on_metadata_inventory_take,
	on_metadata_inventory_put = anvil_on_metadata_inventory_put,
})

core.override_item("mcl_anvils:anvil_damage_2", {
	on_metadata_inventory_move = anvil_on_metadata_inventory_move,
	on_metadata_inventory_take = anvil_on_metadata_inventory_take,
	on_metadata_inventory_put = anvil_on_metadata_inventory_put,
})
