
-- see elepower_papi >> external_nodes_items.lua for explanation
-- shorten table ref
local epr = ele.external.ref
local epi = ele.external.ing

-- Thermal Evaporation Plant
-- Used to extract salt from water
elethermal.cache = {}
local results = {
	{
		input = epr.water_source.." 1000",
		output = "elepower_thermal:brine_source 100",
		heat = 400
	},
	{
		input = "elepower_thermal:brine_source 1000",
		output = "elepower_dynamics:lithium_source 100",
		heat = 400
	}
}

-- Validate evaporator structure from controller position
local function validate_structure(pos, player)
	local inputs = {}
	local outputs = {}
	local all = {}
	local thermal = 0
	local height = 0
	local height_cancel = false

	local node = minetest.get_node(pos)
	local dir = minetest.facedir_to_dir(node.param2)

	for y = 0, 18 do
		if height_cancel then break end
		y = y - 4
		local i = 0
		for x = 0, 3 do
			x = x - 3
			for z = 0, 3 do
				z = z - 2
				local rx = x
				local rz = z
				if dir.z ~= 0 then
					rx = z * -dir.z
					rz = x * -dir.z
				else
					rx = rx * -dir.x
					rz = rz * -dir.x
				end

				local p = vector.add(pos, {x = rx, y = y, z = rz})
				local n = minetest.get_node(p)

				if n.name == "elepower_thermal:evaporator_controller" and not vector.equals(pos, p) then
					height = 0
					if player then minetest.chat_send_player(player, "Multiple controllers detected.") end
					break
				end

				if minetest.get_item_group(n.name, "ele_evaporator_node") > 0 then
					i = i + 1
				elseif minetest.get_item_group(n.name, "ele_solar_generator") > 0 then
					i = i + 1
					local m = minetest.get_meta(p)
					local generation = ele.helpers.get_node_property(m, p, "usage")
					thermal = thermal + generation
					height_cancel = true
				end

				if n.name == "elepower_thermal:evaporator_output" then
					table.insert(outputs, p)
				elseif n.name == "elepower_thermal:evaporator_input" then
					table.insert(inputs, p)
				end

				table.insert(all, p)
			end
		end

		if i == 16 or i == 12 then
			height = height + 1
		elseif height == 0 and y < 0 then
			-- Continue..
		else
			break
		end
	end

	if height <= 2 then
		if player then minetest.chat_send_player(player, "Invalid structure surrounding the controller.") end
		return nil
	end

	if player then minetest.chat_send_player(player, "Structure complete.") end
	local meta = minetest.get_meta(pos)
	meta:set_string("Thermal Evaporation Plant")

	elethermal.cache[minetest.pos_to_string(pos)] = {
		height = height,
		inputs = inputs,
		outputs = outputs,
		thermal = thermal,
		all = all
	}

	return height, inputs, outputs, thermal, all
end

local function get_port_controller(pos)
	for ctrl,t in pairs(elethermal.cache) do
		local ctrlpos = minetest.string_to_pos(ctrl)
		local found = false
		for _,p in pairs(t.all) do
			if vector.equals(p, pos) then
				found = true
				break
			end
		end
		if found then
			return ctrlpos, minetest.get_meta(ctrlpos)
		end
	end
	return nil
end

local function get_recipe(i1, heat)
	local result = nil

	for _, d in pairs(results) do
		local i1a = ItemStack(d.input)

		if i1a:get_name() == i1.fluid and heat > d.heat then
			result = d
			result.output = ItemStack(result.output)
			result.input = i1a
			break
		end
	end

	return result
end

local function controller_formspec (input, output, heat)
	local bar = "image[1.5,3.5;6,1;elethermal_gradient_bg.png^[transformR270]"
	if heat then
		bar = "image[1.5,3.5;6,1;elethermal_gradient_bg.png^[lowpart:"..
			  (100 * heat / 1000)..":elethermal_gradient.png^[transformR270]"
	end
	return "size[8,4.5]"..
		epr.gui_bg..
		epr.gui_bg_img..
		epr.gui_slots..
		bar..
		"tooltip[1.5,3.5;6,1;Heat: "..heat.."K]"..
		ele.formspec.fluid_bar(0, 0, input)..
		ele.formspec.fluid_bar(7, 0, output)
end

local function break_structure(pos)
	local ctrl = get_port_controller(pos)
	if not ctrl then return end
	elethermal.cache[minetest.pos_to_string(ctrl)] = nil
	ele.helpers.start_timer(ctrl)
end

local function controller_timer (pos, elapsed)
	local meta = minetest.get_meta(pos)
	local refresh = true

	if not elethermal.cache[minetest.pos_to_string(pos)] and not validate_structure(pos) then
		meta:set_string("infotext", "Thermal Evaporation Plant Incomplete")
		meta:set_string("formspec", "")
		return false
	end

	local contpos = minetest.pos_to_string(pos)
	local th = elethermal.cache[contpos]

	local in_buffer  = fluid_lib.get_buffer_data(pos, "input")
	local out_buffer = fluid_lib.get_buffer_data(pos, "output")

	local heat = meta:get_int("heat")

	while true do
		local baseline = math.floor(minetest.get_heat(pos) + 273.15)
		local reach_heat = math.floor(th.thermal + (th.height * 10)) + baseline

		if heat < reach_heat then
			heat = heat + reach_heat / 10
		end

		if heat > reach_heat then
			heat = reach_heat
		end

		if heat < baseline then
			heat = baseline
		end

		if heat > 1000 then
			heat = 1000
		end

		local recipe = get_recipe(in_buffer, heat)

		if not recipe then
			break
		end

		local heat_perc = heat / (recipe.heat + baseline)
		if recipe.heat + baseline < heat then
			heat_perc = 100
		end

		local take_perc = math.floor(heat_perc * recipe.input:get_count())
		local outp_perc = math.floor(heat_perc * recipe.output:get_count())

		if in_buffer.amount < take_perc then
			refresh = false
			break
		end

		if out_buffer.amount + outp_perc > out_buffer.capacity then
			refresh = false
			break
		end

		if out_buffer.fluid ~= "" and out_buffer.fluid ~= recipe.output:get_name() then
			refresh = false
			break
		end

		out_buffer.fluid = recipe.output:get_name()
		out_buffer.amount = out_buffer.amount + outp_perc
		in_buffer.amount = in_buffer.amount - take_perc
		heat = heat - (recipe.heat - baseline)
		break
	end

	meta:set_int("heat", heat)

	meta:set_string("input_fluid", in_buffer.fluid)
	meta:set_int("input_fluid_storage", in_buffer.amount)

	meta:set_string("output_fluid", out_buffer.fluid)
	meta:set_int("output_fluid_storage", out_buffer.amount)

	meta:set_string("formspec", controller_formspec(in_buffer, out_buffer, heat))
	return refresh
end

minetest.register_node("elepower_thermal:evaporator_controller", {
	description = "Thermal Evaporation Plant Controller",
	tiles = {
		"elepower_heat_casing.png", "elepower_heat_casing.png", "elepower_heat_casing.png",
		"elepower_heat_casing.png", "elepower_heat_casing.png", "elepower_heat_casing.png^elenuclear_fusion_controller.png",
	},
	paramtype2 = "facedir",
	groups = {
		cracky = 3,
		ele_evaporator_node = 1,
	},
	fluid_buffers = {
		input = {
			capacity  = 8000,
			accepts   = {"elepower_thermal:brine_source", epr.water_source},
			drainable = false,
		},
		output = {
			capacity  = 8000,
			accepts   = false,
			drainable = true,
		},
	},
	on_construct = function (pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("Thermal Evaporation Plant\nPunch to form structure")
	end,
	on_timer = controller_timer,
	on_punch = function (pos, node, puncher, pointed_thing)
		if validate_structure(pos, puncher:get_player_name()) then
			ele.helpers.start_timer(pos)
		end
		minetest.node_punch(pos, node, puncher, pointed_thing)
	end,
	on_destruct = break_structure,
})

minetest.register_node("elepower_thermal:evaporator_output", {
	description = "Thermal Evaporation Plant Output",
	tiles = {
		"elepower_heat_casing.png", "elepower_heat_casing.png", "elepower_heat_casing.png",
		"elepower_heat_casing.png", "elepower_heat_casing.png", "elepower_heat_casing.png^elenuclear_fluid_port_out.png^elepower_power_port.png",
	},
	paramtype2 = "facedir",
	groups = {
		cracky = 3,
		fluid_container = 1,
		ele_evaporator_node = 1,
	},
	fluid_buffers = {},
	node_io_can_put_liquid = function (pos, node, side)
		return false
	end,
	node_io_can_take_liquid = function (pos, node, side)
		local ctrl = get_port_controller(pos)
		if not ctrl then return false end
		return true
	end,
	node_io_accepts_millibuckets = function(pos, node, side) return true end,
	node_io_take_liquid = function(pos, node, side, taker, want_liquid, want_millibuckets)
		local ctrl, ctrl_meta = get_port_controller(pos)
		if not ctrl then return nil end

		local buffers = fluid_lib.get_node_buffers(ctrl)
		local buffer  = "output"
		local took    = 0
		local name    = ""

		local bfdata = fluid_lib.get_buffer_data(ctrl, buffer)
		local storage = bfdata.amount
		local fluid = bfdata.fluid
		if (fluid == want_liquid or want_liquid == "") and storage >= want_millibuckets then
			name, took = fluid_lib.take_from_buffer(ctrl, buffer, want_millibuckets)
		end

		ele.helpers.start_timer(ctrl)

		return {name = name, millibuckets = took}
	end,
	node_io_get_liquid_size = function (pos, node, side)
		return 1
	end,
	node_io_get_liquid_name = function(pos, node, side, index)
		local ctrl, ctrl_meta = get_port_controller(pos)
		if not ctrl then return "" end
		return ctrl_meta:get_string("output_fluid")
	end,
	node_io_get_liquid_stack = function(pos, node, side, index)
		local ctrl, ctrl_meta = get_port_controller(pos)
		if not ctrl then return ItemStack(nil) end

		return ItemStack(ctrl_meta:get_string("output_fluid") .. " " ..
			ctrl_meta:get_int("output_fluid_storage"))
	end,
	on_destruct = break_structure,
})

minetest.register_node("elepower_thermal:evaporator_input", {
	description = "Thermal Evaporation Plant Input",
	tiles = {
		"elepower_heat_casing.png", "elepower_heat_casing.png", "elepower_heat_casing.png",
		"elepower_heat_casing.png", "elepower_heat_casing.png", "elepower_heat_casing.png^elenuclear_fluid_port.png^elepower_power_port.png",
	},
	paramtype2 = "facedir",
	groups = {
		cracky = 3,
		fluid_container = 1,
		ele_evaporator_node = 1,
	},
	fluid_buffers = {},
	node_io_can_put_liquid = function (pos, node, side)
		return true
	end,
	node_io_can_take_liquid = function (pos, node, side)
		return false
	end,
	node_io_get_liquid_size = function (pos, node, side)
		return 1
	end,
	node_io_get_liquid_name = function(pos, node, side, index)
		local ctrl, ctrl_meta = get_port_controller(pos)
		if not ctrl then return "" end

		return ctrl_meta:get_string("input_fluid")
	end,
	node_io_get_liquid_stack = function(pos, node, side, index)
		local ctrl, ctrl_meta = get_port_controller(pos)
		if not ctrl then return ItemStack(nil) end

		return ItemStack(ctrl_meta:get_string("input_fluid") .. " " ..
			ctrl_meta:get_int("input_fluid_storage"))
	end,
	node_io_accepts_millibuckets = function(pos, node, side) return true end,
	node_io_put_liquid = function(pos, node, side, putter, liquid, millibuckets)
		local ctrl, ctrl_meta = get_port_controller(pos)
		if not ctrl then return millibuckets end
		if millibuckets == 0 then return 0 end
		local didnt_fit = fluid_lib.insert_into_buffer(ctrl, "input", liquid, millibuckets)

		ele.helpers.start_timer(ctrl)
		return didnt_fit
	end,
	node_io_room_for_liquid = function(pos, node, side, liquid, millibuckets)
		local ctrl, ctrl_meta = get_port_controller(pos)
		if not ctrl then return 0 end
		return fluid_lib.can_insert_into_buffer(ctrl, "input", liquid, millibuckets)
	end,
	on_destruct = break_structure,
})

minetest.override_item("elepower_machines:heat_casing", {
	on_destruct = break_structure,
})

minetest.register_lbm({
	label = "Enable Thermal Evaporators on load",
	name = "elepower_thermal:evaporator_controllers",
	nodenames = {"elepower_thermal:evaporator_controller"},
	run_at_every_load = true,
	action = ele.helpers.start_timer,
})
