--[[

	The MIT License (MIT)

	Copyright (C) 2019 GreenXenith/GreenDimond

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to
	deal in the Software without restriction, including without limitation the
	rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
	sell copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
	IN THE SOFTWARE.

]]--

local storage = minetest.get_mod_storage()
local range_max = 20
local connection_max = 100
local rules = {
	{x = 1, y = 0, z = 0},
	{x =-1, y = 0, z = 0},
	{x = 0, y = 1, z = 0},
	{x = 0, y =-1, z = 0},
	{x = 0, y = 0, z = 1},
	{x = 0, y = 0, z =-1},
}

local function hash(pos)
	return minetest.hash_node_position(pos)
end

local function dehash(hash)
	return minetest.get_position_from_hash(hash)
end

-- Run function on each position in channel
local function do_for_each(owner, channel, origin, func)
	-- Put data in table
	local data = minetest.deserialize(storage:get_string(owner..":"..channel))
	if not data then
		return
	end
	-- Get range
	local range = minetest.get_meta(origin):get_int("range")
	for pos in pairs(data) do
		pos = dehash(pos)
		if pos ~= origin then
			local dist = vector.distance(origin, pos)
			if dist <= range then
				func(pos, dist)
			end
		end
	end
end

-- Modify storage as table
local function storage_table(owner, channel, func)
	local t = minetest.deserialize(storage:get_string(owner..":"..channel)) or {}
	local set = func(t)
	if type(set) == "table" then
		set = minetest.serialize(set)
	end
	storage:set_string(owner..":"..channel, set)
end

-- Limit digiline actions to 20 per second
local function overheat(pos)
	local actions = minetest.get_meta(pos):get_int("actions")
	if actions >= 20 then
		local node = minetest.get_node(pos)
		-- Burnout
		if node.param2 ~= 192 then
			minetest.swap_node(pos, {name = node.name, param2 = 192})
		end
		return true, actions
	end
	return false, actions
end

-- Signal Transmitter
mesecon.register_node("mesecons_wireless:transmitter", {
	description = "Wireless Mesecon Transmitter",
	overlay_tiles = {
		"",
		"",
		{name = "mesecons_wireless_transmitter_signal.png"},
	},
	paramtype2 = "color",
	palette = "mesecons_wireless_signal_palette.png",
	digiline = {
		effector = {
			action = function(pos, node, dchannel, msg)
				local meta = minetest.get_meta(pos)
				local owner = meta:get_string("owner")
				local channel = meta:get_string("channel")
				local overheated, actions = overheat(pos)
				if overheated then
					return
				end
				-- Signal indicator
				if node.param2 ~= 128 then
					minetest.swap_node(pos, {name = node.name, param2 = 128})
				end
				-- Increase action count
				meta:set_int("actions", actions + 1)
				do_for_each(owner, channel, pos, function(each)
					-- Relay signal
					digiline:receptor_send(each, digiline.rules.default, dchannel, msg)
					local enode = minetest.get_node(each)
					minetest.swap_node(each, {name = enode.name, param2 = 128})
				end)
			end
		}
	},
	on_timer = function(pos)
		-- Cooldown
		minetest.get_meta(pos):set_int("actions", 0)
		local node = minetest.get_node(pos)
		if node.param2 ~= minetest.registered_nodes[node.name].place_param2 then
			minetest.swap_node(pos, {name = node.name, param2 = minetest.registered_nodes[node.name].place_param2})
		end
		minetest.get_node_timer(pos):set(1, 0)
	end,
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_int("range", 15)
		meta:set_string("formspec", "field[channel;Channel;${channel}]field[range;Range (1-"..range_max..");${range}")
		minetest.get_node_timer(pos):set(1, 0)
	end,
	on_destruct = function(pos)
		local meta = minetest.get_meta(pos)
		local owner = meta:get_string("owner")
		local channel = meta:get_string("channel")
		if channel and channel ~= "" then
			do_for_each(owner, channel, pos, function(each)
				minetest.swap_node(each, {name = "mesecons_wireless:receiver_off", param2 = 0})
				mesecon.receptor_off(each, rules)
			end)
		end
	end,
	after_place_node = function(pos, placer)
		minetest.get_meta(pos):set_string("owner", placer:get_player_name())
	end,
	on_receive_fields = function(pos, _, fields, player)
		local meta = minetest.get_meta(pos)
		local setter = player:get_player_name()
		local owner = meta:get_string("owner")
		local channel = meta:get_string("channel")

		-- Only owner can set
		if setter == owner then
			if fields.channel then
				meta:set_string("channel", fields.channel)
			end
			-- Make sure range is valid
			local newrange
			if fields.range then
				newrange = tonumber(fields.range)
			end
			if newrange and newrange >= 1 and newrange <= range_max then
				local current = meta:get_int("range")
				if minetest.get_node(pos).name ~= "mesecons_wireless:transmitter_on" then
					meta:set_int("range", newrange)
					return
				end
				-- Turn off receivers outside smaller range
				if current and current ~= 0 then
					if current > newrange then
						do_for_each(owner, channel, pos, function(each, dist)
							if dist > newrange then
								minetest.swap_node(each, {name = "mesecons_wireless:receiver_off", param2 = 0})
								mesecon.receptor_off(each, rules)
							end
						end)
					end
				end
				meta:set_int("range", newrange)
				-- Turn on new receivers
				do_for_each(owner, channel, pos, function(each)
					local name = minetest.get_node(each).name
					if name == "mesecons_wireless:receiver_on" then
						return
					end
					minetest.swap_node(each, {name = "mesecons_wireless:receiver_on", param2 = 64})
					mesecon.receptor_on(each, rules)
				end)
			end
		end
	end,
	},
	{
		tiles = {
			{name = "mesecons_wireless_metal.png", color = "white"},
			{name = "mesecons_wireless_metal.png", color = "white"},
			{name = "mesecons_wireless_transmitter_off.png", color = "white"}
		},
		color = 0,
		place_param2 = 0,
		groups = {cracky=1},
		mesecons = {
			effector = {
				action_on = function(pos)
					local overheated, actions = overheat(pos)
					if overheated then
						return
					end
					minetest.swap_node(pos, {name = "mesecons_wireless:transmitter_on", param2 = 64})
					local meta = minetest.get_meta(pos)
					local owner = meta:get_string("owner")
					local channel = meta:get_string("channel")
					-- Higher action value
					meta:set_int("actions", actions + 5)
					do_for_each(owner, channel, pos, function(each)
						minetest.swap_node(each, {name = "mesecons_wireless:receiver_on", param2 = 64})
						mesecon.receptor_on(each, rules)
					end)
				end
			},
		},
	},
	{
		tiles = {
			{name = "mesecons_wireless_metal.png", color = "white"},
			{name = "mesecons_wireless_metal.png", color = "white"},
			{name = "mesecons_wireless_transmitter_on.png", color = "white"}
		},
		color = 64,
		place_param2 = 64,
		paramtype = "light",
		light_source = 2,
		groups = {cracky=1, not_in_creative_inventory=1},
		mesecons = {
			effector = {
				action_off = function(pos)
					minetest.swap_node(pos, {name = "mesecons_wireless:transmitter_off", param2 = 0})
					local meta = minetest.get_meta(pos)
					local owner = meta:get_string("owner")
					local channel = meta:get_string("channel")
					do_for_each(owner, channel, pos, function(each)
						minetest.swap_node(each, {name = "mesecons_wireless:receiver_off", param2 = 0})
						mesecon.receptor_off(each, rules)
					end)
				end
			},
		},
	}
)

mesecon.register_node("mesecons_wireless:receiver", {
	description = "Wireless Mesecon Receiver",
	overlay_tiles = {
		"",
		"",
		{name = "mesecons_wireless_receiver_signal.png"},
	},
	paramtype2 = "color",
	palette = "mesecons_wireless_signal_palette.png",
	digiline = {
		receptor = {},
	},
	on_timer = function(pos)
		-- Handle signal indicator
		local node = minetest.get_node(pos)
		if node.param2 ~= minetest.registered_nodes[node.name].place_param2 then
			minetest.swap_node(pos, {name = node.name, param2 = minetest.registered_nodes[node.name].place_param2})
		end
		minetest.get_node_timer(pos):set(1, 0)
	end,
	on_construct = function(pos)
		minetest.get_meta(pos):set_string("formspec", "field[channel;Channel;${channel}]")
		minetest.get_node_timer(pos):set(1, 0)
	end,
	on_destruct = function(pos)
		local meta = minetest.get_meta(pos)
		local owner = meta:get_string("owner")
		local channel = meta:get_string("channel")
		if channel and channel ~= "" then
			storage_table(owner, channel, function(data)
				data[hash(pos)] = nil
				if not next(data) then
					return ""
				end
				return data
			end)
		end
	end,
	after_place_node = function(pos, placer)
		minetest.get_meta(pos):set_string("owner", placer:get_player_name())
	end,
	on_receive_fields = function(pos, _, fields, player)
		local meta = minetest.get_meta(pos)
		local setter = player:get_player_name()
		local owner = meta:get_string("owner")

		if setter == owner then
			if not fields.channel then
				return
			end
			-- Add to network if network < 100
			local t = minetest.deserialize(storage:get_string(owner..":"..fields.channel))
			if t then
				local connections = 0
				for c in pairs(t) do
					connections = connections + 1
				end
				if connections >= connection_max then
					return
				end
			end
			local current = meta:get_string("channel")
			if current and current ~= "" and current ~= fields.channel then
				storage_table(owner, current, function(data)
					data[hash(pos)] = nil
					if not next(data) then
						return ""
					end
					return data
				end)
			end
			meta:set_string("channel", fields.channel)
			if fields.channel == "" then
				return
			end
			storage_table(owner, fields.channel, function(data)
				data[hash(pos)] = 0
				return data
			end)
		end
	end,
	},
	{
		tiles = {
			{name = "mesecons_wireless_metal.png", color = "white"},
			{name = "mesecons_wireless_metal.png", color = "white"},
			{name = "mesecons_wireless_receiver_off.png", color = "white"},
		},
		color = 0,
		place_param2 = 0,
		groups = {cracky=1},
		mesecons = {
			receptor = {
				state = mesecon.state.off,
				rules = rules
			}
		},
	},
	{
		tiles = {
			{name = "mesecons_wireless_metal.png", color = "white"},
			{name = "mesecons_wireless_metal.png", color = "white"},
			{name = "mesecons_wireless_receiver_on.png", color = "white"},
		},
		color = 64,
		place_param2 = 64,
		paramtype = "light",
		light_source = 2,
		groups = {cracky=1, not_in_creative_inventory=1},
		mesecons = {
			receptor = {
				state = mesecon.state.on,
				rules = rules
			}
		},
	}
)

-- Start timers
minetest.register_lbm({
	label = "Refresh Wireless Transmitters",
	name = "mesecons_wireless:clear_actions",
	nodenames = {
		"mesecons_wireless:transmitter_on",
		"mesecons_wireless:transmitter_off",
		"mesecons_wireless:receiver_on",
		"mesecons_wireless:receiver_off",
	},
	run_at_every_load = true,
	action = function(pos)
		minetest.get_node_timer(pos):set(1, 0)
	end,
})

-- Crafting
minetest.register_craftitem("mesecons_wireless:antenna", {
	description = "Antenna",
	inventory_image = "mesecons_wireless_antenna.png",
})

minetest.register_craftitem("mesecons_wireless:dish", {
	description = "Radio Dish",
	inventory_image = "mesecons_wireless_dish.png",
})

local wire = "mesecons:wire_00000000_off"
if minetest.get_modpath("digilines") then
	wire = "digilines:wire_std_00000000"
end