-- Configuration

local chainsaw_max_charge = 30000 -- Maximum charge of the saw
local chainsaw_charge_per_node = 12 -- Gives 2500 nodes on a single charge (about 50 complete normal trees)

local chainsaw_leaves = true -- Cut down tree leaves.
-- Leaf decay may cause slowness on large trees if this is disabled.

local chainsaw_vines = true -- Cut down vines

local timber_nodenames = {} -- Cuttable nodes

local max_saw_radius = 12 -- max x/z distance away from starting position to allow cutting
-- Prevents forest destruction, increase for extra wide trees

-- Support for nodes not in any supported node groups (tree, leaves, leafdecay, leafdecay_drop)
timber_nodenames["default:papyrus"] = true
timber_nodenames["default:cactus"] = true
timber_nodenames["default:bush_stem"] = true
timber_nodenames["default:acacia_bush_stem"] = true
timber_nodenames["default:pine_bush_stem"] = true

if core.get_modpath("growing_trees") then
	timber_nodenames["growing_trees:branch_sprout"] = true
	if chainsaw_leaves then
		timber_nodenames["growing_trees:leaves"] = true
	end
end

if core.get_modpath("snow") then
	if chainsaw_leaves then
		timber_nodenames["snow:needles"] = true
		timber_nodenames["snow:needles_decorated"] = true
		timber_nodenames["snow:star"] = true
	end
end

if core.get_modpath("trunks") then
	if chainsaw_leaves then
		timber_nodenames["trunks:moss"] = true
		timber_nodenames["trunks:moss_fungus"] = true
		timber_nodenames["trunks:treeroot"] = true
	end
end

if core.get_modpath("ethereal") then
	timber_nodenames["ethereal:bamboo"] = true
end

local S = technic.getter

-- Table for saving what was sawed down
local produced = {}

-- Save the items sawed down so that we can drop them in a nice single stack
local function handle_drops(drops)
	for _, item in ipairs(drops) do
		local stack = ItemStack(item)
		local name = stack:get_name()
		local p = produced[name]
		if not p then
			produced[name] = stack
		else
			p:set_count(p:get_count() + stack:get_count())
		end
	end
end

-- This function does all the hard work. Recursively we dig the node at hand
-- if it is in the table and then search the surroundings for more stuff to dig.
local function recursive_dig(pos, origin, remaining_charge)
	if remaining_charge < chainsaw_charge_per_node then
		return remaining_charge
	end
	local node = core.get_node(pos)

	if not timber_nodenames[node.name] then
		return remaining_charge
	end

	-- Wood found - cut it
	handle_drops(core.get_node_drops(node.name, ""))
	core.remove_node(pos)
	remaining_charge = remaining_charge - chainsaw_charge_per_node

	-- Check for snow on pine trees, sand/gravel on leaves, etc
	core.check_for_falling(pos)

	-- Check surroundings and run recursively if any charge left
	for y=-1, 1 do
		if (pos.y + y) >= origin.y then
			for x=-1, 1 do
				if (pos.x + x) <= (origin.x + max_saw_radius) and (pos.x + x) >= (origin.x - max_saw_radius) then
					for z=-1, 1 do
						if (pos.z + z) <= (origin.z + max_saw_radius) and (pos.z + z) >= (origin.z - max_saw_radius) then
							local npos = vector.offset(pos, x, y, z)
							if remaining_charge < chainsaw_charge_per_node then
								return remaining_charge
							end
							if timber_nodenames[core.get_node(npos).name] then
								remaining_charge = recursive_dig(npos, origin, remaining_charge)
							end
						end
					end
				end
			end
		end
	end
	return remaining_charge
end

-- Function to randomize positions for new node drops
local function get_drop_pos(pos)
	local drop_pos = {}

	for i = 0, 8 do
		-- Randomize position for a new drop
		drop_pos.x = pos.x + math.random(-3, 3)
		drop_pos.y = pos.y - 1
		drop_pos.z = pos.z + math.random(-3, 3)

		-- Move the randomized position upwards until
		-- the node is air or unloaded.
		for y = drop_pos.y, drop_pos.y + 5 do
			drop_pos.y = y
			local node = core.get_node_or_nil(drop_pos)

			if not node then
				-- If the node is not loaded yet simply drop
				-- the item at the original digging position.
				return pos
			elseif node.name == "air" then
				-- Add variation to the entity drop position,
				-- but don't let drops get too close to the edge
				drop_pos.x = drop_pos.x + (math.random() * 0.8) - 0.5
				drop_pos.z = drop_pos.z + (math.random() * 0.8) - 0.5
				return drop_pos
			end
		end
	end

	-- Return the original position if this takes too long
	return pos
end

-- Chainsaw entry point
local function chainsaw_dig(pos, current_charge)
	-- Start sawing things down
	local remaining_charge = recursive_dig(pos, pos, current_charge)
	core.sound_play("chainsaw", {pos = pos, gain = 1.0, max_hear_distance = 10}, true)

	-- Now drop items for the player
	for name, stack in pairs(produced) do
		-- Drop stacks of stack max or less
		local count, max = stack:get_count(), stack:get_stack_max()
		stack:set_count(max)
		while count > max do
			core.add_item(get_drop_pos(pos), stack)
			count = count - max
		end
		stack:set_count(count)
		core.add_item(get_drop_pos(pos), stack)
	end

	-- Clean up
	produced = {}

	return remaining_charge
end

technic.register_power_tool("technic:chainsaw", {
	description = S("Chainsaw"),
	inventory_image = "technic_chainsaw.png",
	max_charge = chainsaw_max_charge,
	on_use = function(itemstack, user, pointed_thing)
		if pointed_thing.type ~= "node" then
			return
		end

		local charge = technic.get_charge(itemstack)
		if charge < chainsaw_charge_per_node then
			return
		end

		local name = user:get_player_name()
		if core.is_protected(pointed_thing.under, name) then
			core.record_protection_violation(pointed_thing.under, name)
			return
		end

		-- Send current charge to digging function so that the
		-- chainsaw will stop after digging a number of nodes
		charge = chainsaw_dig(pointed_thing.under, charge)
		if not technic.creative_mode then
			technic.set_charge(itemstack, charge)
		end
		return itemstack
	end,
})

local mesecons_button = core.get_modpath("mesecons_button")
local has_mcl = core.get_modpath("mcl_core")
local trigger = has_mcl and mesecons_button and "mesecons_button:button_wood_off"
	or mesecons_button and "mesecons_button:button_off" or "default:mese_crystal_fragment"

core.register_craft({
	output = "technic:chainsaw",
	recipe = {
		{"technic:stainless_steel_ingot", trigger,                      "technic:battery"},
		{"basic_materials:copper_wire",      "basic_materials:motor",              "technic:battery"},
		{"",                              "",                           "technic:stainless_steel_ingot"},
	},
	replacements = { {"basic_materials:copper_wire", "basic_materials:empty_spool"}, },

})

-- Add cuttable nodes after all mods loaded
core.after(0, function ()
	for k, v in pairs(core.registered_nodes) do
		if v.groups.tree then
			timber_nodenames[k] = true
		elseif chainsaw_leaves and (v.groups.leaves or v.groups.leafdecay or v.groups.leafdecay_drop) then
			timber_nodenames[k] = true
		elseif chainsaw_vines and v.groups.vines then
			timber_nodenames[k] = true
		end
	end
end)
