local function random_2dvectors() end
local last_pls_y_velocity = {} -- pl_name = y_velocity
local get_node = minetest.get_node
local get_pl_by_name = minetest.get_player_by_name

local T = fbrawl.T



function fbrawl.replace_slot_item(player, slot, itemname, amount) 
   local inv = player:get_inventory()
   local list = inv:get_list("main")
   local itemstack = ItemStack(itemname)
   itemstack:set_count(amount or 1)
   list[slot] = itemstack

   inv:set_list("main", list)
end



function fbrawl.hit_player(puncher, hit_pl, damage, knockback) 
   hit_pl:punch(puncher, 2, {damage_groups = {fleshy = damage*20}})

   if knockback then
      hit_pl:add_velocity(knockback)
   end
end



function fbrawl.is_on_the_ground(player)
   local pl_name = player:get_player_name()
   local under_pl_feet = player:get_pos()
   local pl_velocity = player:get_velocity()
   local last_y_velocity = last_pls_y_velocity[pl_name] or pl_velocity.y

   under_pl_feet.y = under_pl_feet.y - 0.4
   
   local is_on_the_ground = 
      not (get_node(under_pl_feet).name == "air")
      or (pl_velocity.y == 0 and last_y_velocity < 0)
   
   last_pls_y_velocity[pl_name] = pl_velocity.y
   
   return is_on_the_ground
end



function fbrawl.area_raycast_up(center, radius, ray_length, objects, liquids)
   local ray_distance = 0.5
   local radius_x, radius_z

   if type(radius) == "number" then
      radius_x = radius
      radius_z = radius
   else
      radius_x = radius.x
      radius_z = radius.z
   end
   
   local start_pos = {x = center.x - radius_x, y = center.y + 0.5, z = center.z - radius_z}
   local final_pos = {x = center.x + radius_x, y = center.y + 0.5, z = center.z + radius_z}
   
   for x = start_pos.x, final_pos.x, ray_distance do
      for z = start_pos.z, final_pos.z, ray_distance do
         local pos1 = {x = x, y = start_pos.y, z = z}
         local pos2 = vector.add(pos1, {x = 0, y = ray_length, z = 0})
         local result = minetest.raycast(pos1, pos2, objects, liquids):next()
         
         if result then 
            return result 
         end
      end
   end

   return false
end



-- TODO: improve
function fbrawl.camera_shake(pl_name, duration, strength, offsets, next_offset, eye_offset, last)
   local player = get_pl_by_name(pl_name)
   local speed = 0.4
   local last = last or false
   offsets = offsets or random_2dvectors(duration, strength)
   next_offset = next_offset or 1

   if not player then
      return
   end
   
   -- When offsets finish go back to zero
   if next_offset > #offsets then
      offsets[next_offset] = {x = 0, y = 0}
      last = true
   end
   
   eye_offset = eye_offset or player:get_eye_offset()
   local target_offset = offsets[next_offset]
   local new_offset = {
      x = fbrawl.interpolate(eye_offset.x, target_offset.x, speed),
      y = fbrawl.interpolate(eye_offset.y, target_offset.y, speed),
      z = 0
   }
   eye_offset = new_offset

   -- If new_offset and eye_offset are equal go to the next offset or return if it was
   -- the last offset
   if target_offset.x == eye_offset.x and target_offset.y == eye_offset.y then
      if last then 
         return 
      end
      next_offset = next_offset + 1
   else
      player:set_eye_offset(new_offset, new_offset)
   end

   minetest.after(0, function() 
      --fbrawl.camera_shake(pl_name, duration, strength, offsets, next_offset, eye_offset, last)
   end)
end



function fbrawl.reset_velocity(player)
   player:add_velocity(vector.multiply(player:get_velocity(), -1))
end



function fbrawl.interpolate(a, b, factor)
   local distance = math.abs(a-b)
   local min_step = 0.1
   local step = distance * factor
   if step < min_step then step = min_step end

   if a > b then
      a = a - step
      if a <= b then
         a = b
      end
   else
      a = a + step
      if a >= b then
         a = b
      end
   end

   return a
end



function fbrawl.vec_interpolate(a, b, factor)
   local i = fbrawl.interpolate
   local f = factor
   local interpolated_vec = {x=i(a.x, b.x, f), y=i(a.y, b.y, f), z=i(a.z, b.z, f)}

   return interpolated_vec
end



function fbrawl.damage_players_near(puncher, pos, range, damage, knockback, callback)
   -- todo: iterare giocatori in arena
   local arena = arena_lib.get_arena_by_player(puncher:get_player_name())

   if not arena then return false end

   if type(range) == "number" then range = vector.new(range, range, range) end

   for pl_name, props in pairs(arena.players) do
      local hit_pl = get_pl_by_name(pl_name)
      local hit_pl_pos = vector.add({x=0, y=1, z=0}, hit_pl:get_pos())

      local is_close_enough = true
      if
         math.abs(pos.x - hit_pl_pos.x) > range.x
         or math.abs(pos.y - hit_pl_pos.y) > range.y
         or math.abs(pos.z - hit_pl_pos.z) > range.z
      then
         is_close_enough = false
      end

      if hit_pl == puncher then is_close_enough = false end

      if is_close_enough then
         fbrawl.hit_player(puncher, hit_pl, damage, knockback)

         if callback then
            callback(hit_pl:get_player_name())
         end
      end
   end
end



local calculate_knockback = minetest.calculate_knockback
function minetest.calculate_knockback(player, hitter, time_from_last_punch, tool_capabilities, dir, distance, damage)
   local mod = arena_lib.get_mod_by_player(player:get_player_name())

   if mod == "fantasy_brawl" then return 0 end

   return calculate_knockback(player, hitter, time_from_last_punch,
   tool_capabilities, dir, distance, damage)
end



function random_2dvectors(amount, strength)
   local rnd = PcgRandom(os.time())
   local vectors = {}

   for i = 1, amount, 1 do
      local rnd_vector = {
         x = rnd:next(-1, 1) * strength,
         y = rnd:next(-1, 1) * strength, 
      }

      table.insert(vectors, rnd_vector)
   end

   return vectors
end



function fbrawl.pl_look_at(player, target)
	local pos = player:get_pos()
	local delta = vector.subtract(target, pos)
	player:set_look_horizontal(math.atan2(delta.z, delta.x) - math.pi / 2)
end



function fbrawl.can_cast_ultimate(pl_name)
   local arena = arena_lib.get_arena_by_player(pl_name)
   return arena.players[pl_name].ultimate_recharge >= fbrawl.min_kills_to_use_ultimate
end



function fbrawl.cast_ultimate(pl_name, skill_name, args)
   local skill = pl_name:get_skill(skill_name)
   if not skill then return false end

   local arena = arena_lib.get_arena_by_player(pl_name)
   local props = arena.players[pl_name]

   if not fbrawl.can_cast_ultimate(pl_name) then return end

   props.ultimate_recharge = 0

   if skill.loop_params then
      pl_name:start_skill(skill_name, args)
   else
      pl_name:cast_skill(skill_name, args)
   end
end



function fbrawl.are_there_nodes_in_area(pos, range)
   range = range - 0.5
   local get_node = minetest.get_node
   local min_edge = vector.subtract(pos, range)
   local max_edge = vector.add(pos, range)
   local area = VoxelArea:new({MinEdge = min_edge, MaxEdge = max_edge})

   for i in area:iterp(min_edge, max_edge) do
      local pos = area:position(i)
      local node_name = get_node(pos).name 

      if node_name ~= "ignore" and node_name ~= "air" then return true end
   end

   return false
end



function fbrawl.get_time_in_seconds()
   return minetest.get_us_time() / 1000000
end



function fbrawl.look_raycast(object, range)
   local pos = {}
   local looking_dir = 0
   local shoot_dir = 0

   -- Assigning the correct values to pos and looking_dir, based on
   -- if the object is a player or not.
   if object:is_player() then
       local pl_pos = object:get_pos()
       local head_pos = {x = pl_pos.x, y = pl_pos.y+1.5, z = pl_pos.z}
       pos = head_pos
       looking_dir = object:get_look_dir()
   else
       pos = object:get_pos()
       looking_dir = vector.normalize(object:get_velocity())
   end
   
   shoot_dir = vector.multiply(looking_dir, range)

   -- Casts a ray from pos to the object looking direction * range.
   local ray = minetest.raycast(
      vector.add(pos, vector.divide(looking_dir, 4)), 
      vector.add(pos, shoot_dir), 
      false, 
      false
   )

   return ray, pos, shoot_dir
end
