--[[
  @package    eg4-crank-ui-2
  @file       menus.lua
  @brief      This file contains the implementation of the menu controls.
]]


-- static variable that indicates the number of options per page
local FRAME_LAYER = "menus_frame_layer"
local PAGING_RICH_TEXT_CONTROL = "paging_rich_text"

-- Status information about the current menu screen
local menu

---
--- @brief  Find the menu layer for the specified screen
local function find_menu_layer()
  if not menu.screen then return end
  local layers = get_children(menu.screen)
  if layers == nil then return end
  for i = #layers,1,-1 do
    local layer = tostring(layers[i])
    if gre.get_value(layer..".grd_hidden") == 0 and gre.get_value(layer..".menu.grd_hidden") == 0 then
      menu.layer = layer
      break
    end
  end
end

---
--- @brief  Process the list and remove any options that are hidden or should not be shown
--- @return The list of menu controls that should be shown
local function filter_on_show()
  menu.list = {}
  local data = {}
  local list = get_children(menu.layer:object_name()..".menu")
  for i=#list,1,-1 do
    local obj = tostring(list[i])
    local fn = _G["show_"..obj:object_name()]
    if fn then
      if fn(menu.screen) then
        data[obj..".grd_hidden"] = 0
        table.insert(menu.list, obj)
      else
        data[obj..".grd_hidden"] = 1
      end
    else
      if gre.get_value(obj..".grd_hidden") == 0 then
        table.insert(menu.list, obj)
      end
    end
    local fn = _G["active_"..obj:object_name()]
    if fn then
      data[obj..".grd_active"] = fn() and 1 or 0
      data[obj..".alpha_active"] = fn() and 255 or 0
    end
  end
  gre.set_data(data)
end

---
--- @brief  Fetch the menu data items
--- @param  mapargs  The map arguments from the callback
local function fetch_menu_data(mapargs)
  menu = {}
  if not mapargs then return end
  menu.screen = mapargs.context_screen or gre.env("active_screen")
  find_menu_layer()
  if not menu.layer then return end
  filter_on_show()
end

---
--- @brief  Get the current option get function for the context control, either from the values suppied, or from a global
---         get function or from sbsettings specified for the context control.
--- @param  context_control  The context control name
--- @param  v1 value 1 (optional) to be encoded
--- @param  v2 value 2 (optional) to be encoded
--- @return The formatted and scaled option value or nil if not found
local function make_option_value(context_control, v1, v2)

  if v1 == nil then
    -- Populate the values from the get function for the context control.
    local fn = "get_"..context_control:object_name():remove_trailing("_toggle")
    fn = _G[fn] or sbsettings[fn]
    if fn == nil then return end
    v1, v2 = fn(gre.get_value(menu.screen..".port_id"))
  end

  if type(v1) == "table" then
    local value = ""
    local sep = ""
    for _,v in ipairs(v1) do
      if v.checked == 1 then
        value = value ..sep .. v.text
        sep = ","
      end
    end
    return value
  end

  -- Scale the values if required by the control
  local scale = gre.get_value(context_control..".scale")
  if scale then
    if v1 then v1 = v1 / scale end
    if v2 then v2 = v2 / scale end
  end

  -- Format the values if required by the control
  local fmt = gre.get_value(context_control..".format")
  if fmt then
    return string.format(fmt, v1, v2)
  end

  -- Check for radio item and return the radio item text
  local v = tonumber(v1)
  if v then
    local radio = gre.get_value(context_control..".radio."..tostring(v1 + 1)..".1")
    if radio then return radio end
  end

  return v1
end

---
--- @brief  Set the context control setting from the current value, either using a global
---         set function or from sbsettings.
--- @param  context_control  The context control name
--- @return The current value
local function set_option_value(context_control, v1, v2)
  local new_value = make_option_value(context_control, v1, v2)
  if new_value == nil then return end
  if new_value == make_option_value(context_control) then return end

  local fn = "set_"..context_control:object_name():remove_trailing("_toggle")
  fn = _G[fn] or sbsettings[fn]
  local port_id = gre.get_value(menu.screen..".port_id")
  if fn then
    if v2 == nil then
      fn(v1, port_id)
    else
      fn(v1, v2, port_id)
    end
  end

  gre.set_value(context_control..".value", new_value)

  local event = gre.get_value(context_control..".event")
  if event then send_event(event) end
end

---
--- @brief  Update the toggle control control using the current value
--- @param  data            The data table for the menu control position updates
--- @param  context_control The context control name
--- @param  value           The current value
local function update_toggle_control(data, context_control, value)
  if value == nil then return end
  value = value ~= 0 and value
  data[context_control..".alpha_enabled"]  = value and 255 or 0
  data[context_control..".alpha_disabled"] = value and 0 or 255
end

---
--- @brief  Callback to update the toggle control
--- @param  mapargs  The map arguments from the callback
function cb_update_toggle_control(mapargs)
  local data = {}
  local value = gre.get_value(mapargs.context_control..".value")
  update_toggle_control(data, mapargs.context_control, value)
  gre.set_data(data)
  set_option_value(mapargs.context_control, value)
end

---
--- @brief  Updae the positions for the controls and populate the values.
--- @param  data  The data table for the menu control position updates
local function update_positions_and_values(data)
  local skip_value_update
  local y = 0
  for i = 1,#menu.list do
    local context_control = menu.list[i]
    if context_control:ends_with("_toggle") then
      data[context_control..".value"] = make_option_value(context_control)
      skip_value_update = true
    elseif not context_control:ends_with("_button") then
      data[context_control..".grd_y"] = y
      y = y + gre.get_value(context_control..".grd_height")
      if skip_value_update then
        skip_value_update = false
      else
        data[context_control..".value"] = make_option_value(context_control)
      end
    end
    -- Update toggle control
    if gre.get_value(context_control..".alpha_enabled") then
      update_toggle_control(data, context_control, data[context_control..".value"])
    end
  end

  -- Calculate the number of pages
  menu.total_pages = math.ceil(y / gre.get_value(menu.layer..".grd_height"))

  -- Adjust button positions to center them to their option
  local info
  for i = #menu.list,1,-1 do
    local context_control = menu.list[i]
    if context_control:ends_with("_button") or context_control:ends_with("_toggle") then
      local h = gre.get_value(context_control..".grd_height")
      data[context_control..".grd_y"] = info.y + (info.height - h) / 2
    else
      info = gre.get_control_attrs(context_control, "height")
      info.y = data[context_control..".grd_y"]
    end
  end
end

---
--- @brief  Update the page navigation controls
--- @param  data    The data table for the menu control position updates
--- @param  index   The current page index (optional)
local function update_page_navigation_control_layer(data, index)
  local group = FRAME_LAYER..".navigation"
  data[group..".grd_hidden"] = menu.total_pages < 2 and 1 or 0
  data[menu.screen.."."..PAGING_RICH_TEXT_CONTROL] = make_page_text(index, menu.total_pages)
  data[group..".up.alpha_disabled"] = index == 1 and gre.get_value(group..".control_inactive_alpha") or 255
  data[group..".up.grd_active"] = index == 1 and 0 or 1
  data[group..".down.alpha_disabled"] = index == menu.total_pages and gre.get_value(group..".control_inactive_alpha") or 255
  data[group..".down.grd_active"] = index == menu.total_pages and 0 or 1
end

---
--- @brief  Update the menu options depending of whether there are more than one page of options
--- @param  data    The data table for the menu control position updates
--- @param  index   The current page index (optional)
local function update_page_navigation_menu_options(data, index)
  -- Minor tweak was required to align the menu options correctly
  local y = (index - 1) * (gre.get_value(menu.layer..".grd_height") - 1)
  animate_scroll(menu.layer, -y)

  local hide_navigation = menu.total_pages > 1 and 0 or 1
  local width = (hide_navigation == 0) and gre.get_value(FRAME_LAYER..".navigation.grd_x") or get_screen_width()

  -- Update the menu control widths
  for i = 1,#menu.list do
    local control = menu.list[i]
    if control:ends_with("_button") or control:ends_with("_toggle") then
      data[control..".grd_x"] = width - gre.get_value(control..".grd_width") - 30
    else
      data[control..".grd_width"] = width
      if control:ends_with(".menu.list") then
        -- Update the table cell widths
        for i=1,gre.get_table_attrs(control, "rows").rows do
          gre.set_table_cell_attrs(control, i, 1, { width = width })
        end
      end
    end
  end
end

---
--- @brief  Update the page navigation
--- @param  data    The data table for the menu control position updates
--- @param  index   The current page index (optional)
local function update_page_navigation(data, index)
  local index = tonumber(index) or get_page_index(menu.screen) or 1
  if index < 1 then index = 1 end
  if index > menu.total_pages then index = menu.total_pages end
  update_page_navigation_control_layer(data, index)
  data[menu.screen.."."..FRAME_LAYER..".grd_x"] = get_screen_width() - gre.get_value(menu.screen.."."..FRAME_LAYER..".grd_width")
  if #menu.list == 0 then return end
  update_page_navigation_menu_options(data, index)
end

---
--- @brief  Check for navigating down menu and clear the page text if required
--- @param  data    The data table for the menu control position updates
local function check_paging_rich_text_clear(mapargs)
  if not menu.screen then return end
  if mapargs.context_event ~= "gre.screenshow.pre" then return end
  if not check_back_screen_set(menu.screen) then return end
  gre.set_value(
    menu.screen.."."..PAGING_RICH_TEXT_CONTROL, "",
    menu.layer..".grd_yoffset", 0
  )
end

---
--- @brief  Update the back icon image depending on context screen
--- @param  data    The data table for the menu control position updates
--- @param  screen  The current screen
local function update_back_icon(data)
  local use_home_icon = tonumber(gre.get_value(menu.screen..".use_home_icon"))
  data[FRAME_LAYER..".home.grd_hidden"] = (use_home_icon == 1) and 0 or 1
  data[FRAME_LAYER..".back.grd_hidden"] = (use_home_icon == 1) and 1 or 0
end

---
--- @brief  Callback to update the menu layer and page navigation
--- @param  mapargs  The map arguments from the callback
function cb_update_menu(mapargs)
  if not mapargs then mapargs = {} end
  fetch_menu_data(mapargs)
  if not menu.layer then return end
  local data = {}
  -- Disable scrolling.
  -- NOTE: Layer scrolling MUST be enabled for the background to render correctly on all pages
  data[menu.layer..".grd_scroll_enabled"] = 0

  local accept = gre.get_value(menu.screen..".accept") or ""
  data[FRAME_LAYER..".accept.grd_hidden"] = accept == "" and 1 or 0
  check_paging_rich_text_clear(mapargs)
  update_positions_and_values(data)
  update_page_navigation(data)
  update_back_icon(data)
  gre.set_data(data)
end

---
--- @brief  Callback to process the page navigation down operation
--- @param  mapargs  The map arguments from the callback
function cb_page_navigate(mapargs)
  local data = {}
  local index = get_page_index(menu.screen) + tonumber(mapargs.dir or 0)
  update_page_navigation(data, index)
  gre.set_data(data)
end

---
--- @brief  Check whether to show the usb power menu option
--- @return true if it should be shown
function show_usb_power()
  return sbsettings.get_usb_power_state() == 0
end

---
--- @brief  Check whether to show the usb power menu option button
--- @return true if it should be shown
function show_usb_power_button()
  return show_usb_power()
end

