gamer = {}
gamer.player_attached = {}
gamer.registered_player_models = {}

local file = io.open(minetest.get_worldpath() .. '/skins', 'r')
if file then
   gamer.player_textures = minetest.deserialize(file:read('*a'))
   file:close()
else
   gamer.player_textures = {}
end

function gamer.save_skins()
   local file = io.open(minetest.get_worldpath() .. '/skins', 'w')
   file:write(minetest.serialize(gamer.player_textures))
   file:close()
end

minetest.register_item(':', {
   type = 'none',
   wield_image = 'wieldhand.png',
   wield_scale = {x=1,y=1,z=2.5},
   range = 10,
})

-- Local for speed.
local models = gamer.registered_player_models

function gamer.player_register_model(name, def)
   models[name] = def
end

-- Default player appearance
gamer.player_register_model('gamer_model.b3d', {
   animation_speed = 30,
   textures = {'gamer_skin_default.png', 'blank.png', 'blank.png', 'blank.png'},
   animations = {
      -- Standard animations.
      stand     = { x=  0, y= 79, },
      lay       = { x=162, y=166, },
      walk      = { x=168, y=187, },
      mine      = { x=189, y=198, },
      walk_mine = { x=200, y=219, },
      sit       = { x= 81, y=160, },
   },
   collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3},
   stepheight = 0.6,
   eye_height = 1.47,
   collide_with_objects = true
})

-- Player stats and animations
local player_model = {}
local player_anim = {}
local player_sneak = {}

function gamer.player_get_animation(player)
   local name = player:get_player_name()
   return {
      model = player_model[name],
      textures = gamer.player_textures[name],
      animation = player_anim[name],
   }
end

function gamer.player_set_model(player, model_name)
   local name = player:get_player_name()
   local model = models[model_name]
   if model then
      if player_model[name] == model_name then
         return
      end
      player:set_properties({
         mesh = model_name,
         textures = {gamer.player_textures[name]} or {'gamer_skin_default.png'},
         visual = 'mesh',
         visual_size = {x=10, y=10},
      })
      gamer.player_set_animation(player, 'stand')
   else
      player:set_properties({
         textures = {'gamer_skin_default.png'},
         visual = 'upright_sprite',
      })
   end
   player_model[name] = model_name
end

--[[function gamer.player_set_textures(player, texture)
   local name = player:get_player_name()
   player:set_properties({textures = texture})
end
]]

function gamer.player_set_animation(player, anim_name, speed)
   local name = player:get_player_name()
   if player_anim[name] == anim_name then
      return
   end
   local model = player_model[name] and models[player_model[name]]
   if not (model and model.animations[anim_name]) then
      return
   end
   local anim = model.animations[anim_name]
   player_anim[name] = anim_name
   player:set_animation(anim, speed or model.animation_speed)
end

function gamer.player_update_clothes(player, layer_1, layer_2, layer_3)
   local name = player:get_player_name()
   local skin = gamer.player_textures[name]
   if skin then
      player:set_properties({
         textures = {skin, layer_1, layer_2, layer_3},
         visual = 'mesh',
         visual_size = {x=10, y=10},
         mesh = 'gamer_model.b3d',
      })
      if layer_1 == 'blank.png' and layer_2 == 'blank.png' and layer_3 == 'blank.png' then
         minetest.chat_send_player(name, 'Seriously, put some clothes on!')
         player:set_properties({
            textures = {'gamer_punisher.png'},
            visual = 'upright_sprite',
            visual_size = {x=10, y=10},
         })
      end
   else
      minetest.chat_send_player(name, 'Set a skin with the /skin command.')
      player:set_properties({
         visual = 'mesh',
         mesh = 'gamer_model.b3d',
         textures = {'gamer_skin_default.png'},
         visual_size = {x=10, y=10},
      })
      gamer.player_set_animation(player, 'stand')
   end
end

function gamer.save_clothes(player, inv)
   local player_attributes = player:get_meta()
   local clothes = {}
   for i = 1,18 do
      local stack = inv:get_stack('slots', i)
      if not stack:is_empty() then
         clothes[i] = stack:to_string()
      end
   end
   player_attributes:set_string('clothing_inv', minetest.serialize(clothes))
end

function gamer.retrieve_clothes(player)
   local name = player:get_player_name()
   local player_attributes = player:get_meta()
   local clothing_inv = player_attributes:get_string('clothing_inv')
   local clothing = minetest.deserialize(clothing_inv) or {}
   local inv = minetest.get_inventory({type = 'detached', name = name..'_clothing'})
   for i = 1,18 do
      inv:set_stack('slots', i, clothing[i] or "")
   end
end

function gamer.get_clothes(player)
   local name = player:get_player_name()
   local inv = minetest.get_inventory({type = 'detached', name = name..'_clothing'})
   local tex1 = {}
   local tex2 = {}
   local tex3 = {}
   for i = 1,6 do
      local item = inv:get_stack('slots', i)
      local def = item:get_definition() or {}
      if def.tex then
         tex1[#tex1+1] = def.tex
      end
   end
   for i = 7,12 do
      local item = inv:get_stack('slots', i)
      local def = item:get_definition() or {}
      if def.tex then
         tex2[#tex2+1] = def.tex
      end
   end
   for i = 13,18 do
      local item = inv:get_stack('slots', i)
      local def = item:get_definition() or {}
      if def.tex then
         tex3[#tex3+1] = def.tex
      end
   end
   local string1 = table.concat(tex1, '^')
   local string2 = table.concat(tex2, '^')
   local string3 = table.concat(tex3, '^')
   if string1 == '' then
      string1 = 'blank.png'
   end
   if string2 == '' then
      string2 = 'blank.png'
   end
   if string3 == '' then
      string3 = 'blank.png'
   end
   gamer.player_update_clothes(player, string1, string2, string3)
   gamer.save_clothes(player, inv)
end

-- Localize for better performance.
local player_set_animation = gamer.player_set_animation
local player_attached = gamer.player_attached

-- Check each player and apply animations
minetest.register_globalstep(function(dtime)
   for _, player in pairs(minetest.get_connected_players()) do
      local name = player:get_player_name()
      local model_name = player_model[name]
      local model = model_name and models[model_name]
      if model and not player_attached[name] then
         local controls = player:get_player_control()
         local walking = false
         local animation_speed_mod = model.animation_speed or 30

         -- Determine if the player is walking
         if controls.up or controls.down or controls.left or controls.right then
            walking = true
         end

         -- Determine if the player is sneaking, and reduce animation speed if so
         if controls.sneak then
            animation_speed_mod = animation_speed_mod / 2
         end

         -- Apply animations based on what the player is doing
         if walking then
            if player_sneak[name] ~= controls.sneak then
               player_anim[name] = nil
               player_sneak[name] = controls.sneak
            end
            if controls.LMB then
               player_set_animation(player, 'walk_mine', animation_speed_mod)
            else
               player_set_animation(player, 'walk', animation_speed_mod)
            end
         elseif controls.LMB then
            player_set_animation(player, 'mine')
         else
            player_set_animation(player, 'stand', animation_speed_mod)
         end
      end
   end
end)

minetest.register_on_leaveplayer(function(player)
   local name = player:get_player_name()
   player_model[name] = nil
   player_anim[name] = nil
   gamer.save_skins()
end)

local function player_heal()
   for _, player in pairs(minetest.get_connected_players()) do
      local hp = player:get_hp()
      local breath = player:get_breath()
      local max_hp = player:get_properties().hp_max
      if hp > 0 and hp < max_hp and breath > 0 then
         player:set_hp(hp + 1)
      end
   end
   minetest.after(3.0, player_heal)
end
minetest.after(5.0, player_heal)

dofile(minetest.get_modpath('gamer')..'/player_callbacks.lua')
dofile(minetest.get_modpath('gamer')..'/change_skin.lua')
