---
--- @package   eg4-crank-ui
--- @file      home-screen-menu.lua
--- @brief     Functionality related to the display and control of the home
---            screen menu icons.
---
--- @copyright Copyright 2019 Hanover Displays Limited.
--- @license   This program is the confidential and proprietary product of
---            Hanover Displays Limited. Any unauthorised use, reproduction or
---            transfer of this program is strictly prohibited. (Subject to
---            limited distribution and restricted disclosure only.) All
---            rights reserved.
---

-- Data and status for the icon menu
local icon_menu = {}
local starting_icon_order
local current_icon_order

---
--- @brief  Changes the hidden status of a home screen icon.
--- @param  name - name of icon
--- @param  hidden - 0 or 1
---
function SetHomeScreenIcon(name, hidden)
  gre.set_control_attrs("banner_icon_menu_layer.controls."..name, { hidden = hidden })
end

---
--- @brief  Converts x,y location to row col. 
--- Note: icon_menu must be populated for this function work
--- @param  x - control location
--- @param  y - control location
--- @return row, col - calculated cell position
---
local function ConvertToColRow(x, y)
    if x == nil or y == nil then return end

    local col = math.floor(x / icon_menu.cell_width) + 1
    if col < 1 then col = 1
    elseif col > icon_menu.cols then col = icon_menu.cols end

    local row = math.floor(y / icon_menu.cell_height) + 1
    if row < 1 then row = 1
    elseif row > icon_menu.rows then row = icon_menu.rows end

    return col, row
end

---
--- @brief  Calculates the control icon position in the table 
--- @param  control - control to determine position
--- @return Calculated cell position
---
local function GetControlPosition(control)
  local x = gre.get_value(control..".grd_x")
  local y = gre.get_value(control..".grd_y")
  local col, row = ConvertToColRow(x, y)
  return (row - 1) * icon_menu.cols + col
end

---
--- @brief  Populate the icon_menu data  
---
--- @param gre#context mapargs
local function PopulateIconMenuData(layer, group, control)
  icon_menu.layer_context = layer
  icon_menu.group_context = group
  icon_menu.x_origin = gre.get_layer_attrs(layer, "x").x + gre.get_value(group..".grd_x")
  icon_menu.y_origin = gre.get_layer_attrs(layer, "y").y + gre.get_value(group..".grd_y")
  icon_menu.rows = gre.get_table_attrs(group..".spacers", "rows").rows
  icon_menu.cols = gre.get_table_attrs(group..".spacers", "cols").cols
  icon_menu.cell_width = gre.get_value(group..".spacers.grd_width") / icon_menu.cols
  icon_menu.cell_height = math.floor(gre.get_value(group..".spacers.grd_height")) / 4

  icon_menu.control_context = control
  icon_menu.start_col, icon_menu.start_row = ConvertToColRow(gre.get_value(control..".grd_x"), gre.get_value(control..".grd_y"))
  icon_menu.control_height = gre.get_value(control..".grd_height")
  icon_menu.x_limit = gre.get_value(group..".spacers.grd_width") - icon_menu.cell_width
  icon_menu.y_limit = icon_menu.rows * icon_menu.cell_height - icon_menu.control_height
end

---
--- @brief  Starts the position edit of home menu icons  
---
--- @param gre#context mapargs
function CBEditControlCheck(mapargs)
  -- Check if the control is on the editing screen
  if mapargs.context_screen == "mo_display_home_icons_edit" then
    
    -- Calculate the various variables used to reposition the icons 
    PopulateIconMenuData(mapargs.context_layer, mapargs.context_group, mapargs.context_control)

    -- Bring the icon to the front
    gre.set_value(mapargs.context_control..".grd_zindex", 255)
  end
end

---
--- @brief  Indicates to other functions when the icon menu is opening up.
--- @return true it is current being opened.
---
function IconMenuOpening()
  return icon_menu.control_context ~= nil or (icon_menu.editing ~= true) and (icon_menu.in_progress or false)
end

---
--- @brief  Starts the opening process for the home screen icon menu
---
--- @param gre#context mapargs
function CBIconMenuDown(mapargs)
  -- Check if already open
  if icon_menu.open ~= true then
    -- Check that the icon editing is not active 
    if mapargs.context_screen ~= "mo_display_home_icons_edit" then
      icon_menu.layer = mapargs.context_layer
      icon_menu.rows = gre.get_table_attrs(icon_menu.layer..".controls.spacers", "rows").rows
      if icon_menu.rows > 1 then
        icon_menu.cell_height = gre.get_value(icon_menu.layer..".controls.spacers.grd_height") / 4
        icon_menu.start_top = 480 - icon_menu.cell_height
        icon_menu.open_height = icon_menu.cell_height * icon_menu.rows
        icon_menu.open_start_y = mapargs.context_event_data.y
        if mapargs.context_event_data.y > icon_menu.start_top then
          gre.set_value(icon_menu.layer..".controls.grd_y", icon_menu.start_top,
                        icon_menu.layer..".background.grd_hidden", 0,
                        icon_menu.layer..".background.alpha", 0,
                        icon_menu.layer..".controls.background.alpha", 0)
          icon_menu.opening = true
        end
      end
    end
  end
end

---
--- @brief  Starts the opening process for the home screen icon menu
---
--- @param gre#context mapargs
function CBCloseIconMenu()
  if icon_menu.opening or icon_menu.open then
    gre.set_value(icon_menu.layer..".controls.grd_y", icon_menu.start_top,
                  icon_menu.layer..".background.grd_hidden", 1,
                  icon_menu.layer..".background.alpha", 0,
                  icon_menu.layer..".controls.background.alpha", 0)
    icon_menu = {}
  end
end

---
--- @brief Take the arrayed list and use it to determine the new location of the icons
--- @param list - arrayed list of icons
---
local function UpdateIconLocations(list)
  local i = 1
  for row = 1, icon_menu.rows do
    for col = 1, icon_menu.cols do
      if list[i] ~= "" then
        gre.set_value(list[i]..".grd_x", (col - 1) * (icon_menu.cell_width),
                      list[i]..".grd_y", (row - 1) * icon_menu.cell_height + 13)
      end
      i = i + 1
    end      
  end
end

---
--- @brief  Update the icon positions as a result of an icon being moved.
---
local function UpdateIconPositions()

  -- Create list that is the size of all the slots available for controls.
  local list = {}
  for i = 1,icon_menu.rows * icon_menu.cols do
    table.insert(list, "")
  end

  -- Populate list in controls in the order the icons appear on the screen
  local objects = GetChildObjectList(icon_menu.group_context)
  for i = 1,#objects do
    local control = tostring(objects[i])
    if control ~= icon_menu.control_context and gre.get_value(control..".short_name") ~= nil then
      list[GetControlPosition(control)] = control 
    end
  end


  local end_pos = GetControlPosition(icon_menu.control_context)
  -- Check if destination control slot is already occupied. 
  if list[end_pos] ~= "" then
     -- Determine the closest gap to the moved icon in the direct the icon was moved.
    local start_pos = (icon_menu.start_row - 1) * icon_menu.cols + icon_menu.start_col
    local gap_index
    -- Check if icon is moving right
    if icon_menu.start_col < (end_pos - 1) % icon_menu.cols + 1 then
      for i = end_pos - 1, 0, -1 do
        if list[i] == "" then
          gap_index = i
          break
        end
      end
      if gap_index == nil then
        for i = end_pos + 1, #list do
          if list[i] == "" then
            gap_index = i
            break
          end
        end
      end
    else
      -- Item is moving left
      for i = end_pos + 1, #list do
        if list[i] == "" then
          gap_index = i
          break
        end
      end
      if gap_index == nil then
        for i = end_pos - 1, 0, -1 do
          if list[i] == "" then
            gap_index = i
            break
          end
        end
      end
    end
    
    -- Create gap for the moved icon.
    table.remove(list, gap_index)
    table.insert(list, end_pos, "")

    -- Set the updated icon locations
    UpdateIconLocations(list)
  end
end

---
--- @brief  Processes the icon menu release even. Either closes the icon menu or completes the open action
---
--- @param gre#context mapargs
function CBIconMenuRelease(mapargs)

  -- Check if release is a result of editing
  if icon_menu.control_context ~= nil then

    local col, row = ConvertToColRow(mapargs.context_event_data.x - icon_menu.x_origin, mapargs.context_event_data.y - icon_menu.y_origin)
    local x = (col - 1) * icon_menu.cell_width
    local y = (row - 1) * icon_menu.cell_height + 13

    gre.set_value(icon_menu.control_context..".grd_x", x,
                  icon_menu.control_context..".grd_y", y)

    UpdateIconPositions()

    icon_menu.control_context = nil
  else
    if icon_menu.open == true then
      if mapargs.context_event_data.y < gre.get_value(icon_menu.layer..".controls.grd_y") then
        CBCloseIconMenu()
      end
    elseif icon_menu.opening == true then
      -- Lock icon menu open if 
      if mapargs.context_event_data.y < icon_menu.open_start_y - 98 * 2 then
        gre.set_value(icon_menu.layer..".controls.grd_y", 480 - icon_menu.open_height,
                      icon_menu.layer..".background.alpha", 64,
                      icon_menu.layer..".controls.background.alpha", 255)
        icon_menu.open = true
        icon_menu.opening = false
        icon_menu.in_progress = false
      else
        CBCloseIconMenu()
      end
    end
  end
end

---
--- @brief  Process the open menu motion event
---
--- @param gre#context mapargs
function CBIconMenuMotion(mapargs)
  if icon_menu.control_context ~= nil then
    local x = mapargs.context_event_data.x - icon_menu.x_origin - icon_menu.cell_width / 2
    local y = mapargs.context_event_data.y - icon_menu.y_origin - icon_menu.control_height / 2

    if x < 0 then x = 0 
    elseif x > icon_menu.x_limit then x = icon_menu.x_limit end
    if y < 0 then y = 0 
    elseif y > icon_menu.y_limit then y = icon_menu.y_limit end

    gre.set_value(icon_menu.control_context..".grd_x", x,
                  icon_menu.control_context..".grd_y", y)
  else
    if icon_menu.opening == true then
      if icon_menu.open_start_y - mapargs.context_event_data.y > 10 then
        icon_menu.in_progress = true
        local y = (icon_menu.open_start_y - mapargs.context_event_data.y) / (480 / 2)
        if y < 0 then y = 0 end
        if y > 1 then y = 1 end
        gre.set_value(icon_menu.layer..".background.alpha", y * 64,
                      icon_menu.layer..".controls.grd_y", icon_menu.start_top - y * (icon_menu.rows - 1) * icon_menu.cell_height,
                      icon_menu.layer..".controls.background.alpha", y * 255)
      end
    end
  end
end

---
--- @brief  Copies the control location for all child controls in a group from the src
--         _group to the dst_group.
---
--- @param dst_group - parent of all controls to be update
--- @param src_group - parent of all controls that shall provide locations.
local function CopyControlLocations(dst_group, src_group)
  local objects = GetChildObjectList(dst_group)
  for i = 1,#objects do
    local dst = tostring(objects[i])
    local src = src_group..dst:sub(#dst_group + 1)
    if gre.get_value(src..".short_name") ~= nil then
      gre.set_value(dst..".grd_x", gre.get_value(src..".grd_x"), 
                    dst..".grd_y", gre.get_value(src..".grd_y"))
    end
  end
end

---
--- @brief  Start the home icon edit. Unless already done, copies all 
---         icons from the icon menu to the edit layer.
---
--- @param gre#context mapargs
function CBStartDisplayHomeIconsEdit(mapargs)
  -- Clear editing variables
  icon_menu = {}

    -- Populate layer/group parts of 
  local layer = "home_icon_menu_edit_layer"
  local dst_group = layer..".controls"
  local src_group = "banner_icon_menu_layer.controls"

  -- Check if edit group is not already populated.
  local objects = GetChildObjectList(dst_group)
  if #objects <= 2 then
    -- Clone icons from home icon menu group
    local objects = GetChildObjectList(src_group)
    for i = 1,#objects do
      local control = tostring(objects[i])
      if gre.get_value(control..".short_name") ~= nil then
        gre.clone_object(control, control:sub(#src_group + 2), dst_group, { hidden = 0 })
      end
    end
  end

  -- Copy location information from the home icon group 
  CopyControlLocations(dst_group, src_group)
end

---
--- @brief  Send an updated icon positional list to the backend 
---
local function SendHomeIconLocations()
  -- Check if nothing moved.
  if next(icon_menu) == nil then return end

  local list = {}
  for i = 1,icon_menu.rows * icon_menu.cols do
    table.insert(list, "-")
  end

  -- Populate list in controls in the order the icons appear on the screen
  local objects = GetChildObjectList(icon_menu.group_context)
  for i = 1,#objects do
    local control = tostring(objects[i])
    if gre.get_value(control..".short_name") ~= nil then
      local pos = GetControlPosition(control)
      list[pos] = gre.get_value(control..".short_name")
    end
  end

  local value = ""
  local sep = ""
  for i = 1,#list do
    value = value..sep..list[i]
    sep = ","
  end

  sbsettings.set_home_screen_icon_order(value)
end

---
--- @brief  Copies updated icon locations back to the home screen and sends update to backend
---
--- @param gre#context mapargs
function CBAcceptHomeScreenEdits(mapargs) 
  -- Copy location information back to the home icon group 
  SendHomeIconLocations()
  CopyControlLocations("banner_icon_menu_layer.controls", icon_menu.group_context)
  icon_menu = {}
  TransitionBackScreen(mapargs)
end

---
--- @brief  Parse the icon order string into a table. 
--- 
--- Extracts all alphanumeric words and individual hyphens into separate table
--- items. All other characters are treated as separators. Repeated separators 
--- are ignored. Hyphens may be contiguous and will be divided into separate items. 
--- 
--- @param  value - current icon order value
--- @return table of alphanumeric items parsed from the string
local function ParseIconOrder(value)
  local order = {}
  local item
  for c in value:gmatch(".") do
    if (c:match("%W")) then
      if item then
        table.insert(order, item)
        item = nil
      end
      if c == '-' then
        table.insert(order, c)
      end
    else
      item = (item or "")..c
    end
  end
  if item then
    table.insert(order, item)
  end
  return order
end

---
--- @brief  Applies the icon order string and updates the home screen icons accordingly.
---
local function ApplyHomeScreenIconOrder(icon_order)
  -- Create map of short name to control name
  local objects = GetChildObjectList(icon_menu.group_context)
  local map = {}
  for i = 1,#objects do
    local control = tostring(objects[i])
    local short_name = gre.get_value(control..".short_name")
    if short_name ~= nil then
      map[short_name] = control
    end
  end

  local list = {}
  for i = 1,icon_menu.rows * icon_menu.cols do
    table.insert(list, "")
  end

  -- Populate list with controls using short name reference, then remove from map.
  local value = ParseIconOrder(icon_order)
  local max = (#value < #list) and #value or #list
  for i=1,max do
    local temp = value[i]
    list[i] = map[temp] or ""
    map[temp] = nil
  end
  
  -- Insert controls not specified in setting
  for _,control in pairs(map) do
    local pos = GetControlPosition(control)
    -- Check if setting control has occupied unspecified control position
    if list[pos] ~= "" then
      -- Look for a gap and insert existing unspecified item
      for i = pos - 1, 0, -1 do
        if list[i] == "" then
          list[i] = control
          control = nil
          break
        end
      end
      if control then
        for i = pos + 1, #list do
          if list[i] == "" then
            list[i] = control
            break
          end
        end
      end
    else
      list[pos] = control
    end
  end  

  -- Set the updated icon locations
  UpdateIconLocations(list)
end

---
--- @brief  Processes home screen icon order changes
---
local function ProcessHomeScreenIconOrder()
  -- Check if layer is accessible, if not delay processing the setting until screen show
  local layer ="banner_icon_menu_layer"
  if gre.get_layer_attrs(layer, "x") == nil then return end

  -- Check if the setting has changed and exit if not.
  local icon_order = sbsettings.get_home_screen_icon_order()
  if current_icon_order == icon_order then return end
  current_icon_order = icon_order

  PopulateIconMenuData(layer, layer..".controls", layer..".controls.settings_control")

  if starting_icon_order then
    -- Reset icons to starting order
    ApplyHomeScreenIconOrder(starting_icon_order)
  else
    -- Collect the starting order
    local objects = GetChildObjectList(icon_menu.group_context)
    if not starting_icon_order then
      for i = 1,#objects do
        local control = tostring(objects[i])
        local short_name = gre.get_value(control..".short_name")
        if short_name ~= nil then
          starting_icon_order = short_name..(starting_icon_order and (","..starting_icon_order) or "")
        end
      end
    end
  end

  -- Apply the updated icon order
  ApplyHomeScreenIconOrder(icon_order)

  -- Clear values
  icon_menu = {}
end

---
--- @brief  Callback apply icon order settings when the layer becomes available
---
function CBProcessHomeScreenIconOrder()
  ProcessHomeScreenIconOrder()
end

-- EOF --
