-- LUALOCALS < ---------------------------------------------------------
local klots, math, minetest, vector
    = klots, math, minetest, vector
local math_cos, math_pi, math_random
    = math.cos, math.pi, math.random
-- LUALOCALS > ---------------------------------------------------------

local modname = minetest.get_current_modname()

local nodename = modname .. ":teleporter"
local particlename = modname .. ":teleporter_particles"

local function porticles(pos)
	local cpos = {x = pos.x, y = pos.y + 0.5, z = pos.z}
	return minetest.add_particlespawner({
			time = 1,
			amount = 25,
			exptime = 1,
			size = {
				min = 1,
				max = 3,
				bias = 1
			},
			collisiondetection = false,
			node = {name = particlename},
			pos = cpos,
			radius = {
				min = 1,
				max = 3,
				bias = 1
			},
			attract = {
				kind = "point",
				strength = {
					min = 1,
					max = 5,
					bias = 1
				},
				origin = cpos
			}
		})
end

local function getdestraw(pos)
	local meta = minetest.get_meta(pos)
	local topos = meta:get_string("dest") or ""
	return topos and topos ~= "" and minetest.deserialize(topos)
end

local function getdest(pos)
	local dest = getdestraw(pos)
	if dest.win then
		return {
			x = 0,
			y = 20000,
			z = 0
		}
	end
	if dest.lv then
		return klots.leveldata[dest.lv].pos
	end
	return dest
end

minetest.register_node(particlename,
	{tiles = {modname .. "_teleparticles.png"}})

minetest.register_node(nodename, {
		description = "Teleporter",
		drawtype = "airlike",
		walkable = false,
		paramtype = "light",
		sunlight_propagates = true,
		pointable = klots.editmode or false,
		groups = {
			ambiance = 100,
			auto_timer = 100
		},
		sounds = {
			ambiance = {
				name = modname .. "_portal",
				gain = 0.2,
				max_hear_distance = 12
			}
		},
		on_timer = function(pos)
			minetest.get_node_timer(pos):start(1)
			return porticles(pos)
		end,
		on_construct = function(pos)
			minetest.get_node_timer(pos):start(0.001)
		end,
		on_rightclick = klots.editmode and function(pos, _, clicker)
			local pname = clicker and clicker.get_player_name
			and clicker:get_player_name()
			if not pname then return end
			local dest = getdestraw(pos) or {}
			klots.show_editor(clicker, pos, minetest.serialize(dest), function(text)
					local topos = minetest.deserialize(text)
					minetest.get_meta(pos):set_string("dest",
						minetest.serialize(topos))
				end)
		end
	})

if not klots.editmode then
	klots.register_playerstep(function(player, data, dtime)
			local pos = player:get_pos()

			local found = data.tpos
			if not (found and minetest.get_node(found).name == nodename) then
				found = minetest.find_node_near(pos, 3, nodename, true)
				if not found then return end
				found.y = found.y - 0.49
				data.tpos = found
			end

			if vector.distance(pos, found) > 5 then
				data.tpos = nil
				return
			end

			local topos = getdest(found)
			if not topos then return end

			if vector.distance(pos, found) < 1 then
				data.tpos = nil
				return klots.player_teleport(player, found, topos)
			end

			klots.player_control_set(player, "zerognofly")
			klots.player_control_apply(player, function(obj)
					local dv = vector.subtract(found, pos)
					dv = vector.multiply(dv, dtime * 10)
					local vel = obj:get_velocity()
					dv = vector.add(dv, vector.multiply(vel,
							(0.5 ^ dtime) - 1))
					obj:add_velocity(dv)
				end)
		end)

	local tau = math_pi * 2
	klots.register_on_player_teleport(function(player, from)
			local pos = player:get_pos()
			if from.y < 128 and pos.y < 128 then return end

			minetest.sound_play(modname .. "_teleport", {
					pos = from,
					gain = 1
				}, true)

			minetest.sound_play(modname .. "_teleport", {
					pos = pos,
					gain = 1
				}, true)

			pos.y = pos.y + player:get_properties().eye_height
			local theta = math_random() * tau
			for i = 1, 3 do
				local p = {
					x = pos.x + math_cos(theta + i * tau / 3),
					y = pos.y,
					z = pos.z + math_cos(theta + i * tau / 3),
				}
				minetest.sound_play(modname .. "_teleport", {
						pos = p,
						gain = 1,
						pitch = 0.9 + math_random() * 0.2,
						to_player = player:get_player_name()
					}, true)
			end
		end)
end
