local altar_item_offset = {
	x = 0, y = -0.3, z = 0
}

local get_altar_ent = function(pos)
	for _, obj in pairs(minetest.get_objects_inside_radius(pos,0.9)) do
		if not obj then goto nextloop end
		local ent = obj:get_luaentity()
		if not ent then goto nextloop end

		if ent.name == "sorcery:altar_item" then
			return obj
		end

		::nextloop::
	end
	return nil
end

local update_altar = function(pos, object)
	local meta = minetest.get_meta(pos)
	local contents = meta:get_inventory()

	if object == nil then
		object = get_altar_ent(pos)
		if object == nil then
			object = minetest.add_entity({
				x = pos.x + altar_item_offset.x,
				y = pos.y + altar_item_offset.y,
				z = pos.z + altar_item_offset.z
			}, "sorcery:altar_item")
		end
	end

	if contents:is_empty('item') then
		if object ~= nil then object:remove() end
	else
		local itemstring = contents:get_stack('item',1):to_string()
		local props = object:get_properties()
		local node = minetest.get_node(pos)
		props.wield_item = itemstring
		object:set_properties(props)
		object:set_yaw(math.pi*2 - node.param2*(math.pi / 2))
	end
end

for name, god in pairs(sorcery.data.gods) do
	local hitbox = {
		0-(god.idol.width / 2.0), 0-(god.idol.height / 2.0), -0.15,
		   god.idol.width / 2.0,     god.idol.height / 2.0,   0.15
	} -- {xmin, ymin, zmin,
	  -- xmax, ymax, zmax} in nodes from node center.
	paramtype = "light";
	minetest.register_node('sorcery:idol_' .. name, {
		description = god.idol.desc;
		drawtype = "mesh";
		mesh = 'sorcery-idol-' .. name .. '.obj';
		paramtype = 'light';
		paramtype2 = 'facedir';
		sunlight_propagates = true;
		stack_max = 1;
		tiles = god.idol.tex;
		selection_box = { type = "fixed"; fixed = {hitbox}; };
		collision_box = { type = "fixed"; fixed = {hitbox}; };
		groups = { cracky = 2, sorcery_idol = 1, heavy = 1, sorcery_worship = 1};

		after_place_node = function(pos, placer, stack, pointat)
			local meta = minetest.get_meta(pos)
			local stackmeta = stack:get_meta()
			meta:set_int('favor', stackmeta:get_int('favor'))
			meta:set_string('last_sacrifice', stackmeta:get_string('last_sacrifice'))

			minetest.get_node_timer(pos):start(60)
		end;

		drop = {
			-- for some idiot reason this is necessary for
			-- preserve_metadata to work right
			max_items = 1;
			items = {
				{ items = {'sorcery:idol_' .. name} }
			};
		};

		preserve_metadata = function(pos, node, meta, newstack)
			newstack[1]:get_meta():from_table(meta)
		end;

		on_timer = function(pos, elapsed)
			local altar = minetest.find_node_near(pos, 3, "sorcery:altar")
			-- TODO even without an altar, an idol with high favor could still be the source of miracles
			if not altar then return true end

			local altarmeta = minetest.get_meta(altar)
			local inv = altarmeta:get_inventory()
			local idolmeta = minetest.get_meta(pos)
			local divine_favor = idolmeta:get_int('favor')
			local bestow = function(item,color)
				if type(item) == 'string' then
					item = ItemStack(item)
				end
				if color == nil then
					color = sorcery.lib.color(god.color)
				end
				local range = function(min, max)
					local span = max - min
					local val = math.random() * span
					return val + min
				end
				for i=0,32 do
					minetest.add_particle{
						pos = {
							x = altar.x + range(-0.4,0.4);
							z = altar.z + range(-0.4,0.4);
							y = altar.y + range(-0.2,0.3);
						};
						expirationtime = range(3,8);
						size = range(1,3);
						velocity = {
							x = range(-0.6, 0.6);
							z = range(-0.6, 0.6);
							y = range(-0.6, 0.6);
						};
						acceleration = {
							x = 0; y = range(0,1); z = 0;
						};
						texture = sorcery.lib.image('sorcery_sparkle.png'):
							transform(math.random(8) - 1):
							multiply(color:brighten(1.7)):
								render();
						glow = 14;
					}
				end
				for i=0,48 do
					minetest.add_particle{
						pos = {
							x = altar.x + range(-0.3,0.3);
							z = altar.z + range(-0.3,0.3);
							y = altar.y + range(-0.3,0.3);
						};
						expirationtime = range(3,8);
						size = range(2,9);
						velocity = {
							x = range(-0.5, 0.5);
							z = range(-0.5, 0.5);
							y = range(-0.3, 0.8);
						};
						acceleration = {
							x = 0; y = range(0,1); z = 0;
						};
						texture = 'sorcery_divine_radiance_'..math.random(9)..'.png' ..
							'^[multiply:' .. color:hex() ..
							'^[opacity:' .. (math.random(127) + 127) ..
							'^[transform' .. (math.random(8) - 1);
						glow = math.random(14 - 9) + 9;
					}
				end
				inv:set_stack('item',1,item)
			end
			if inv:is_empty('item') then 
				-- nothing on the altar. decide if we're going to do a
				-- gift cycle by rolling against the god's stinginess
				if math.random(god.stinginess) == 1 then
					-- we've beat the odds and started a gift cycle. now
					-- we pick a random gift and roll against its rarity
					-- to determine if the god is feeling generous
					local gift = sorcery.lib.tbl.pick(god.gifts)
					local data = god.gifts[gift]
					local value, rarity = data[1], data[2]
					if value <= divine_favor and math.random(rarity) == 1 then
						bestow(gift)
						print(god.name .. ' has produced ' .. gift .. ' upon an altar as a gift')
						if math.random(god.generosity) == 1 then
							-- unappreciated gifts may incur divine
							-- irritation
							divine_favor = divine_favor - 1
						end
					end
				end
				
				goto refresh
			end

			-- gods are taciturn and unpredictable creatures, but if you
			-- have won their favor for your idol through sacrifice and
			-- obeisance, they are more likely to pay attention to you
			-- and lay blessings more rapidly upon you
			do
				local chance_to_act = math.max(2, god.laziness - divine_favor)
				if math.random(chance_to_act) ~= 1 then goto refresh end

				local stack = inv:get_stack('item',1)
				local itemname = stack:get_name()

				-- loop through the sacrifices accepted by this god and check
				-- whether the item on the altar is any of them
				for s, value in pairs(god.sacrifice) do
					if itemname == s then
						if value < 0 then
							bestow("sorcery:ash", sorcery.lib.color(254,117,103))
						else
							if idolmeta:get_string('last_sacrifice') == s then
								-- the gods are getting bored
								value = math.floor(value / 2)
							end
							bestow(nil)
						end
						divine_favor = divine_favor + value
						
						print(god.name.." has accepted a sacrifice of "..s..", raising divine favor by "..value.." points to "..divine_favor)
						idolmeta:set_string('last_sacrifice', s)

						goto refresh
					end
				end

				-- loop through the list of things this god will consecrate and
				-- check whether the item on the altar is any of them
				for s, cons in pairs(god.consecrate) do
					local cost, tx = cons[1], cons[2]
					if type(tx) == "table" then
						tx = tx[math.random(#tx)]
					end
					local gift = ItemStack(tx)
					local wear = stack:get_wear()
					if wear > 0 then
						gift:set_wear(wear)
					end
					if itemname == s then
						if divine_favor >= cost then
							bestow(gift)
							divine_favor = divine_favor - cost
							print(god.name..'has consecrated ' ..s.. ' into ' ..tx.. ', for the cost of ' ..cost.. ' points of divine favor')
							goto refresh
						end
					end
				end
			end

			::refresh::
			idolmeta:set_int('favor', divine_favor)
			update_altar(altar,nil)
			return true
		end;
	})

end

minetest.register_entity("sorcery:altar_item", {
	initial_properties = {
		visual = "wielditem";
		visual_size = { x = 0.2, y = 0.2 };
		wield_item = "air";
		glow = 11; -- why the fuck isn't light working normally?
		collisionbox = {0};
		physical = false;
		pointable = false;
	};

	on_activate = function(self,data,dtime)
		local pos = self.object:get_pos()
		local nodepos = {
			x = pos.x - altar_item_offset.x,
			y = pos.y - altar_item_offset.y,
			z = pos.z - altar_item_offset.z
		}
		if minetest.get_node(nodepos).name ~= "sorcery:altar" then
			self.object:remove()
		else
			update_altar(nodepos,self.object)
		end
	end
})
minetest.register_node("sorcery:altar", {
	description = "Altar";
	drawtype = "mesh";
	mesh = "sorcery-altar.obj";
	paramtype = "light";
	paramtype2 = "facedir";
	sunlight_propagates = true;
	tiles = { 
		"default_sandstone.png",
		"default_silver_sandstone.png",
		"default_copper_block.png",
		"default_steel_block.png",
		"default_gold_block.png",
		"default_coal_block.png"
	};
	selection_box = {
		type = "fixed",
		fixed = { {-0.5, -0.5, -0.5, 0.5, -0.09, 0.5} }
	};
	collision_box = {
		type = "fixed",
		fixed = { {-0.5, -0.5, -0.5, 0.5, -0.09, 0.5} }
	};

	groups = {cracky = 2, oddly_breakable_by_hand = 2, sorcery_worship = 1};

	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		inv:set_size('item', 1)
	end;

	on_destruct = function(pos)
		local ent = get_altar_ent(pos)
		if not ent then return nil end

		ent:remove()
	end;

	on_rightclick = function(pos,node,user,stack)
		local meta = minetest.get_meta(pos)
		local contents = meta:get_inventory()
		if contents:is_empty('item') then
			if stack:is_empty() then return stack end
			local new_item = stack:take_item(1)
			contents:set_stack('item',1,new_item)
		else
			local pinv = user:get_inventory()
			if pinv == nil
				then return stack end

			local give_item = contents:get_stack('item',1)

			-- this is more complex than it should really need to
			-- be. because minetest implements a modify-current-
			-- stack-through-return-value feature, which is very
			-- poorly integrated with features for controlling the
			-- player's inventory, it's not enough to just say
			-- "give them the item" and let minetest work out where
			-- to place it. the value returned by this function
			-- always overrides any changes to the inventory made
			-- by this function, so if the current stack matches
			-- the object being removed, it will seem to just
			-- disappear into the ether, as the original stack
			-- argument overwrites it. (yes, returning nil has the
			-- same effect, i tried. bug imo.)

			if stack:item_fits(give_item) then
				-- first check if the item we're taking fits onto 
				-- the selected stack, and just increment it if so.
				stack:add_item(give_item)
			elseif not pinv:room_for_item('main',give_item) then
				-- it doesn't fit onto the current stack, but does
				-- it fit into the inventory somewhere else? if not,
				-- we need to bail without changing anything
				return stack
			else -- it fits in the inventory
				pinv:add_item('main',give_item)
			end

			-- clear the contents of the altar
			contents:set_stack('item',1,ItemStack(nil))

		end

		update_altar(pos,nil)
		return stack
	end
})
