-- Copyright (C) 2024 rstcxk
-- 
-- This program is free software: you can redistribute it and/or modify it under the terms of
-- the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
-- 
-- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-- without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
-- 
-- You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. 

S = core.get_translator(minetest.get_current_modname())

-- table containing all channels and their power levels
-- key: channel id, which is a combination of the three power levels
-- value: table with format 
-- {
-- power = <powerlevel>,
-- transmitters = <directory where keys are power levels, and values are the number of transmitters with that power level>
-- channel_id = the key to get to this table
-- }
redstonepp.radio_channels = {}

-- table containing hashes of all transmitters and the reference to the channel's data.
-- This is a hack for not being able to persist a lot of data in mcl_redstone's functions
-- key: position hash of the transmitter
-- value: table with the same format as values in redstonepp.radio_channels, is a direct refrence to those tables
redstonepp.transmitters = {}

local function get_channel_id(part1, part2, part3)
	return part1 * 256 + part2 * 16 + part3
end

-- assumes old_power and new_power arent equal
local function update_channel(channel_data, channel_id, old_power, new_power)
	core.debug(debug.traceback())
	core.debug(dump(channel_data), channel_id, old_power, new_power)
	if not channel_data then
		redstonepp.radio_channels[channel_id] = {power = new_power, transmitters = {[new_power] = 0}, channel_id = channel_id}
		channel_data = redstonepp.radio_channels[channel_id]
	end

	if new_power ~= 0 then
		channel_data.transmitters[new_power] = (channel_data.transmitters[new_power] or 0) + 1
	end

	if old_power ~= 0 and channel_data.transmitters[old_power] then
		channel_data.transmitters[old_power] = channel_data.transmitters[old_power] - 1
		if channel_data.transmitters[old_power] == 0 then
			channel_data.transmitters[old_power] = nil
		end
	end

	-- if no transmitters transmit at that power level and channel anymore
	if channel_data.transmitters[old_power] == nil then

		-- highest power level changed
		if channel_data.power == old_power  then
			local max = -1

			for power_level, _ in pairs(channel_data.transmitters) do
				if power_level > max then
					max = power_level 
				end
			end

			if max == -1 then
				-- no transmitters transmit at that channel anymore
				redstonepp.radio_channels[channel_data.channel_id] = nil
				channel_data = nil
			else
				channel_data.power = max
			end
			-- channel_data.power = max
		end
	end

	if channel_data and new_power > channel_data.power then
		channel_data.power = new_power
	end
end

local function update_receiver(pos, node)
	local right = minetest.fourdir_to_dir((node.param2 + 1) % 4)
	local left = minetest.fourdir_to_dir((node.param2 - 1) % 4)
	local back = -minetest.fourdir_to_dir((node.param2) % 4)
	local part1 = mcl_redstone.get_power(pos, left)
	local part2 = mcl_redstone.get_power(pos, back)
	local part3 = mcl_redstone.get_power(pos, right)

	local channel_id = get_channel_id(part1, part2, part3)
	local node_power = bit.rshift(node.param2, 2)
	local power_level = redstonepp.radio_channels[channel_id] and redstonepp.radio_channels[channel_id].power or 0
	-- core.debug(node_power, power_level, channel_id)
	if node_power ~= power_level then
		return
		{
			param2 = bit.bor(bit.band(node.param2, 3), power_level * 4),
			name = "redstonepp:radio_receiver",
		}
	end
end

local node_boxes = {
		{ -8/16, -8/16, -8/16,
		   8/16, -6/16,  8/16 },	-- the main slab
		{ -4/16, -6/16, 1/16,
		  -6/16, -1/16, -1/16 },	-- left torch
		{ 3/16, -6/16, 1/16,
		  5/16, -1/16, -1/16 },	-- right torch

		{ -1/16, -6/16, -6/16,
		  1/16, -1/16, -4/16 },	-- front torch

		{ 7/32, -6/16, 11/32,
		  11/32, -1/16, 7/32 },	-- soul torch
}

local collision_box = {
	type = "fixed",
	fixed = { -8/16, -8/16, -8/16, 8/16, -6/16, 8/16 },
}

-- param2 format:
-- 00 0000 00
-- ^^ ^^^^ ^^
-- AA BBBB CC
--
-- A - always zero
-- B - bits used to store redstone power
-- C - bit used to store rotation
core.register_node("redstonepp:radio_receiver",
{
	drawtype = "nodebox",
	node_box = 
	{
		type = "fixed",
		fixed = node_boxes
	},
	selection_box = collision_box,
	collision_box = collision_box,
	paramtype = "light",
	paramtype2 = "4dir",
	description = S("radio receiver"),
	tiles = {
		"redstonepp_radio_base.png",
		"redstonepp_radio_bottom.png",
		"redstonepp_radio_left_side.png",
	},
	on_construct = function(pos)
		local timer = minetest.get_node_timer(pos)
		if not timer:is_started() then
			timer:start(mcl_redstone.tick_speed)
		end
	end,
	on_rightclick = function(pos)
		core.set_node(pos, {name = "redstonepp:radio_transmitter", param2 = 0})
	end,
	on_timer = function(pos, elapsed)
		local new_node = update_receiver(pos, core.get_node(pos))
		if new_node then
			core.set_node(pos, new_node)
		end
		return true
	end,
	_mcl_redstone =
	{
		connects_to = function(node, dir)
			return true
		end,
		get_power = function(node, dir)
			return vector.equals(minetest.fourdir_to_dir(node.param2), dir) and bit.rshift(node.param2, 2) or 0
		end,
		update = function(pos, node)
			return update_receiver(pos, node)
		end
	}
})

core.register_node("redstonepp:radio_transmitter",
{
	drawtype = "nodebox",
	node_box = 
	{
		type = "fixed",
		fixed = node_boxes
	},
	selection_box = collision_box,
	collision_box = collision_box,
	paramtype = "light",
	paramtype2 = "4dir",
	description = S("radio transmitter"),
	tiles = {
		"redstonepp_radio_base.png^redstonepp_radio_base_on.png",
		"redstonepp_radio_bottom.png",
		"redstonepp_radio_left_side.png"
	},
	groups = {not_in_creative_inventory = 1},
	drop = "redstonepp:radio_receiver",
	after_destruct = function(pos, oldnode)
		local old_power = bit.rshift(oldnode.param2, 2)
		if old_power ~= 0 then
			local channel_data = redstonepp.transmitters[core.hash_node_position(pos)]
			update_channel(channel_data, channel_data.channel_id, old_power, 0)
		end
	end,
	on_rightclick = function(pos)
		local new_node = update_receiver(pos, core.get_node(pos))
		core.debug(dump(new_node))
		if new_node then
			core.set_node(pos, new_node)
		else
			core.set_node(pos, {name = "redstonepp:radio_receiver", param2 = 0})
		end
	end,
	_mcl_redstone =
	{
		connects_to = function(node, dir)
			return true
		end,
		update = function(pos, node)
			local right = minetest.fourdir_to_dir((node.param2 + 1) % 4)
			local left = minetest.fourdir_to_dir((node.param2 - 1) % 4)
			local back = -minetest.fourdir_to_dir((node.param2) % 4)
			local rear = minetest.fourdir_to_dir((node.param2) % 4)
			local part1 = mcl_redstone.get_power(pos, left)
			local part2 = mcl_redstone.get_power(pos, back)
			local part3 = mcl_redstone.get_power(pos, right)

			local channel_id = get_channel_id(part1, part2, part3)
			local old_power = bit.rshift(node.param2, 2)
			local rear_power = mcl_redstone.get_power(pos, rear)
			local channel_data = redstonepp.transmitters[minetest.hash_node_position(pos)]

			-- this means the transmitter changed its channel id
			if channel_data and channel_data.channel_id ~= channel_id then
				core.debug("AA1")
				update_channel(channel_data, channel_data.channel_id, old_power, 0)
				redstonepp.transmitters[minetest.hash_node_position(pos)] = redstonepp.radio_channels[channel_id]
				update_channel(redstonepp.radio_channels[channel_id], channel_id, 0, rear_power)
				redstonepp.transmitters[minetest.hash_node_position(pos)] = redstonepp.radio_channels[channel_id]
			end

			if old_power ~= rear_power then
				core.debug("AA2")
				channel_data = redstonepp.radio_channels[channel_id]
				update_channel(channel_data, channel_id, old_power, rear_power)
				redstonepp.transmitters[minetest.hash_node_position(pos)] = redstonepp.radio_channels[channel_id]
				return
				{
					param2 = bit.bor(bit.band(node.param2, 3), rear_power * 4),
					name = "redstonepp:radio_transmitter",
					delay = 1,
				}
			elseif redstonepp.radio_channels[channel_id] == nil and rear_power ~= 0 then
				core.debug("AA3")
				channel_data = redstonepp.radio_channels[channel_id]
				update_channel(channel_data, channel_id, 0, rear_power)
				redstonepp.transmitters[minetest.hash_node_position(pos)] = redstonepp.radio_channels[channel_id]
				return
				{
					param2 = bit.bor(bit.band(node.param2, 3), rear_power * 4),
					name = "redstonepp:radio_transmitter",
					delay = 1,
				}
			end
		end
	}
})

core.register_craft(
{
	output = "redstonepp:radio_transmitter",
	recipe = {
		{"mcl_redstone_torch:redstone_torch_on", "mcl_redstone_torch:redstone_torch_on", "mcl_redstone_torch:redstone_torch_on"},
		{"mcl_mobitems:blaze_powder", "mcl_blackstone:soul_torch", "mcl_mobitems:blaze_powder"},
		{"mcl_core:stone", "mcl_core:stone", "mcl_core:stone"},
	}
})
