config_storeXP = true; -- Also stops XP being dropped as baubles
config_signTypes = { "oak_sign", "spruce_sign", "birch_sign", "jungle_sign", "acacia_sign", "dark_oak_sign", "mangrove_sign", "cherry_sign", "pale_oak_sign", "bamboo_sign", "crimson_sign", "warped_sign" }

function signTypeFromName(playername)
	local hash = 0
	for i=1,#playername do
		hash = hash*33 + playername:byte(i)
	end
	local signIndex = hash % #config_signTypes + 1 -- lua indexes from 1, not 0
	local signType = config_signTypes[signIndex]
	return signType
end

function findNearbyTwoHighSpot(startingpos)
	local pos
	
	-- Start at the level the player died, then work our way upwards
	for extraverticaloffset = 0,15 do
		-- Search in a 3x3 horizontal region
		-- Spiral search pattern, starting in the middle
		local offsets3x3 = { Vec3(0,0,0), Vec3(1,0,0), Vec3(1,0,-1), Vec3(0,0,-1), Vec3(-1,0,-1), Vec3(-1,0,0), Vec3(-1,0,1), Vec3(0,0,1), Vec3(1,0,1) }
		for _,offset in pairs(offsets3x3) do
			pos = startingpos + offset + Vec3(0,extraverticaloffset,0)
			if blockIsEmpty(pos) and blockIsEmpty(pos + Vec3(0,1,0)) then return pos; end
		end
	end
	
	-- Give up, we can't find anywhere empty.  Player must be buried.  Replace some of what they're buried in.
	return startingpos
end

function blockIsEmpty(pos)
	-- Players won't mind these blocks getting destroyed/replaced with something like a chest.
	-- We don't want to replace something that will give them a sense of loss, like diamond blocks, obsidian, redstone or another chest.
	local emptyBlocks = {"air", "water", "lava", "portal"} 
	
	spell.pos = pos
	for _,blocktype in pairs(emptyBlocks) do
		if spell.block.type.id == blocktype then return true; end
	end
	return false
end

function sanitiseUsername(namein)
	local nameout=""
	local badchars = { '"', "'", "\\", '{', '}', '(', ')', ';' } -- Exhaustive?  Could be security holes here for arbitrary command execution.

	for i=1,#namein do
		local c = namein:sub(i,i)
		
		for _, badchar in pairs(badchars) do
			if c == badchar then
				c = '.'
				break
			end
		end
		nameout = nameout .. c
	end
	
	return nameout
end


function tableLength(table)
	-- Lua has a method to check the length of a table:
	--    local table = {1,2,3}
	--    print(#table)
	-- Unfortunately it only works if the table has consecutive numberic keys.  Which means it's totally useless for looking at player.nbt.equipment et al
	
	if not table then return 0; end
	local length = 0
	for a,b in pairs(table) do
		length = length + 1
	end
	return length
end

function valueOrDefault(value, default)
	if value then
		return value
	else
		return default
	end
end

function onEntityDeathHandler(event)
	if event.entity.type.id ~= "player" then return true; end
	-- todo check gamerule keepinventory?
	
	local player = event.entity
	
	-- Check if it's even worth dropping a grave
	do
		local playerGoodies = tableLength(player.nbt.equipment) + tableLength(player.nbt.Inventory)
		if player.nbt.XpLevel >= 3 then playerGoodies = playerGoodies + 1; end;
		if playerGoodies == 0 then return true; end
	end
	
	local signPos, signEntity
	do
		signPos = findNearbyTwoHighSpot(player.pos)
		spell.pos = signPos
		local signType = signTypeFromName(player.name)
		spell.block = Block:new(signType)
		signEntity = spell.blockEntity
			
		local signMessages = {"","RIP", player.name, str(tableLength(player.nbt.Inventory)+tableLength(player.nbt.equipment)) .. " items " .. str(player.nbt.XpLevel) .. " XP"}
		signEntity:putNbt( { back_text = {messages=signMessages}, front_text = {messages=signMessages} } )
	end
	
	local headPos, headEntity
	do
		headPos = signPos + Vec3(0,1,0)
		spell.pos = headPos
		spell.block = Block:new("minecraft:player_head")
		headEntity = spell.blockEntity
		
		headEntity:putNbt( { profile = {name=player.name} } )
	end
	
	-- An 'interaction' is an invisible entity that is the size of a block. See https://minecraft.wiki/w/Interaction
	-- We want somewhere to store the player's name and inventory contents, but the game won't allow us to attach extra arbitrary information to standard blocks (like signs & heads).  We use this invisible entity as a workaround instead.
	local invisibleEntity
	do
		spell.pos = signEntity.pos + Vec3(0.5,0,0.5)
		invisibleEntity = spell:summon("minecraft:interaction")
		
		invisibleEntity:putNbt( { height = 1.6 } )
		invisibleEntity:putExtra({
			deathSign = 
			{ 
				XpTotal = player.nbt.XpTotal,
				inventory = player.nbt.Inventory, 
				equipment = player.nbt.equipment,
				playerName = "Nidmyr",
				playerUUID = "1234",
				XpLevel = player.nbt.XpLevel,
				XpTotal = player.nbt.XpTotal,
			}
		})
	end
	
	player.inventory:clear() -- avoid items _also_ being dropped as per normal minecraft
	if config_storeXP == true then spell:executeSilent("/xp set " .. player.uuid .. " 0 levels"); end
	
	return true -- tell the game to continue death as normal.
end

function onEntityClickHandler(event)
	if not event.hitResult then return true; end -- Two events are fired on every right click, we only want to run once
	if not event.entity then return true; end
	if not event.entity.name == "Interaction" then return true; end
	if not event.entity.extra.deathSign then return true; end
	
	
	local player = event.player
	local deathSignData = event.entity.extra.deathSign
	
	-- Step 0: Check if this player is allowed to touch
	do
		local playermatches = false
		if deathSignData.playerUUID then
			if deathSignData.playerUUID == player.uuid then playermatches = true; end -- New grave format, use UUIDs
		else
			if deathSignData.playerName == player.name then playermatches = true; end -- Old grave format, kept for compat with older graves.  TODO remove at some point.
		end
		
		if not playermatches then
			if event.player.mainHandItem and event.player.mainHandItem.name == "Shears" then
				spell:execute("/tellraw " .. player.name .. " {text:\"Stealing " .. sanitiseUsername(deathSignData.playerName) .. "'s grave.\",color:'yellow'}") -- Minecraft bug: can't use player UUID in this command, only player name :|
			else
				spell:execute("/tellraw " .. player.name .. " {text:\"This is " .. sanitiseUsername(deathSignData.playerName) .. "'s grave.  To destroy it use a pair of shears\",color:'yellow'}") -- Minecraft bug: can't use player UUID in this command, only player name :|
				return true
			end
		end
	end
	
	-- Step 1: Dump the player's current inventory on the ground, so it doesn't clash with anything we are about to put in
	for _,item in pairs(event.player.inventory) do
		if type(item) == 'Item' then
			event.player:dropItem(item, 1)
		end
	end
	event.player.inventory:clear() -- avoid item duplication, :dropItem drops clones
	
	-- Step 2: Merge in the saved inventory hiding in the invisible interaction entity
	event.player:putNbt({Inventory = event.entity.extra.deathSign.inventory})
	event.player:putNbt({equipment = event.entity.extra.deathSign.equipment})
	if config_storeXP == true then spell:executeSilent("/xp add " .. event.player.uuid .. " " .. valueOrDefault(event.entity.extra.deathSign.XpTotal,0) .. " points"); end
	spell:executeSilent("/playsound minecraft:entity.player.breath player @a ~ ~ ~ 2")
	
	-- Step 3: Tidy up
	spell.pos = event.entity.pos + Vec3(0,1,0)
	if spell.block.type.id == "player_head" then
		spell.block = Block:new("air")
		spell:executeSilent("/particle minecraft:smoke ~ ~ ~ 0.2 0.2 0.2 0 100 force")
	end
	event.entity:kill() -- Invisible entity
	
	return true
end



-- Prevent duplicate spells from running
local name = 'towelGraveSigns-spell'
spell:executeSilent("wol spell break byName " .. name)
spell.name = name


spell:intercept({"BeforeLivingEntityDeathEvent"}, onEntityDeathHandler)
spell:intercept({"PlayerUseEntityEvent"}, onEntityClickHandler)


-- Keep the spell active so spell:intercepts keep working
while true do sleep(20) end 