local Event = require 'utils.event'
local Global = require 'utils.global'
local SpamProtection = require 'utils.spam_protection'
local BottomFrame = require 'utils.gui.bottom_frame'
local Gui = require 'utils.gui'
local Task = require 'utils.task_token'

local melee_mode_name = Gui.uid_name()

local state = {}
Global.register(state, function (s) state = s end)

local delay_add_inner_frame_token =
    Task.register(
       function (event)
          local player_index = event.player_index
          local player = game.get_player(player_index)
          if not player or not player.valid then
             return
          end

          local activate_custom_buttons = BottomFrame.get('activate_custom_buttons')

          if activate_custom_buttons then
             BottomFrame.add_inner_frame(
                {
                   player = player,
                   element_name = melee_mode_name,
                   tooltip =
                   {
                      'modules_melee.tooltip'
                   },
                   sprite = 'item/pistol',
                   section_override = 1
                }
             )
          end
       end
    )

local function create_gui_button(player)
   if player.gui.top[melee_mode_name] then
      return
   end
   local tooltip = { "modules_melee.tooltip" }
   local b = player.gui.top.add(
      {
         type = 'sprite-button',
         sprite = 'item/pistol',
         name = melee_mode_name,
         tooltip = tooltip
      })
   b.style.font_color = { r = 0.11, g = 0.8, b = 0.44 }
   b.style.font = 'heading-1'
   b.style.minimal_height = 40
   b.style.maximal_width = 40
   b.style.minimal_width = 38
   b.style.maximal_height = 38
   b.style.padding = 1
   b.style.margin = 0
end

local function on_player_joined_game(event)
   local activate_custom_buttons = BottomFrame.get('activate_custom_buttons')
   local player = game.get_player(event.player_index)

   if not activate_custom_buttons then
      create_gui_button(player)
   else
      Task.set_timeout_in_ticks(5, delay_add_inner_frame_token, { player_index = event.player_index })
   end
end

local function move_to_main(player, from, to)
   local ret = {}
   if from == nil or to == nil then
      return {}
   end
   for i = 1, #from do
      local c = from[i]
      if c.valid_for_read then
         if to.can_insert(c) then
            local amt = to.insert(c)
            ret[#ret + 1] = { name = c.name, count = amt, quality = c.quality }
            c.count = c.count - amt
         else
            player.print('Unable to move ' .. c.name .. ' to main inventory')
         end
      end
   end
   return ret
end

local function change_to_melee(player)
   local main_inv = player.get_main_inventory()
   local gun_inv = player.get_inventory(defines.inventory.character_guns)
   local ammo_inv = player.get_inventory(defines.inventory.character_ammo)
   if main_inv == nil or gun_inv == nil or ammo_inv == nil then
      return false
   end
   local gun_moved = move_to_main(player, gun_inv, main_inv)
   local ammo_moved = move_to_main(player, ammo_inv, main_inv)

   state[player.index] = { gun = gun_moved, ammo = ammo_moved }
   return true
end

local function try_move_from_main(main, to, what)
   if what == nil or main == nil or to == nil then
      return
   end
   for i = 1, #what do
      local amt_out = main.remove(what[i])
      if amt_out > 0 then
         local amt_in = to.insert({ name = what[i].name, count = amt_out, quality = what[i].quality })
         if amt_in < amt_out then
            main.insert({ name = what[i].name, count = amt_out - amt_in, quality = what[i].quality })
         end
      end
   end
end

local function change_to_ranged(player)
   local moved = state[player.index]
   if moved == nil then
      moved = {}
   end
   local main_inv = player.get_main_inventory()
   local gun_inv = player.get_inventory(defines.inventory.character_guns)
   local ammo_inv = player.get_inventory(defines.inventory.character_ammo)
   if main_inv == nil or gun_inv == nil or ammo_inv == nil then
      return false
   end
   try_move_from_main(main_inv, gun_inv, moved.gun)
   try_move_from_main(main_inv, ammo_inv, moved.ammo)
   state[player.index] = {}
   return true
end

local function on_gui_click(event)
   local activate_custom_buttons = BottomFrame.get('activate_custom_buttons')
   if activate_custom_buttons then
      return
   end

   if not event.element then
      return
   end
   if not event.element.valid then
      return
   end
   if event.element.name ~= melee_mode_name then
      return
   end
   local player = game.get_player(event.player_index)
   if not player or not player.valid then
      return
   end
   local mm = player.gui.top[melee_mode_name]
   if mm and mm.valid and mm.sprite == 'item/pistol' then
      if change_to_melee(player) then
         player.print({ "modules_melee.change_to_melee" })
         mm.sprite = 'technology/steel-axe'
      else
         player.print({ "modules_melee.change_to_melee_failed" })
      end
   else
      if change_to_ranged(player) then
         player.print({ "modules_melee.change_to_ranged" })
         mm.sprite = 'item/pistol'
      else
         player.print({ "modules_melee.change_to_ranged_failed" })
      end
   end
end

local function moved_to_string(tbl)
   local ret = ''
   for i = 1, #tbl do
      if ret ~= '' then
         ret = ret .. ', '
      end
      ret = ret .. tbl[i].count .. ' ' .. tbl[i].name
   end
   return ret
end

local function player_inventory_changed(player_index, inv_id, name)
   local player = game.get_player(player_index)
   if not player or not player.valid then
      return
   end

   local activate_custom_buttons = BottomFrame.get('activate_custom_buttons')
   if activate_custom_buttons then
      local old_frame = BottomFrame.get_frame_by_element_name(player, melee_mode_name)
      if old_frame and old_frame.sprite == 'item/pistol' then
         return
      end
   else
      if player.gui.top[melee_mode_name] and player.gui.top[melee_mode_name].valid and player.gui.top[melee_mode_name].sprite == 'item/pistol' then
         return
      end
   end

   local inv = player.get_inventory(inv_id)
   local moved = move_to_main(player, inv, player.get_main_inventory())
   if #moved > 0 then
      player.print({ "modules_melee.move_to_main_inventory", moved_to_string(moved) })
   end
   if not inv or not inv.is_empty() then
      player.print({ "modules_melee.move_to_main_inventory_failed", name })
   end
end

local function on_player_ammo_inventory_changed(event)
   if event.tick < 200 then return end
   player_inventory_changed(event.player_index, defines.inventory.character_ammo, 'ammo')
end

local function on_player_gun_inventory_changed(event)
   if event.tick < 200 then return end
   player_inventory_changed(event.player_index, defines.inventory.character_guns, 'guns')
end

Gui.on_click(
   melee_mode_name,
   function (event)
      local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Mtn v3 Spectate Ready Button')
      if is_spamming then
         return
      end

      local player = event.player
      if not player or not player.valid or not player.connected then
         return
      end

      local old_frame = BottomFrame.get_frame_by_element_name(player, melee_mode_name)
      if old_frame and old_frame.sprite == 'item/pistol' then
         if change_to_melee(player) then
            player.print({ "modules_melee.change_to_melee" })
            old_frame.sprite = 'technology/steel-axe'
            BottomFrame.refresh_inner_frames(player)
         else
            player.print({ "modules_melee.change_to_melee_failed" })
         end
      else
         if change_to_ranged(player) then
            player.print({ "modules_melee.change_to_ranged" })
            old_frame.sprite = 'item/pistol'
            BottomFrame.refresh_inner_frames(player)
         else
            player.print({ "modules_melee.change_to_ranged_failed" })
         end
      end

      on_gui_click(event)
   end
)

Event.add(defines.events.on_player_joined_game, on_player_joined_game)
Event.add(defines.events.on_gui_click, on_gui_click)
Event.add(defines.events.on_player_ammo_inventory_changed, on_player_ammo_inventory_changed)
Event.add(defines.events.on_player_gun_inventory_changed, on_player_gun_inventory_changed)
