--[[

	TechAge
	=======

	Copyright (C) 2019-2020 Joachim Stolberg

	AGPL v3
	See LICENSE.txt for more information

	Assemble routines

]]--

-- for lazy programmers
local P = minetest.string_to_pos
local M = minetest.get_meta
local S = techage.S

techage.assemble = {}

local Face2Dir = {[0]=
	{x=0,  y=0,  z=1},
	{x=1,  y=0,  z=0},
	{x=0,  y=0, z=-1},
	{x=-1, y=0,  z=0},
	{x=0,  y=-1, z=0},
	{x=0,  y=1,  z=0}
}

-- Determine the destination position based on the base position,
-- param2, and a route table like : {0,3}
-- 0 = forward, 1 = right, 2 = backward, 3 = left
local function dest_pos(pos, param2, route, y_offs)
	local p2 = param2
	local pos1 = {x=pos.x, y=pos.y+y_offs, z=pos.z}
	for _,dir in ipairs(route) do
		p2 = (param2 + dir) % 4
		pos1 = vector.add(pos1, Face2Dir[p2])
	end
	return pos1, p2
end


-- timer based function
local function build(pos, param2, AssemblyPlan, idx)
	local item = AssemblyPlan[idx]
	if item ~= nil then
		local y, path, fd_offs, node_name = item[1], item[2], item[3], item[4]
		local pos1 = dest_pos(pos, param2, path, y)
		minetest.add_node(pos1, {name=node_name, param2=(param2 + fd_offs) % 4})
		minetest.after(0.5, build, pos, param2, AssemblyPlan, idx+1)
	else
		local nvm = techage.get_nvm(pos)
		nvm.assemble_locked = false
	end
end

-- timer based function
local function remove(pos, param2, AssemblyPlan, idx)
	local item = AssemblyPlan[idx]
	if item ~= nil then
		local y, path = item[1], item[2]
		local pos1 = dest_pos(pos, param2, path, y)
		minetest.remove_node(pos1)
		minetest.after(0.5, remove, pos, param2, AssemblyPlan, idx-1)
	else
		local nvm = techage.get_nvm(pos)
		nvm.assemble_locked = false
	end
end

local function check_space(pos, param2, AssemblyPlan, player_name)
	for _,item in ipairs(AssemblyPlan) do
		local y, path, node_name = item[1], item[2], item[4]
		local pos1 = dest_pos(pos, param2, path, y)
		if minetest.is_protected(pos1, player_name) then
			minetest.chat_send_player(player_name, S("[TA] Area is protected!"))
			return false
		end

		local node = techage.get_node_lvm(pos1)
		local ndef = minetest.registered_nodes[node.name]
		if not ndef or not ndef.buildable_to and node.name ~= node_name then
			minetest.chat_send_player(player_name, S("[TA] Not enough space!"))
			return false
		end
	end
	return true
end


-- Two important flags:
-- 1) nvm.assemble_locked is true while the object is being assembled/disassembled
-- 2) nvm.assemble_build is true if the object is assembled
function techage.assemble.build(pos, AssemblyPlan, player_name)
	-- check protection
	if minetest.is_protected(pos, player_name) then
		return
	end
	local nvm = techage.get_nvm(pos)
	if nvm.assemble_locked then
		return
	end
	local node = minetest.get_node(pos)
	if check_space(pos, node.param2, AssemblyPlan, player_name) then
		nvm.assemble_locked = true
		build(pos, node.param2, AssemblyPlan, 1)
		nvm.assemble_build = true
	end
end

function techage.assemble.remove(pos, AssemblyPlan, player_name)
	-- check protection
	if minetest.is_protected(pos, player_name) then
		return
	end
	local nvm = techage.get_nvm(pos)
	if nvm.assemble_locked then
		return
	end
	local node = minetest.get_node(pos)
	nvm.assemble_locked = true
	remove(pos, node.param2, AssemblyPlan, #AssemblyPlan)
	nvm.assemble_build = false
end

--------------------------------------------------------------------------------
-- Assembly functions based on nodes from node inventory
--------------------------------------------------------------------------------
local function play_sound(pos, sound)
	minetest.sound_play(sound, {
		pos = pos,
		gain = 1,
		max_hear_distance = 10,
	})
end

local function build_inv(pos, inv, param2, AssemblyPlan, player_name, idx)
	local item = AssemblyPlan[idx]
	if item ~= nil then
		local y, path, fd_offs, node_name = item[1], item[2], item[3], item[4]
		local pos1 = dest_pos(pos, param2, path, y)
		if not minetest.is_protected(pos1, player_name) then
			local node = minetest.get_node(pos1)
			if techage.is_air_like(node.name) then
				local stack = inv:remove_item("src", ItemStack(node_name))
				if stack:get_count() == 1 then
					minetest.add_node(pos1, {name=node_name, param2=(param2 + fd_offs) % 4})
					play_sound(pos, "default_place_node_hard")
					local ndef = minetest.registered_nodes[node_name]
					if ndef and ndef.after_place_node then
						local placer = minetest.get_player_by_name(player_name)
						ndef.after_place_node(pos1, placer, ItemStack(node_name))
					end
				end
			end
		end
		minetest.after(0.5, build_inv, pos, inv, param2, AssemblyPlan, player_name, idx + 1)
	else
		local nvm = techage.get_nvm(pos)
		nvm.assemble_locked = false
	end
end

local function remove_inv(pos, inv, param2, AssemblyPlan, player_name, idx)
	local item = AssemblyPlan[idx]
	if item ~= nil then
		local y, path, fd_offs, node_name = item[1], item[2], item[3], item[4]
		local pos1 = dest_pos(pos, param2, path, y)
		if not minetest.is_protected(pos1, player_name) then
			local stack = ItemStack(node_name)
			if inv:room_for_item("src", stack) then
				local node = minetest.get_node(pos1)
				if node.name == node_name then
					local meta = M(pos1):to_table()
					minetest.remove_node(pos1)
					inv:add_item("src", stack)
					play_sound(pos, "default_dig_cracky")
					local ndef = minetest.registered_nodes[node_name]
					if ndef and ndef.after_dig_node then
						local digger = minetest.get_player_by_name(player_name)
						ndef.after_dig_node(pos1, node, meta, digger)
					end
				end
			end
		end
		minetest.after(0.5, remove_inv, pos, inv, param2, AssemblyPlan, player_name, idx - 1)
	else
		local nvm = techage.get_nvm(pos)
		nvm.assemble_locked = false
	end
end

function techage.assemble.build_inv(pos, inv, AssemblyPlan, player_name)
	-- check protection
	if minetest.is_protected(pos, player_name) then
		return
	end
	local nvm = techage.get_nvm(pos)
	if nvm.assemble_locked then
		return
	end
	local node = minetest.get_node(pos)
	nvm.assemble_locked = true
	build_inv(pos, inv, node.param2, AssemblyPlan, player_name, 1)
end

function techage.assemble.remove_inv(pos, inv, AssemblyPlan, player_name)
	-- check protection
	if minetest.is_protected(pos, player_name) then
		return
	end
	local nvm = techage.get_nvm(pos)
	if nvm.assemble_locked then
		return
	end
	local node = minetest.get_node(pos)
	nvm.assemble_locked = true
	remove_inv(pos, inv, node.param2, AssemblyPlan, player_name, #AssemblyPlan)
end

function techage.assemble.count_items(AssemblyPlan)
	local t = {}
	for _, item in ipairs(AssemblyPlan) do
		local node_name = item[4]
		local ndef = minetest.registered_nodes[node_name]
		local name = ndef.description
		if not t[name] then
			t[name] = 1
		else
			t[name] = t[name] + 1
		end
	end
	return t
end

-- Determine the destination position based on the given route
-- param2, and a route table like : {0,3}
-- 0 = forward, 1 = right, 2 = backward, 3 = left
-- techage.assemble.get_pos(pos, param2, route, y_offs)
techage.assemble.get_pos = dest_pos