local S = core.get_translator("block_league")

local function rotate_and_shoot() end
local function draw_particles() end

local SENTRY_RANGE = 20
local DAMAGE = 10
local KNOCKBACK = 6



local multiplier2 = 3.6
local sentry_gun = {
  initial_properties = {
    hp_max = 100,
    physical = true,
    collide_with_objects = true,
    visual = "mesh",
    mesh = "bl_sentrygun.b3d",
    backface_culling = false,
    visual_size = {x = 1 * multiplier2, y = 1 * multiplier2, z = 1 * multiplier2},
    textures = {"bl_sentrygun_texture.png"},
    collisionbox = {-0.15 * multiplier2, -0.141 * multiplier2, -0.14 * multiplier2, 0.15 * multiplier2, 0.16 * multiplier2, 0.14 * multiplier2}
  },

  _arena = nil,
  _is_spawning = true,
  _shooting = false,
  _is_dying = false,
  _name = "",
  _owner = "",
  _teamID = nil,
  _loggable = true,
  _log_icon = "bl_sentrygun.png",
  _can_be_targeted = true,
  _no_knockback = true,
  _dmg_received = nil, -- table in on_activate so it's not shared

  on_activate = function(self, staticdata)
    if not staticdata or staticdata == "" then self.object:remove() return end

    local staticdata_tbl = core.deserialize(staticdata)
    local sentry = self.object

    self._owner = staticdata_tbl.owner
    self._teamID = staticdata_tbl.teamID
    self._arena = arena_lib.get_arena_by_player(self._owner)
    self._name = S("@1's Sentry Gun", self._owner)

    sentry:set_rotation(vector.new(0,staticdata_tbl.rot,0))
    sentry:set_animation({x=120, y=130}, 12, nil, false)
    sentry:set_velocity(vector.new(0,-14,0))
    audio_lib.play_sound("bl_sentrygun_spawn", {gain = 0.2, pitch = 1.15, object = sentry, max_hear_distance=6})

    if self._arena then
      local teamID = self._teamID
      local col = teamID == 1 and "orange" or "blue"

      sentry:set_properties({textures = {sentry:get_properties().textures[1] .. "^[colorize:" .. col .. ":85"}})

      local p_name = self._owner
      local arena = self._arena

      self._dmg_received = {}
      arena.players[p_name].entities[self._name] = sentry

      for pl_name, pl_data in pairs(arena.players) do
        -- danni alla torretta
        self._dmg_received[pl_name] = {timestamp = 99999, dmg = 0}

        for ent_name, _ in pairs(pl_data.entities) do
          self._dmg_received[pl_name .. "@" .. ent_name] = {timestamp = 99999, dmg = 0}
        end

        -- danni della torretta
        if pl_data.teamID ~= teamID then
          local sentry_id = p_name .. "@" .. self._name
          pl_data.dmg_received[sentry_id] = {timestamp = 99999, dmg = 0}

          for _, obj in pairs(pl_data.entities) do
            if obj:get_luaentity() then -- mi fido 0 senza
              obj:get_luaentity()._dmg_received[sentry_id] = {timestamp = 99999, dmg = 0}
            end
          end
        end
      end
    end

    core.after(1.5, function()
      if not sentry then return end
      sentry:set_animation({x=1, y=100}, 20)
      self._is_spawning = false
    end)
  end,



  on_step = function(self, dtime, moveresult)
    if self._is_spawning or self._shooting or (self._arena and self._arena.weapons_disabled) then return end

    local target
    local sentry = self.object
    local s_pos = sentry:get_pos() -- origine
    local dir = vector.new(-math.sin(sentry:get_yaw()), 0, math.cos(sentry:get_yaw()))
    local shooting_height, to_target_dir, angle

    -- cerca l'obiettivo
    for obj in core.objects_inside_radius(s_pos, SENTRY_RANGE) do
      local is_player = obj:is_player()
      local ent_lua = obj:get_luaentity()

      if is_player or self._owner ~= ent_lua._owner then
        -- TODO: volevo calcolare l'altezza a seconda dell'ingombro di collisione (quindi
        -- sia entità che giocanti) ma non so perché continua a crashare. Per ora evito le entità
        --shooting_height = vector.new(0, (collisionbox[5] + math.abs(collisionbox[2])) / 2, 0) -- altezza media
        shooting_height = vector.new(0, is_player and obj:get_properties().eye_height or 0, 0)

        local t_pos = vector.add(obj:get_pos(), shooting_height)
        local ray = core.raycast(s_pos, t_pos, false) -- non iniziare a sparare se ci son ostacoli nel mezzo

        to_target_dir = vector.direction(s_pos, t_pos)
        angle = math.deg(vector.angle(to_target_dir, dir))

        if math.abs(angle) <= 60 and obj:get_hp() > 0 and not ray:next() then
          local teamID = self._teamID

          if is_player then
            local t_name = obj:get_player_name()
            -- is_player_playing x non colpire chi assiste
            if t_name ~= self._owner and (not teamID or (arena_lib.is_player_playing(t_name) and teamID ~= arena_lib.get_teamID(t_name))) then
              target = obj
              break
            end
          else
            if ent_lua._can_be_targeted then
              if (not teamID or teamID ~= ent_lua._teamID) then
                target = obj
                break
              end
            end
          end
        end
      end
    end

    if not target then return end

    sentry:set_animation({x=105, y=106}, 10)
    self._shooting = true
    rotate_and_shoot(sentry, to_target_dir)   -- 1° colpo

    core.after(0.3, function()            -- 2° colpo
      -- se controllo solo obj e l'obiettivo si è sconnesso, passa il controllo
      if not target:get_pos() or not self.object or self._is_dying then return end
      to_target_dir = vector.direction(s_pos, vector.add(target:get_pos(), shooting_height))
      rotate_and_shoot(sentry, to_target_dir)

      core.after(0.3, function()          -- 3° colpo
        if not target:get_pos() or not self.object or self._is_dying then return end
        to_target_dir = vector.direction(s_pos, vector.add(target:get_pos(), shooting_height))
        rotate_and_shoot(sentry, to_target_dir)
      end)
    end)

    core.after(1, function()
      if not self.object then return end
      self.object:set_animation({x=26, y=26})

      core.after(1, function()
        if not self then return end
        sentry:set_bone_position("Rotation", vector.new(0,1,0), {x=0,y=0,z=0})
        sentry:set_animation({x=1, y=100}, 20)
        self._shooting = false
      end)
    end)
  end,



  on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
    local sentry = self.object

    if not puncher then return end

    if self._teamID and puncher:is_player() then
      local p_team = arena_lib.get_teamID(puncher:get_player_name())
      if p_team and p_team == self._teamID then return true end
    end

    if not puncher:is_player() or puncher:get_player_name() ~= self._owner then
      block_league.play_sound_to_player(self._owner, "player_damage")
    end

    if not self._is_dying and sentry:get_hp() - damage <= 0 then
      self:_kill(puncher)
      return true
    end
  end,



  _kill = function(self, killer)
    local sentry = self.object

    sentry:set_hp(1)
    sentry:set_animation({x=112, y=115}, 25, nil, false)
    sentry:set_properties({physical=false})
    self._is_dying = true

    audio_lib.play_sound("bl_sentrygun_death", {pos = sentry:get_pos()})
    core.add_particlespawner({
      attached = self.object,
      amount = 200,
      time = 0.1,
      minvel = {x = -10,y = -6, z = -10},
      maxvel = {x = 10, y = 6, z = 10},
      minsize = 1,
      maxsize = 3,
      texture = {
        name = "bl_sentrygun_explosion.png",
        alpha_tween = {1, 0},
        scale_tween = {
          {x = 1, y = 1},
          {x = 0, y = 0},
        },
        animation = {
          type = "vertical_frames",
          aspect_w = 16, aspect_h = 16,
          length = 0.3,
        },
        },
      glow = 12,
    })

    local teamID = self._teamID
    local p_name = self._owner
    local arena = self._arena

    -- se in arena, rimuovi da `entities` e da `dmg_received` di chi è in gioco.
    -- Non posso controllare solo `_teamID` perché se eseguo da on_end la gente già
    -- non è più in arena
    if arena then
      if arena.players[p_name] then
        arena.players[p_name].entities[self._name] = nil -- TEMP: in futuro usare UUID https://github.com/minetest/minetest/pull/14135
      end

      for _, pl_data in pairs(arena.players) do
        if pl_data.teamID ~= teamID then
          pl_data.dmg_received[p_name .. "@" .. self._name] = nil

          for _, ent_name in ipairs(pl_data.entities) do
            pl_data.dmg_received[p_name .. "@" .. ent_name] = nil
          end
        end
      end
    end

    core.after(0.15, function()
      sentry:punch(killer, nil, {damage_groups = { fleshy = 999}})
    end)
  end
}

core.register_entity("block_league:sentry", sentry_gun)



function rotate_and_shoot(sentry, dir)
  if not sentry:get_rotation() then return end -- se controllo solo `sentry`, crasha sotto. Prob le entità non vengono rimosse all'istante, ma le loro funzioni sì (?)

  local sentry_rot_y = sentry:get_rotation().y -- between 0 and 2pi
  local pitch = vector.dir_to_rotation(dir).x
  local yaw = vector.dir_to_rotation(dir).y

  -- convert the turret yaw rotation to range [-pi, pi] for proper comparison
  if sentry_rot_y > math.pi then
    sentry_rot_y = sentry_rot_y - 2 * math.pi
  end

  -- calculate the normalized yaw angle difference in the range [-pi, pi]
  local yaw_nrml = yaw - sentry_rot_y

  if yaw_nrml > math.pi then
    yaw_nrml = yaw_nrml - 2 * math.pi
  elseif yaw_nrml < -math.pi then
    yaw_nrml = yaw_nrml + 2 * math.pi
  end

  -- invert the yaw for the correct rotation direction
  yaw_nrml = -yaw_nrml

  local new_rot = vector.new(pitch, yaw_nrml, 0)

  sentry:set_bone_override("Rotation", {
    position = {
      vec = vector.new(0, 1, 0),
      absolute = true
    },
    rotation = {vec = new_rot, absolute=true, interpolation = 0.1}
  })

  local sentry_centre = 0.3
  local pointed_object = weapons_lib.get_pointed_objects(sentry, 20, false, {height = sentry_centre, dir = dir})

  draw_particles(dir, vector.add(sentry:get_pos(), vector.new(0, sentry_centre, 0)))
  audio_lib.play_sound("bl_sentrygun_shoot", {pitch =  2.3, object = sentry})

  if next(pointed_object) then
    local target = pointed_object[1].object
    local old_hp = target:get_hp()
    local is_target_player = target:is_player()

    if is_target_player and target:get_meta():get_int("bl_immunity") == 1 then return end

    local target_lua = target:get_luaentity()
    local sentry_lua = sentry:get_luaentity()
    local arena = sentry_lua._arena
    local owner = sentry_lua._owner

    if arena then
      local teamID = sentry_lua._teamID

      -- non spingere/danneggiare gente della stessa squadra
      if (is_target_player and arena_lib.is_player_in_same_team(arena, owner, target:get_player_name())) or
         not is_target_player and teamID == target_lua._teamID then return end

      -- se non `_loggable`, non tiene neanche traccia dei danni
      if is_target_player or (target_lua and target_lua._loggable) then
        local dmgr_name = owner .. "@" .. sentry_lua._name
        local t_name = is_target_player and target:get_player_name() or target_lua._owner .. "@" .. target_lua._name
        local dmg_table = is_target_player and arena.players[t_name].dmg_received or target_lua._dmg_received

        if dmg_table[dmgr_name] then -- senza questo, a volte crasha, immagino questione di globalstep
          dmg_table[dmgr_name] = {
            timestamp = arena.current_time,
            dmg = arena.current_time > dmg_table[dmgr_name].timestamp - 5 and dmg_table[dmgr_name].dmg + DAMAGE or DAMAGE
          }
        end

        block_league.calc_dmg_dealt_and_increase_points(arena, owner, DAMAGE)
      end

      local dead = old_hp - DAMAGE <= 0 or (target_lua and target_lua._is_dying)

      -- eventuale renderizzazione registro azioni
      if old_hp > 0 and dead and not arena.weapons_disabled then
        if is_target_player then
          block_league.entity_kill_player(arena, sentry_lua, target)
        elseif target_lua._loggable then -- tipo i muri
          block_league.entity_kill_entity(arena, sentry_lua, target_lua)
        end
      end
    end

    if is_target_player or not target_lua._no_knockback then
      target:add_velocity(vector.multiply(dir, KNOCKBACK))
    end

    target:punch(sentry, nil, {damage_groups = {fleshy = DAMAGE}})
    block_league.play_sound_to_player(owner, "bl_hit")
  end
end



function draw_particles(dir, origin)
  core.add_particlespawner({
    amount = 5,
    time = 0.1,
    pos = vector.new(origin),
    vel = vector.multiply(dir, 60),
    size = 1.5,
    collisiondetection = true,
    collision_removal = true,
    object_collision = true,
    texture = "bl_pixelgun_trail.png"
  })
end