local pr_a -- PseudoRandom var

-- Default minimum light level for sapling to grow
local MIN_LIGHT = 8

-- Check if a sapling position has the correct
-- underground to allow sapling growth.
-- * pos: Sapling position (the underground node is checked below)
-- * undergrounds: (optional) List of nodenames that count as
--   permissible undergrounds. Default: hades_trees.DEFAULT_UNDERGROUND
function hades_trees.check_underground(pos, undergrounds)
	local cpos = vector.offset(pos, 0, -1, 0)
	local nodename = minetest.get_node(cpos).name
	if not undergrounds then
		undergrounds = hades_trees.DEFAULT_UNDERGROUND
	end
	for _,name in ipairs(undergrounds) do
		if nodename == name then
			return true
		end
	end
	return false
end

function hades_trees.grow_sapling(pos, check_light)
	local node = minetest.get_node(pos)
	local trunk, leaves, fruit, replacement

	local ndef = minetest.registered_nodes[node.name]

	-- Check underground
	local undergrounds = table.copy(hades_trees.DEFAULT_UNDERGROUND)
	if ndef._hades_growtype == "ash" then
		table.insert(undergrounds, "hades_core:ash")
		table.insert(undergrounds, "hades_core:volcanic_sand")
		table.insert(undergrounds, "hades_core:fertile_sand")
	elseif ndef._hades_growtype == "fertile_sand" then
		table.insert(undergrounds, "hades_core:fertile_sand")
	elseif ndef._hades_growtype == "none" then
		-- Sapling doesn't grow at all
		return
	end
	local u_ok = hades_trees.check_underground(pos, undergrounds)
	if not u_ok then
		-- Wrong underground: Refuse to grow
		return
	end

	-- Grow tree charred if sapling was close to lava
	local lava = minetest.find_node_near(pos, 1, "group:lava")
	if lava then
		trunk = "hades_trees:charred_tree"
		leaves = "hades_trees:burned_branches"
		-- Also remove all fruit
		fruit = "air"
		replacement = {}
	else
		if ndef and ndef._hades_fruit and ndef._hades_fruit_chance then
			replacement = { name = ndef._hades_fruit, chance = ndef._hades_fruit_chance }
		end
	end

	local ndef = minetest.registered_nodes[node.name]
	if ndef and ndef._hades_grow_sapling then
		ndef._hades_grow_sapling(pos, check_light, trunk, leaves, replacement, fruit)
	end
end

local function check_node_light(pos, minlight, check)
	if check == false then
		return true
	end
	local l = minetest.get_node_light(pos)
	if not l or l < minlight then
		return false
	end
	return true
end

--[[
Generates a tree based on a shematic.

pos: Position of tree base
schematic: Path to schematic file
check_light: whether to check light
]]
function hades_trees.generate_tree_schematic(pos, schematic, check_light, original_trunk, original_leaves, original_fruit, override_trunk, override_leaves, override_fruit)
	local orig_pos = table.copy(pos)
	local floor = vector.offset(pos, 0, -1, 0)
	local nodename = minetest.get_node(floor).name
	if not check_node_light(pos, MIN_LIGHT, check_light) then
		return false
	end

	local schem = minetest.read_schematic(schematic, {})
	if not schem then
		minetest.log("error", "[hades_trees] Could not read tree schematic '"..tostring(schematic).."'!")
		return false
	end
	local height = schem.size.y

	-- Check if there is enough space for the schematic
	local cpos = table.copy(pos)
	for dy=1, height-1 do
		cpos.y = pos.y+dy
		if minetest.get_node(cpos).name ~= "air" then
			return false
		end
	end

	-- Change leaves param2 values in schematic to correct seasonal color
	if original_leaves and not override_leaves then
		local ndef = minetest.registered_nodes[original_leaves]
		if ndef and ndef.paramtype2 == "color" then
			local leaves_param2 = hades_seasons.get_seasonal_palette_color_param2()
			for s=1, #schem.data do
				if schem.data[s].name == original_leaves then
	--				schem.data[s].param2 = leaves_param2
				end
			end
		end
	end

	local replacements = {}
	if original_leaves and override_leaves then
		replacements[original_leaves] = override_leaves
	end
	if original_trunk and override_trunk then
		replacements[original_trunk] = override_trunk
	end
	if original_fruit and override_fruit then
		replacements[original_fruit] = override_fruit
	end

	minetest.remove_node(pos)
	local ok = minetest.place_schematic(pos, schem, "random", replacements, false, { place_center_x = true, place_center_z = true })
	return ok ~= nil
end

--[[
Generates a basic tree that has a round-ish shape.

pos: Position of tree base
check_light: whether to check light
trunk: tree trunk node name (optional)
leaves: leaves node name (optional)
replacement: {
	name = --node name of node to replace leaves,
	chance = -- 1 divided by this is the chance to replace leaves,
}
]]
function hades_trees.generate_spheric_tree(pos, check_light, trunk, leaves, replacement)
	if not trunk then
		trunk = "hades_trees:tree"
	end
	if not leaves then
		leaves = "hades_trees:leaves"
	end
	local orig_pos = table.copy(pos)
	if not check_node_light(pos, MIN_LIGHT, check_light) then
		return false
	end

	local node = {name = ""}
	-- Check if enough space free
	local cpos = table.copy(pos)
	for dy=1,5 do
		cpos.y = pos.y+dy
		if minetest.get_node(cpos).name ~= "air" then
			return false
		end
	end
	node.name = trunk
	for dy=0,3 do
		minetest.set_node(vector.offset(pos, 0, dy, 0), node)
	end


	local ndef = minetest.registered_nodes[node.name]

	node.name = leaves
	if ndef and ndef.paramtype2 == "color" then
		node.param2 = hades_seasons.get_seasonal_palette_color_param2()
	end
	pos.y = pos.y+4
	for dx=-2,2 do
		for dz=-2,2 do
			for dy=-1,2 do
				pos.x = pos.x+dx
				pos.y = pos.y+dy
				pos.z = pos.z+dz


				if dx == 0 and dz == 0 and dy==3 then
					if minetest.get_node(pos).name == "air" and math.random(1, 6) <= 4 then
						minetest.set_node(pos, node)
						if replacement then
							if math.random(1, replacement.chance) == 1 then
								minetest.set_node(pos, {name=replacement.name, param2=0})
							end
						end
					end
				elseif dx == 0 and dz == 0 and dy==4 then
					if minetest.get_node(pos).name == "air" and math.random(1, 6) <= 4 then
						minetest.set_node(pos, node)
						if replacement then
							if math.random(1, replacement.chance) == 1 then
								minetest.set_node(pos, {name=replacement.name, param2=0})
							end
						end
					end
				elseif math.abs(dx) ~= 2 and math.abs(dz) ~= 2 then
					if minetest.get_node(pos).name == "air" then
						minetest.set_node(pos, node)
						if replacement then
							if math.random(1, replacement.chance) == 1 then
								minetest.set_node(pos, {name=replacement.name, param2=0})
							end
						end
					end
				else
					if math.abs(dx) ~= 2 or math.abs(dz) ~= 2 then
						if minetest.get_node(pos).name == "air" and math.random(1, 6) <= 4 then
							minetest.set_node(pos, node)
							if replacement then
								if math.random(1, replacement.chance) == 1 then
									minetest.set_node(pos, {name=replacement.name, param2=0})
								end
							end
						end
					end
				end


				pos.x = pos.x-dx
				pos.y = pos.y-dy
				pos.z = pos.z-dz
			end
		end
	end
	minetest.log("action", "[hades_trees] A sapling grows into a tree at "..minetest.pos_to_string(orig_pos))
	return true
end

-- Deprecated function, synonymous with hades_trees.generate_spheric_tree.
-- See hades_trees.generate_spheric_tree.
-- TODO: Remove this function eventually.
hades_trees.generate_tree = function(...)
	minetest.log("warning", "[hades_trees] Calling hades_trees.generate_tree is deprecated! Use hades_trees.generate_spheric_tree instead.")
	hades_trees.generate_spheric_tree(...)
end

--[[
Generates a tree that is shaped like an apple tree.

Parameters:
* pos: Position of tree base
* check_light: whether to check light
* trunk: tree trunk node name (optional)
* leaves: leaves node name (optional)
* replacement: {
	name = --node name of node to replace leaves,
	chance = -- 1 divided by this is the chance to replace leaves,
}
* config: table to set details about the tree
	* height_min: minimum trunk height (optional, default: 4)
	* height_max: minimum trunk height (optional, default: 6)
	* random_leaves: number of randomly grown leaves (optional, default: 8)
	* leaves_blob_min: vector with an offset for the minimum position of leaves blobs (optional, default: vector.new(-2,-2,-2))
	* leaves_blob_max: vector with an offset for the maximum position of leaves blobs (optional, default: vector.new(2,2,2))
]]
function hades_trees.generate_applelike_tree(pos, check_light, trunk, leaves, replacement, config)
        if not trunk then
                trunk = "hades_trees:tree"
        end
        if not leaves then
                leaves = "hades_trees:leaves"
        end

	local c_tree = minetest.get_content_id(trunk)
	local c_leaves = minetest.get_content_id(leaves)

        if not config then
                config = {}
        end
        config = table.copy(config)
        if not config.random_leaves then
                config.random_leaves = 8
        end
        if not config.leaves_blob_min then
                config.leaves_blob_min = vector.new(-2,-2,-2)
        end
        if not config.leaves_blob_max then
                config.leaves_blob_max = vector.new(2,2,2)
        end
        -- minimum tree height (based on trunk)
        if not config.height_min then
                config.height_min = 4
        end
        -- maximum tree height (based on trunk)
        if not config.height_max then
                config.height_max = 6
        end

	local c_replacement
	if replacement then
		c_replacement = minetest.get_content_id(replacement.name)
	end

	local param2
	local ndef = minetest.registered_nodes[leaves]
	if ndef and ndef.paramtype2 == "color" then
		param2 = hades_seasons.get_seasonal_palette_color_param2()
	else
		param2 = 0
	end

	local vm = minetest.get_voxel_manip()
	local minp, maxp = vm:read_from_map({x=pos.x-16, y=pos.y-1, z=pos.z-16}, {x=pos.x+16, y=pos.y+16, z=pos.z+16})
	local a = VoxelArea:new{MinEdge=minp, MaxEdge=maxp}
	local data = vm:get_data()
	local data_p2 = vm:get_param2_data()

	if not pr_a then
		local seed = math.random(1,100000)
		pr_a = PseudoRandom(seed)
	end
        local x, y, z = pos.x, pos.y, pos.z

	local lpos = {x=pos.x, y=pos.y+1, z=pos.z}
	if not check_node_light(lpos, 8, check_light) then
		return false
	end

	local c_air = minetest.CONTENT_AIR
	local c_ignore = minetest.CONTENT_IGNORE

	local tmin = config.height_min
	local tmax = config.height_max
        local th = pr_a:next(tmin, tmax)
	local main_trunk = c_tree
        for h = 1, th do
		local yy = y + (h - 1)
                local vi = a:index(x, yy, z)
                if a:contains(x, yy, z) and (data[vi] == c_air or yy == y) then
                        data[vi] = main_trunk
			data_p2[vi] = param2
                else
			if h < tmin then
				-- Fail tree generation if not enough space for trunk
				return false
			else
				main_trunk = c_leaves
			end
		end
        end
        y = y+th-1 -- (x, y, z) is now last piece of trunk
        local leaves_a = VoxelArea:new({MinEdge=config.leaves_blob_min, MaxEdge=config.leaves_blob_max})
        local leaves_buffer = {}

        -- Force leaves near the trunk
        local d = 1
        for xi = -d, d do
        for yi = -d, d do
        for zi = -d, d do
                leaves_buffer[leaves_a:index(xi, yi, zi)] = true
        end
        end
        end

        -- Add leaves randomly
        for iii = 1, config.random_leaves do
                local d = 1
                local xx = pr_a:next(leaves_a.MinEdge.x, leaves_a.MaxEdge.x - d)
                local yy = pr_a:next(leaves_a.MinEdge.y, leaves_a.MaxEdge.y - d)
                local zz = pr_a:next(leaves_a.MinEdge.z, leaves_a.MaxEdge.z - d)

                for xi = 0, d do
                for yi = 0, d do
                for zi = 0, d do
                        leaves_buffer[leaves_a:index(xx+xi, yy+yi, zz+zi)] = true
                end
                end
                end
        end

        -- Add the leaves
        for xi = leaves_a.MinEdge.x, leaves_a.MaxEdge.x do
        for yi = leaves_a.MinEdge.y, leaves_a.MaxEdge.y do
        for zi = leaves_a.MinEdge.z, leaves_a.MaxEdge.z do
                if a:contains(x+xi, y+yi, z+zi) then
                        local vi = a:index(x+xi, y+yi, z+zi)
                        if data[vi] == c_air or data[vi] == c_ignore then
                                if leaves_buffer[leaves_a:index(xi, yi, zi)] then
					if replacement and math.random(1, replacement.chance) == 1 then
						data[vi] = c_replacement
					else
						data[vi] = c_leaves
						data_p2[vi] = param2
					end
                                end
                        end
                end
        end
        end
        end

	minetest.log("action", "[hades_trees] A sapling grows into a tree at "..minetest.pos_to_string(pos))
	vm:set_data(data)
	vm:set_param2_data(data_p2)
	vm:write_to_map()
	vm:update_map()
	return true
end

--[[
Generate a tree with a cuboid shape.

pos: Position of tree base
check_light: whether to check light
trunk: tree trunk node name (optional)
leaves: leaves node name (optional)
replacement: {
	name = --node name of node to replace leaves,
	chance = -- 1 divided by this is the chance to replace leaves,
}
_unused: unused argument (just set it to nil)
config:
* min_light: min light level to grow
* trunk_height: height of trunk nodes
* leaves_start_height: height above base at which leaves appear
* leaves_height: total height of leaves
* leaves_outwards: how much leaves spread on the X/Z plane
* leaves_chance_denominator: chance of a leaves node to generate is this devided by the number below
* leaves_chance_numerator: chance of a leaves node to generate is the number above divided by this
]]

function hades_trees.generate_cuboid_tree(pos, check_light, trunk, leaves, replacement, _unused, config)
	if not trunk then
		trunk = "hades_trees:tree"
	end
	if not leaves then
		leaves = "hades_trees:leaves"
	end
	if not config then
		config = {}
	end
	if not config.min_light then
		config.min_light = MIN_LIGHT
	end

	-- Check light
	if not check_node_light(pos, config.min_light, check_light) then
		return false
	end

	-- Check if enough space to grow
	local cpos = table.copy(pos)
	local node = {name = ""}
	for dy=1,config.trunk_height  do
		cpos.y = pos.y+dy
		if minetest.get_node(cpos).name ~= "air" then
			return false
		end
	end
	local trunknodes = {}
	for dy=0,config.trunk_height-1 do
		local tpos = vector.offset(pos, 0, dy, 0)
		local tnode = minetest.get_node(tpos)
		table.insert(trunknodes, tpos)
	end

	local leafnodes = {}
	local replacementnodes = {}
	for dx=-config.leaves_outwards, config.leaves_outwards do
		for dz=-config.leaves_outwards, config.leaves_outwards do
			for dy=config.leaves_start_height, config.leaves_start_height + config.leaves_height - 1 do
				pos.x = pos.x+dx
				pos.y = pos.y+dy
				pos.z = pos.z+dz
				if dx ~= 0 or dz ~= 0 or dy > config.trunk_height then
					local tnode = minetest.get_node(pos)
					if tnode.name == "air" and math.random(1, config.leaves_chance_denominator) <= config.leaves_chance_numerator then
						local replaced = false
						if replacement and replacement.name then
							if math.random(1, replacement.chance) == 1 then
								table.insert(replacementnodes, table.copy(pos))
								replaced = true
							end
						end
						if not replaced then
							table.insert(leafnodes, table.copy(pos))
						end
					end
				end
				pos.x = pos.x-dx
				pos.y = pos.y-dy
				pos.z = pos.z-dz
			end
		end
	end

	node.name = trunk
	minetest.bulk_set_node(trunknodes, node)
	node.name = leaves
	local ndef = minetest.registered_nodes[node.name]
	if ndef and ndef.paramtype2 == "color" then
		node.param2 = hades_seasons.get_seasonal_palette_color_param2()
	end
	minetest.bulk_set_node(leafnodes, node)
	if #replacementnodes > 0 then
		node.name = replacement.name
		node.param2 = 0
		minetest.bulk_set_node(replacementnodes, node)
	end
	return true
end

-- Generate a tree by trying out multiple variants and pick the first
-- one that succeeds.
-- Useful to try to generate a tree that supports multiple variants
-- like a large one, and if that fails, try a smaller one.
--
-- * attempts is a table of tables, each inner table is of form:
--   { func, param_list },
-- where func is the function to call and param_list is the list of
-- parameters to pass to that function.
-- This function will start with the first function in the list and call
-- it.
-- If func returns true, this function returns.
-- If func returns anything else, the next function is called in the list,
-- and this is repeated until either one function returned true or
-- the list is completed.

-- Returns: true if any inner function succeeded, otherwise returns false
hades_trees.generate_tree_variant = function(attempts)
	for a=1, #attempts do
		local attempt = attempts[a]
		local func = attempt[1]
		local params = attempt[2]
		local success = func(unpack(params))
		if success == true then
			return true
		end
	end
	return false
end


