---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by michieal.
--- DateTime: 12/29/22 12:34 PM -- Restructure Date
--- Copyright (C) 2022 - 2023, Michieal. See License.txt

local DEBUG = false

local rand = math.random
math.randomseed((os.time() + 31) * 31415) -- try to make a valid seed
local BAMBOO_MAX_HEIGHT = 16 -- base height check.

local BAMBOO_SOIL_DIST = BAMBOO_MAX_HEIGHT * -1
local BAM_MAX_HEIGHT_STPCHK = BAMBOO_MAX_HEIGHT - 5
local BAM_MAX_HEIGHT_TOP = BAMBOO_MAX_HEIGHT - 1
local GROW_DOUBLE_CHANCE = 32

--Bamboo can be planted on moss blocks, grass blocks, dirt, coarse dirt, rooted dirt, gravel, mycelium, podzol, sand, red sand, or mud
mcl_bamboo.bamboo_dirt_nodes = {
	"mcl_core:redsand",
	"mcl_core:sand",
	"mcl_core:dirt",
	"mcl_core:coarse_dirt",
	"mcl_core:dirt_with_grass",
	"mcl_core:podzol",
	"mcl_core:mycelium",
	"mcl_lush_caves:rooted_dirt",
	"mcl_lush_caves:moss",
	"mcl_mud:mud",
}

function mcl_bamboo.is_dirt(node_name)
	local index = table.indexof(mcl_bamboo.bamboo_dirt_nodes, node_name)
	if index == -1 then
		return false
	else
		return true
	end
end

mcl_bamboo.bamboo_index = {
	"mcl_bamboo:bamboo",
	"mcl_bamboo:bamboo_1",
	"mcl_bamboo:bamboo_2",
	"mcl_bamboo:bamboo_3",
}
mcl_bamboo.bamboo_set = {}
for _,key in pairs(mcl_bamboo.bamboo_index) do
	mcl_bamboo.bamboo_set[key] = true
end

function mcl_bamboo.is_bamboo(node_name)
	local index = table.indexof(mcl_bamboo.bamboo_index, node_name)
	if index == -1 then
		return false
	else
		return index
	end
end

--- pos: node position; placer: ObjectRef that is placing the item
--- returns: true if protected, otherwise false.
function mcl_bamboo.is_protected(pos, placer)
	local name = placer:get_player_name()
	if minetest.is_protected(pos, name) then
		minetest.record_protection_violation(pos, name)
		return true
	end
	return false
end

local BAMBOO_ENDCAP_NAME = "mcl_bamboo:bamboo_endcap"

-- check if supporting block is broken. pistons now break the bamboo plant.
function mcl_bamboo.break_orphaned(pos)
	mcl_bamboo.mcl_log("Break_Orphaned called.")
	local node_below = minetest.get_node(vector.offset(pos, 0, -1, 0))
	local node_name = node_below.name

	-- short circuit checks.
	if node_name == "ignore" or mcl_bamboo.is_dirt(node_name) or mcl_bamboo.is_bamboo(node_name) or mcl_bamboo.is_bamboo(minetest.get_node(pos).name) == false then
		return
	end

	-- dig the node.
	minetest.remove_node(pos)    -- if that fails, remove the node
	local istack = ItemStack("mcl_bamboo:bamboo")
	local sound_params = {
		pos = pos,
		gain = 1.0, -- default
		max_hear_distance = 10, -- default, uses a Euclidean metric
	}

	minetest.remove_node(pos)
	minetest.sound_play(mcl_sounds.node_sound_wood_defaults().dug, sound_params, true)
	minetest.add_item(pos, istack)
end
--]]

function mcl_bamboo.grow_bamboo(pos, bonemeal_applied)
	local log = mcl_bamboo.mcl_log
	local node_above = minetest.get_node(vector.offset(pos, 0, 1, 0))
	log("Grow bamboo called; bonemeal: " .. tostring(bonemeal_applied))

	if not bonemeal_applied then
		-- Only allow natural growth at the top of the bamboo
		if mcl_bamboo.is_bamboo(node_above.name) ~= false then return false end

		-- Don't perform natual growth in low light
		if minetest.get_node_light(pos) < 8 then return false end
	end

	-- Determine the location of soil
	local soil_pos = mcl_util.trace_nodes(pos, -1, mcl_bamboo.bamboo_set, BAMBOO_MAX_HEIGHT - 1)

	-- No soil found, return false so that bonemeal isn't used
	if not soil_pos then return false end
	log("Grow bamboo; soil found. ")

	-- Find the first bamboo shoot and retrieve data about it
	local first_shoot = vector.offset(soil_pos, 0, 1, 0)
	local first_shoot_meta = minetest.get_meta(first_shoot)

	-- Get or initialize bamboo height
	local height = (first_shoot_meta and first_shoot_meta:get_int("height", -1)) or -1
	if height == -1 then
		height = rand(BAM_MAX_HEIGHT_STPCHK + 1, BAM_MAX_HEIGHT_TOP + 1)
		first_shoot_meta:set_int("height", height)
	end
	log("Grow bamboo; height: " .. height)

	-- Locate the bamboo tip
	local bamboo_tip,actual_height,bamboo_tip_node = mcl_util.trace_nodes(first_shoot, 1, mcl_bamboo.bamboo_set, height - 1)
	log("Current height: "..tostring(actual_height))

	-- Short circuit growth if the bamboo is already finished growing
	if not bamboo_tip or not actual_height or actual_height >= height then
		log("Bamboo is already as large as it can grow")
		return false
	end

	-- Now that we are actually going to add nodes, initialize some more information
	local first_shoot_node_name = minetest.get_node(first_shoot).name

	-- If applying bonemeal, randomly grow two segments instead of one
	local grow_amount = 1
	if bonemeal_applied then
		local rng = PcgRandom(minetest.hash_node_position(pos) + minetest.get_us_time())
		if rng:next(1, GROW_DOUBLE_CHANCE) == 1 then
			grow_amount = 2
		end
	end
	log("Growing up to "..grow_amount.." segments")

	-- Perform bamboo growth
	for i = 1,grow_amount do
		-- Check for air to grow into
		local bamboo_tip_node = minetest.get_node(bamboo_tip)
		if not bamboo_tip_node or bamboo_tip_node.name ~= "air" then
			-- Something is blocking growth, stop and signal that use bonemeal has been used if at least on segment has grown
			return i ~= 1
		end

		if actual_height + 1 == height then
			-- This is the end cap
			minetest.set_node(bamboo_tip, { name = BAMBOO_ENDCAP_NAME })
			return true
		else
			-- This isn't the end cap, add a bamboo segment
			minetest.set_node(bamboo_tip, { name = first_shoot_node_name })
			actual_height = actual_height + 1
		end

		bamboo_tip = vector.offset(bamboo_tip, 0, 1, 0)
	end

	return true
end

-- Add Groups function, courtesy of Warr1024.
function mcl_bamboo.add_groups(name, ...)
	local def = minetest.registered_items[name] or error(name .. " not found")
	local groups = {}
	for k, v in pairs(def.groups) do
		groups[k] = v
	end
	local function add_all(x, ...)
		if not x then
			return
		end
		groups[x] = 1
		return add_all(...)
	end
	addall(...)
	return minetest.override_item(name, { groups = groups })
end

function mcl_bamboo.mcl_log(m, l)
	if not m then
		minetest.log("error", "expected string, received: " .. m)
		return
	end
	if DEBUG then
		if not l then
			minetest.log("[mcl_bamboo]: " .. m)
		else
			minetest.log(l, "[mcl_bamboo]: " .. m)
		end
	end
end
