local game = ...
local warp_menu = {}  -- The warp menu.
local initial_point
local initial_y = 10
local initial_volume 
local hero_x, hero_y
local matched = {}          -- Array of discovered warp point entries, sorted geographically (by y, then x).
local current_match_index = 1  -- Index into matched: which discovered point is currently selected.
local joy_avoid_repeat = {-2, -2}

-- Warp point name, Companion point, Warp to map, Coordinate x on minimap, Coordinate y on minimap, Name of warp.
warp_points = {         -- Intentionally Global!
  b1500 = { "b1501", "133", 166, 102, "Old Kasuto" },     -- Patch at Hidden Village (G8)
  b1501 = { "b1500", "46", 178, 220, "Hidden Village" },  -- Patch at Old Kasuto (L5)
  b1502 = { "b1503", "51", 90, 194, "Kakariko City" },
  b1503 = { "b1502", "11", 162, 372, "Ordon Village" },
  b1504 = { "b1505", "72", 2, 228, "Gerudo Camp" },
  b1505 = { "b1504", "66", 208, 178, "Goron City" },
  b1506 = { "b1507", "82", 50, 346, "Beach" },
  b1507 = { "b1506", "37", 218, 266, "Lost Woods" },
  b1508 = { "b1509", "60", 4, 180, "Snowpeak" },
  b1509 = { "b1508", "88", 15, 25, "Calatia Peaks" }, 
  b1510 = { "b1511", "56", 140, 160, "Septen Heights" },
  b1511 = { "b1510", "139", 178, 126, "Three Eye Rock" },
  b1512 = { "b1513", "57", 184, 150, "Zora's Domain" },
  b1513 = { "b1512", "93", 34, 38, "Ruto Town" },
  b1514 = { "b1515", "34", 120, 258, "Lake Hylia" },
  b1515 = { "b1514", "13", 210, 365, "Floria Peninsula" },
  b1516 = { "b1517", "135", 194, 26, "Nabooru Town" },
  b1517 = { "b1516", "114", 100, 74, "Mido Town" },
  b1518 = { "b1519", "118", 130, 80, "Island Palace" },
  b1519 = { "b1518", "103", 48, 90, "Saria Town" }
}

-- Rebuilds "matched" from scratch: every warp point whose companion has
-- been discovered, sorted geographically (top to bottom, then left to
-- right) so that pressing the same direction repeatedly moves the
-- selection across the map in a consistent, predictable order both ways.
local function rebuild_matched()
  matched = {}
  for key, v in pairs(warp_points) do
    if game:get_value(v[1]) then
      table.insert(matched, v)
    end
  end
  table.sort(matched, function(a, b)
    if a[4] ~= b[4] then
      return a[4] < b[4]  -- Primarily by y (minimap row).
    end
    return a[3] < b[3]    -- Then by x, for points on the same row.
  end)
end

function game:on_warp_started(point)
  initial_point = point
  if not sol.menu.is_started(warp_menu) then sol.menu.start(game, warp_menu) end
end

function warp_menu:on_started()
  game.hud:set_enabled(false)
  game.hud:set_enabled(true)  -- Refresh the HUD so it stays on top of the menu.
  self.hero_head_sprite = sol.sprite.create("menus/hero_head")
  self.hero_head_sprite:set_animation("tunic" .. game:get_item("tunic"):get_variant())
  self.background_surfaces = sol.surface.load("languages/" .. sol.language.get_language() .. "/images/pause_submenus.png")
  self.background_surfaces:set_opacity(192)
  self.cursor_sprite = sol.sprite.create("menus/pause_cursor")
  local menu_font, menu_font_size = sol.language.get_menu_font()
  self.caption_text_1 = sol.text_surface.create{
    horizontal_alignment = "center",
    vertical_alignment = "middle",
    font = menu_font,
    font_size = menu_font_size,
  }
  self.caption_text_2 = sol.text_surface.create{
    horizontal_alignment = "center",
    vertical_alignment = "middle",
    font = menu_font,
    font_size = menu_font_size,
  }

  -- Show a world map.
  self.caption_text_1:set_text(sol.language.get_string("map.caption.warp"))
  self.outside_world_minimap_size = { width = 225, height = 399 }
  self.world_minimap_img = sol.surface.load("sprites/menus/warp_map.png")
  self.world_minimap_movement = nil
  self.world_minimap_visible_xy = {x = 0, y = 0 }

  -- Build the list of discovered warp points, once, in a stable
  -- geographic order.
  rebuild_matched()

  -- Start the selection on the warp point for the map the hero is
  -- currently standing on (this menu can only be opened from a map that
  -- is itself one of the warp destinations).
  local current_map_id = game:get_map():get_id()
  current_match_index = 1
  for i, v in ipairs(matched) do
    if v[2] == current_map_id then
      current_match_index = i
      break
    end
  end
  self:select_current_match()

  -- Ensure the hero can't move and the HUD is correct.
  game:get_map():get_hero():freeze()
  game.hud.elements[10].surface:set_opacity(128) --item_icon_1
  game.hud.elements[11].surface:set_opacity(128) --item_icon_2
  game.hud.elements[12].surface:set_opacity(128) --attack_icon
  game.hud.elements[9]:on_menu_opened() -- pause_icon

  -- Lower the volume (so ocarina sound can be heard when point selected).
  initial_volume = sol.audio.get_music_volume()
  sol.audio.set_music_volume(initial_volume/3)
end

function warp_menu:on_command_pressed(command)
  if command == "left" or command == "up" then
    self:previous_warp_point()
    handled = true
  elseif command == "right" or command == "down" then
    self:next_warp_point()
    handled = true
  elseif command == "action" then
    local v = matched[current_match_index]
    if v ~= nil and game:get_value(v[1]) then
      game:start_dialog("warp.to_"..v[2], function(answer)
        if answer == 1 then
          sol.menu.stop(warp_menu)
          game:get_map():get_hero():set_animation("ocarina")
          sol.audio.play_sound("ocarina_wind")
          game:get_map():get_entity("hero"):teleport(v[2], "ocarina_warp", "fade")
        end
      end)
    end
  elseif command == "pause" then
    sol.menu.stop(warp_menu)
  end

  return true
end

-- Moves the selection to the next discovered warp point, geographically
-- (wrapping back to the first point after the last one).
function warp_menu:next_warp_point()
  if #matched == 0 then
    return
  end
  current_match_index = current_match_index + 1
  if current_match_index > #matched then
    current_match_index = 1
  end
  self:select_current_match()
end

-- Moves the selection to the previous discovered warp point, geographically
-- (wrapping around to the last point from the first one).
function warp_menu:previous_warp_point()
  if #matched == 0 then
    return
  end
  current_match_index = current_match_index - 1
  if current_match_index < 1 then
    current_match_index = #matched
  end
  self:select_current_match()
end

-- Updates the cursor and minimap scroll to match whatever warp point
-- current_match_index now points to.
function warp_menu:select_current_match()
  local v = matched[current_match_index]
  if v == nil then
    return
  end
  self:set_cursor_position(v[3], v[4])
  if v[4] >= 133 then initial_y = v[4] - 133 + 10 else initial_y = 0 end
  self.world_minimap_visible_xy = {x = 0, y = initial_y }
end

function warp_menu:set_cursor_position(x, y)
  self.cursor_x = x
  self.cursor_y = y
  if y > 133 then
    if y < 399 then self.world_minimap_visible_xy.y = y - 51 else self.world_minimap_visible_xy.y = 399 end
  end

  -- Update the caption text. Only show it if that point has been discovered.
  for k, v in pairs(warp_points) do
    if v[3] == self.cursor_x and v[4] == self.cursor_y and game:get_value(v[1]) then
      self.caption_text_2:set_text(v[5])
      game:set_custom_command_effect("action", "validate")
    end
  end
end

function warp_menu:on_draw(dst_surface)
  -- Draw background.
  local width, height = dst_surface:get_size()
  self.background_surfaces:draw_region(320, 0, 320, 240, dst_surface, (width - 320) / 2, (height - 240) / 2)

  -- Draw caption.
  local width, height = dst_surface:get_size()
  self.caption_text_1:draw(dst_surface, width / 2, height / 2 + 83)
  self.caption_text_2:draw(dst_surface, width / 2, height / 2 + 95)

  -- Draw the minimap.
  self.world_minimap_img:draw_region(self.world_minimap_visible_xy.x, self.world_minimap_visible_xy.y, 255, 133, dst_surface, 96, 59)

  -- Draw the warp points.
  for k, v in pairs(warp_points) do
    if game:get_value(v[1]) then -- Only those that have been discovered are shown.
      local point_visible_y = v[4] - self.world_minimap_visible_xy.y
      if point_visible_y >= 10 and point_visible_y <= 133 then self.hero_head_sprite:draw(dst_surface, v[3] + 88, point_visible_y + 51) end
    end
  end

  -- Draw the cursor.
  if self.cursor_y >= (10 + self.world_minimap_visible_xy.y) and self.cursor_y <= (133 + self.world_minimap_visible_xy.y) then
    self.cursor_sprite:draw(dst_surface, self.cursor_x + 96, self.cursor_y + 55 - self.world_minimap_visible_xy.y)
  end
end

function warp_menu:on_finished()
  sol.timer.start(game, 1500, function() sol.audio.set_music_volume(initial_volume) end)
  game:get_map():get_hero():unfreeze()
  game:set_custom_command_effect("pause", nil)
  game:set_custom_command_effect("action", nil)
  game.hud.elements[10].surface:set_opacity(255) --item_icon_1
  game.hud.elements[11].surface:set_opacity(255) --item_icon_2
  game.hud.elements[12].surface:set_opacity(255) --attack_icon
  game.hud.elements[9]:on_menu_closed() -- pause_icon
end

function game:on_joypad_axis_moved(axis, state)
  local handled = joy_avoid_repeat[axis % 2] == state
  joy_avoid_repeat[axis % 2] = state
  
  return handled
end
