-- vim: set foldmethod=marker :
--- Initial definitions ---
-- Texture dimensions --
TEXTURE_WIDTH = 128
TEXTURE_HEIGHT = 128

-- local state variables (do not access within pings) --
armor_enabled=data.load("armor_enabled")
if armor_enabled==nil then
	armor_enabled=true
else
	armor_enabled=armor_enabled=="true"
end
vanilla_enabled=data.load("vanilla_enabled")
if vanilla_enabled==nil then
	vanilla_enabled=false
else
	vanilla_enabled=vanilla_enabled=="true"
end
snore_enabled=data.load("snore_enabled")
if snore_enabled==nil then
	snore_enabled=false
else
	snore_enabled=snore_enabled=="true"
end

-- utility functions -- {{{
--- dump table --
function dumpTable(o)
   if type(o) == 'table' then
      local s = '{ '
      for k,v in pairs(o) do
         if type(k) ~= 'number' then k = '"'..k..'"' end
         s = s .. '['..k..'] = ' .. dumpTable(v) .. ','
      end
      return s .. '} '
   else
      return tostring(o)
   end
end

function UV(uv)
	return vectors.of({
	uv[1]/TEXTURE_WIDTH,
	uv[2]/TEXTURE_HEIGHT
	})
end


function splitstring (inputstr, sep)
        if sep == nil then
                sep = "%s"
        end
        local t={}
        for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
                table.insert(t, str)
        end
        return t
end

function unstring(input)
	if input=="nil" then
		return nil
	elseif input == "true" or input == "false" then
		return input=="true"
	elseif number(input) ~= nil then
		return number(input)
	else
		return input
	end
end

function map(func, table)
	local t={}
	for k, v in pairs(table) do
		t[k]=func(v)
	end
	return t
end

function filter(func, table)
	local t={}
	for k, v in pairs(table) do
		if func(v) then
			t[k]=v
		end
		return t
	end
end
-- }}}

-- local state variables (do not access within pings) --
-- armor_enabled=data.load("armor_enabled")
-- if armor_enabled==nil then
-- 	armor_enabled=true
-- else
-- 	armor_enabled=armor_enabled=="true"
-- end
-- vanilla_enabled=data.load("vanilla_enabled")
-- if vanilla_enabled==nil then
-- 	vanilla_enabled=false
-- else
-- 	vanilla_enabled=vanilla_enabled=="true"
-- end
-- snore_enabled=data.load("snore_enabled")
-- if snore_enabled==nil then
-- 	snore_enabled=false
-- else
-- 	snore_enabled=snore_enabled=="true"
-- end
state={}


-- Parts --
HEAD=model.Head.Head
VANILLA_OUTER={ vanilla_model.HAT, vanilla_model.JACKET, vanilla_model.LEFT_SLEEVE, vanilla_model.RIGHT_SLEEVE, vanilla_model.LEFT_PANTS_LEG, vanilla_model.RIGHT_PANTS_LEG }
VANILLA_INNER={
    vanilla_model.HEAD,
    vanilla_model.TORSO,
    vanilla_model.LEFT_ARM,
    vanilla_model.RIGHT_ARM,
    vanilla_model.LEFT_LEG,
    vanilla_model.RIGHT_LEG
}
VANILLA_ALL={}
for _, v in pairs(VANILLA_INNER) do table.insert(VANILLA_ALL,v) end
for _, v in pairs(VANILLA_OUTER) do table.insert(VANILLA_ALL,v) end

SNORES={"snore-1", "snore-2", "snore-3"}
snore_index=1

-- Expression change -- {{{
do
	-- Values for UV mappings --
	expr_current={damage=0, expression=0}
	local expr_step={u=32, v=16}
	local expr_offset={u=64, v=0}

	local function getExprUV(damage, expression)
		local u=expr_offset.u+(damage*expr_step.u)
		local v=expr_offset.v+(expression*expr_step.v)
		return UV{u, v}
	end
	function changeExpression(_damage, _expression, ticks)
		-- u is damage, v is expression
		local damage = _damage
		local expression = _expression
		if damage == nil then
			damage = expr_current.damage
		end
		if expression == nil then
			expression = expr_current.expression
		end

		HEAD.setUV(getExprUV(damage,expression))
		namedWait(ticks, resetExpression, "resetExpression")
	end
	function setExpression(damage, expression)
		expr_current.damage=damage
		expr_current.expression=expression
		HEAD.setUV(getExprUV(damage, expression))
	end
	function resetExpression()
		HEAD.setUV(getExprUV(expr_current.damage,expr_current.expression))
	end
end
-- }}}

-- Action Wheel & Pings -- {{{
action_wheel.SLOT_1.setTitle('test expression')
action_wheel.SLOT_1.setFunction(function() ping.expressionTest() end)
function ping.expressionTest()
	setExpression(1,0)
	changeExpression(nil, 1, 10)
end
action_wheel.SLOT_2.setTitle('log health')
action_wheel.SLOT_2.setFunction(function() print(player.getHealth()) end)
action_wheel.SLOT_3.setTitle('Toggle Armor')
action_wheel.SLOT_3.setFunction(function() setArmor() end)

-- Pings --
--- Damage function --
function ping.oof(health) -- This is a replacement for onDamage, that function doesn't sync for some reason
	if health <= 5 then
		setExpression(1,0)
	end
	changeExpression(nil,1,10)
end
--- Heal function (revert expression) --
function ping.healed(health)
	setExpression(0,0)
end

--- Toggle Armor ---
function setArmor(state)
	if state == nil then
		armor_enabled=not armor_enabled
	else
		armor_enabled=state
	end
	data.save("armor_enabled", armor_enabled)
	ping.setArmor(armor_enabled)
end
function ping.setArmor(state)
	for key, value in pairs(armor_model) do
		value.setEnabled(state)
	end
end

function snore()
	if snore_enabled then
		sound.playCustomSound(SNORES[snore_index], player.getPos(), vectors.of{20,1})
		snore_index=snore_index%#SNORES+1
	end
end

function setSnoring(state)
	if state == nil then
		snore_enabled=not snore_enabled
	else
		snore_enabled=state
	end
	data.save("snore_enabled", snore_enabled)
end

function ping.setSnoring(state)
	snore_enabled=state
end

--- Toggle Vanilla ---
function setVanilla(state)
	if state == nil then
		vanilla_enabled=not vanilla_enabled
	else
		vanilla_enabled=state
	end
	data.save("vanilla_enabled", vanilla_enabled)
	ping.setVanilla(vanilla_enabled)
end

function ping.setVanilla(state)
	if not meta.getCanModifyVanilla() then return end
	for _, v in pairs(VANILLA_ALL) do
		v.setEnabled(state)
	end
	for _, v in pairs(model) do
		v.setEnabled(not state)
	end
end

function syncState()
	ping.setArmor(armor_enabled)
	ping.setVanilla(vanilla_enabled)
	ping.setSnoring(snore_enabled)
end
-- }}}

-- Timer (not mine lol) -- {{{
do
	local timers = {}
	function wait(ticks,next)
		table.insert(timers, {t=world.getTime()+ticks,n=next})
	end
	function tick()
		for key,timer in pairs(timers) do
			if world.getTime() >= timer.t then
				timer.n()
				table.remove(timers,key)
			end
		end
	end
end

-- named timers (this one is mine but heavily based on the other) --
-- if timer is armed twice before expiring it will only be called once) --
do
	local timers = {}
	function namedWait(ticks, next, name)
		-- main difference, this will overwrite an existing timer with
		-- the same name
		timers[name]={t=world.getTime()+ticks,n=next}
	end
	function tick()
		for key, timer in pairs(timers) do
			if world.getTime() >= timer.t then
				timer.n()
				timers[key]=nil
			end
		end
	end
end

-- named cooldowns
do
	local timers={}
	function cooldown(ticks, name)
		if timers[name] == nil then
			timers[name]={t=world.getTime()+ticks}
			return true
		end
		return false
	end
	function tick()
		for key, timer in pairs(timers) do
			if world.getTime() >= timer.t then
				timers[key]=nil
			end
		end
	end
end

-- }}}



-- initialize values --
function player_init()
	old_health=player.getHealth()
	syncState()
end
-- Initial configuration --
if meta.getCanModifyVanilla() then
	for key, value in pairs(vanilla_model) do
		value.setEnabled(false)
	end
else
	for _, v in pairs(model) do
		v.setEnabled(false)
	end
end
vanilla_model.CAPE.setEnabled(true)


-- Tick function --
function tick()
	-- optimization, only execute these once a second --
	if world.getTimeOfDay() % 20 == 0 then
		-- if face is cracked
		if expr_current.damage==1 and player.getHealth() > 5 then
			ping.healed()
		end

		if player.getAnimation() == "SLEEPING" then
			if cooldown(20*4, "snore") then
				snore()
			end
		end

		-- Sync state every 10 seconds
		if world.getTimeOfDay() % (20*10) == 0 then
			syncState()
		end
	end

	-- Damage ping (onDamage doesn't work in multiplayer) --
	if old_health>player.getHealth() then
		-- debug
		-- print(string.format('old_health=%03.2f, player.getHealth=%03.2f', old_health,player.getHealth()))
		ping.oof(player.getHealth())
	end

	-- End of tick --
	old_health=player.getHealth()
end

-- Enable commands --
chat_prefix="$"
chat.setFiguraCommandPrefix(chat_prefix)
function onCommand(input)
	input=splitstring(input)
	if input[1] == chat_prefix .. "vanilla" then
		setVanilla()
		print("Vanilla skin is now " .. (vanilla_enabled and "enabled" or "disabled"))
	end
	if input[1] == chat_prefix .. "toggle_custom" then
		for key, value in pairs(model) do
			value.setEnabled(not value.getEnabled())
		end
	end
	if input[1] == chat_prefix .. "toggle_outer" then
		for k, v in pairs(VANILLA_OUTER) do
			v.setEnabled(not v.getEnabled())
		end
	end
	if input[1] == chat_prefix .. "toggle_inner" then
		for k, v in pairs(VANILLA_INNER) do
			v.setEnabled(not v.getEnabled())
		end
	end
	if input[1] == chat_prefix .. "test_expression" then
		setExpression(input[2], input[3])
		print(input[2] .. " " .. input[3])
	end
	if input[1] == chat_prefix .. "snore" then
		if input[2] == "toggle" or #input==1 then
			setSnoring()
			log("Snoring is now " .. (snore_enabled and "enabled" or "disabled"))
		end
	end
	if input[1] == chat_prefix .. "armor" then
		setArmor()
		log("Armor is now " .. (armor_enabled and "enabled" or "disabled"))
	end
end