local S = core.get_translator("block_league")

local function calc_xp() end
local function recalculate_lv() end

-- `_LV` per tenere traccia più facilmente. Non modificabile esternamente, onde evitare sorprese
-- `stored` è l'exp da riversare nel prossimo livello una volta completata la missione
local xp = {}             -- KEY p_name, INDEX {(int) _LV, w_name1 = {lv, xp, stored}, w_name = {lv, xp, stored}, ...}
local XP_REQUIRED_PER_LV = block_league.XP_REQUIRED_PER_LV
local MAX_WEAP_LV = #XP_REQUIRED_PER_LV
local WEAP_SUPPORTING_EXP = {
  ['block_league:peacifier'] = true,
  ['block_league:pixelgun'] = true,
  ['block_league:sentry_gun'] = true,
  ['block_league:smg'] = true,
  ['block_league:sword'] = true,
  ['block_league:bomb'] = true,
}



-- non esporre
function block_league.load_xp(p_name, xp_table)
  xp[p_name] = xp_table
end



-- non esporre
function block_league.unload_xp(p_name)
  xp[p_name] = nil
end



-- non esporre
function block_league.give_and_show_xp(ar_mode, stats, p_name, time_spent_playing, avg_players_amount, end_points)
  -- salta il tutorial
  if ar_mode == 0 then return end

  local first_license = false
  local maxed = 0
  local lv_up_data -- in caso di più salite di livello, voglio mostrare subito solo la prima, inviando le info da concatenare tramite la gestione campi della 1° aperta

  if xp[p_name]._LV == 0 then
    first_license = true

    for w_name, _ in pairs(WEAP_SUPPORTING_EXP) do
      if block_league.get_xp_required_for_lv_up(p_name, w_name) == 0 then
        first_license = false
        break
      end
    end
  end

  -- da passare alla GUI postpartita
  local weaps_xp_data = {}

  for i, w_name in ipairs(block_league.get_equipped_weapons(p_name, true)) do
    if block_league.is_weapon_exp_enabled(w_name) then
      local required_xp = block_league.get_xp_required_for_lv_up(p_name, w_name)
      weaps_xp_data[i] = {prev = 0, earned = 0}

      -- se non ha superato il limite settimanale
      if not block_league.has_exceeded_weekly_time(p_name) then
        -- se la barra non è piena, aggiungi esperienza
        if required_xp ~= 0 then
          local prev_xp = block_league.get_xp(p_name, w_name)
          local xp_earned = calc_xp(w_name, stats, time_spent_playing, avg_players_amount, end_points)
          block_league.add_xp(p_name, w_name, xp_earned)
          weaps_xp_data[i] = {prev = prev_xp, earned = xp_earned}

          if block_league.get_xp_required_for_lv_up(p_name, w_name) == 0 then
            maxed = maxed + 1
          end

        -- se ha giocato fino in fondo, la barra è piena e la licenza è completata, sali di livello
        elseif end_points ~= nil and block_league.can_get_licence(p_name, w_name) and block_league.get_licence_progress(p_name, w_name).completed then
          local next_lv = block_league.get_weapon_lv(p_name, w_name) + 1
          local licence = block_league.get_licence_info(w_name, next_lv)
          local reward = licence.reward
          local stored = block_league.get_xp_stored(p_name, w_name)
          local xp_earned = calc_xp(w_name, stats, time_spent_playing, avg_players_amount, end_points)

          -- sali di livello
          block_league.set_level(p_name, w_name, next_lv)
          weaps_xp_data[i] = {prev = stored, earned = xp_earned}

          -- dài eventuale ricompensa
          if reward then
            if reward.type == "SKIN" then
              block_league.unlock_weapon_variant(p_name, w_name, reward.item)
            end
          end

          -- memorizza dati per mostrare la salita di livello
          if not lv_up_data then
            lv_up_data = {w_name = w_name, new_lv = block_league.get_player_level(p_name), next_levels = {}}
          else
            lv_up_data.next_levels[#lv_up_data.next_levels+1] = {w_name = w_name, new_lv = block_league.get_player_level(p_name)}
          end

        -- se non è riuscitə a completare la licenza
        elseif block_league.can_get_licence(p_name, w_name) and not block_league.get_licence_progress(p_name, w_name).completed then
          weaps_xp_data[i] = {prev = block_league.get_xp(p_name, w_name), earned = 0}
        end
      end
    end
  end

  local show_licence_guide = first_license and maxed > 0

  block_league.gui_show_xp_earned(p_name, weaps_xp_data, lv_up_data, show_licence_guide)
end





----------------------------------------------
--------------------UTILS---------------------
----------------------------------------------

function block_league.is_weapon_exp_enabled(w_name)
  return WEAP_SUPPORTING_EXP[w_name]
end



function block_league.add_xp(p_name, w_name, amount)
  if not weapons_lib.is_weapon(w_name) then
    return false, S("@1 is not a weapon!", w_name)
  end

  block_league.try_to_load_player(p_name)

  if not xp[p_name] then
    return false, S("There is no player in memory called @1!", p_name)
  end

  local w_xp = xp[p_name][w_name]
  local lv = w_xp.lv

  if lv == MAX_WEAP_LV then return end

  amount = math.floor(amount)

  local new_xp = w_xp.xp + amount
  local required = XP_REQUIRED_PER_LV[lv+1]

  if new_xp >= required then
    w_xp.xp = required
    w_xp.stored = new_xp - required
  else
    w_xp.xp = w_xp.xp + amount
  end

  block_league.print_debug("Weapon " .. w_name .. " of player " .. p_name .. ": +" .. amount .. "xp")
  block_league.update_storage(p_name, "xp", xp[p_name])
end



function block_league.flush_xp(p_name)
  if not block_league.DEBUG_MODE then
    return false, S("Debug mode is required to perform this action!")
  end

  block_league.try_to_load_player(p_name)

  if not xp[p_name] then
    return false, S("There is no player in memory called @1!", p_name)
  end

  xp[p_name] = {_LV = 0}
  for k, _ in pairs(block_league.get_weapons_supporting_xp()) do
    xp[p_name][k] = {lv = 0, xp = 0, stored = 0}
  end

  block_league.update_storage(p_name, "xp", xp[p_name])

  return true, S("Xp of @1 completely flushed", p_name)
end





----------------------------------------------
-----------------GETTERS----------------------
----------------------------------------------

function block_league.get_player_level(p_name)
  block_league.try_to_load_player(p_name)

  if not xp[p_name] then return end

  return xp[p_name]._LV
end



function block_league.get_weapon_lv(p_name, w_name)
  block_league.try_to_load_player(p_name)

  if not xp[p_name] or not xp[p_name][w_name] then return end

  return xp[p_name][w_name].lv
end



function block_league.get_weapons_max_lv()
  return MAX_WEAP_LV
end



function block_league.get_xp(p_name, w_name)
  block_league.try_to_load_player(p_name)

  if not xp[p_name] or not xp[p_name][w_name] then return end

  return xp[p_name][w_name].xp
end



function block_league.get_xp_stored(p_name, w_name)
  block_league.try_to_load_player(p_name)

  if not xp[p_name] or not xp[p_name][w_name] then return end

  return xp[p_name][w_name].stored
end



function block_league.get_xp_required_for_lv_up(p_name, w_name)
  block_league.try_to_load_player(p_name)

  if not xp[p_name] or not xp[p_name][w_name] then return end

  local data = xp[p_name][w_name]
  local full = XP_REQUIRED_PER_LV[data.lv+1] or data.xp -- se è al livello massimo, ritorno 0

  return full - data.xp
end



function block_league.get_weapons_supporting_xp()
  return table.copy(WEAP_SUPPORTING_EXP)
end





----------------------------------------------
-----------------SETTERS----------------------
----------------------------------------------

function block_league.set_xp(p_name, w_name, amount)
  if not weapons_lib.is_weapon(w_name) then
    return false, S("@1 is not a weapon!", w_name)
  end

  block_league.try_to_load_player(p_name)

  if not xp[p_name] then
    return false, S("There is no player in memory called @1!", p_name)
  end

  -- non andare oltre l'exp richiesta dal livello
  amount = math.min(amount, XP_REQUIRED_PER_LV[xp[p_name][w_name].lv+1])

  xp[p_name][w_name].xp = amount
  block_league.update_storage(p_name, "xp", xp[p_name])

  return true, S("Exp of @1's weapon @2 set to @3", p_name, w_name, amount)
end



function block_league.set_level(p_name, w_name, w_lv)
  if not weapons_lib.is_weapon(w_name) then
    return false, S("@1 is not a weapon!", w_name)
  end

  block_league.try_to_load_player(p_name)

  if not xp[p_name] then
    return false, S("There is no player in memory called @1!", p_name)
  end

  if w_lv < 0 or w_lv > MAX_WEAP_LV then
    return false, S("This level doesn't exist!")
  end

  local w_data = xp[p_name][w_name]

  w_data.lv = w_lv
  w_data.xp = w_data.stored or 0  -- tengo `or 0` per quando la funzione viene lanciata dai comandi
  w_data.stored = 0

  recalculate_lv(p_name)

  for _, callback in ipairs(block_league.registered_on_levelup) do
    callback(p_name, w_name, w_lv, xp[p_name]._LV)
  end

  block_league.update_storage(p_name, "xp", xp[p_name])

  return true, S("Level of @1's weapon @2 set to @3", p_name, w_name, w_lv)
end





----------------------------------------------
---------------FUNZIONI LOCALI----------------
----------------------------------------------

-- formula xp: ((danni inflitti / 50) + uccisioni + (minuti * 1.5) + esito [0/5/10]) * fino alla fine * moltip. giocanti
-- TODO: se metterò armi che curano, al posto di kills avere una variante apposita
function calc_xp(w_name, stats, leaving_time, avg_players_amount, end_points)
  local time = (stats.entering_time - leaving_time) / 60
  local end_multiplier = end_points ~= nil and 1 or 0.75
  local players_multiplier = avg_players_amount < 4 and 0.5 or 1
  local w_xp_multiplier = core.registered_nodes[w_name]._xp_multiplier or 1
  local points_by_dmg = stats.dmg_dealt[w_name].hp / 50

  -- meno di 30 secondi o meno di 4 punti = niente punti
  if time <= 0.5 or stats.points < 4 then return 0 end

  block_league.print_debug(string.format("xp earned %s = (%.2f + %d + %.2f + %d) * %.2f * %.1f * %.2f",
      w_name, points_by_dmg, stats.dmg_dealt[w_name].kills, time * 1.5, end_points or 0, end_multiplier, players_multiplier, w_xp_multiplier))

  return math.floor(((points_by_dmg + stats.dmg_dealt[w_name].kills + (time * 1.5) + (end_points or 0)) * end_multiplier * players_multiplier * w_xp_multiplier) + 0.5) -- +0.5 per il floor
end



function recalculate_lv(p_name)
  local lv = 0
  for ww_name, _ in pairs(WEAP_SUPPORTING_EXP) do
    lv = lv + xp[p_name][ww_name].lv
  end

  xp[p_name]._LV = lv
end