---
--- @package   eg4-crank-ui
--- @file      update-progress.lua
--- @brief     Functionality for update progress screens
---
--- @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.
---
---

local user_data_file_list
local sign_font_file_list
local show_notification = false
local in_progress_timer = nil

local update_progress_tile_status
local update_progress_tile_info
local clear_update_tile_timer_id

---
-- @brief  Clears the update in progress tile
local function CBClearUpdateTile()
  if not clear_update_tile_timer_id then return end
  clear_update_tile_timer_id = nil
  gre.set_value("home_status_layer.status.downloading.grd_hidden", 1)
  DeactivateTile("remote_updates_group")
end

---
-- @brief  Update the updateb in progress tile
local function UpdateProgressTile()
  if update_progress_tile_status then

    local tile = "remote_updates_group"
    local control = "home_tiles_layer."..tile..".status"
    local text = gre.get_value(control.."."..update_progress_tile_status)
    gre.set_value(control..".text", string.format(text, update_progress_tile_info or ""))
    ActivateTile(tile)

    gre.set_value("home_status_layer.status.downloading.grd_hidden", update_progress_tile_status == "downloading" and 0 or 1)

    gre.timer_clear_timeout(clear_update_tile_timer_id)
    clear_update_tile_timer_id = nil
  else
    -- If we are not updating or downloading, then we want to hide the tile after a short delay
    clear_update_tile_timer_id = gre.timer_set_timeout(CBClearUpdateTile, 1000)
  end
end

---
-- @brief  Process the downloading in progress event
--- @param gre#context mapargs
function CBFileDownloadInProgress(mapargs)
  update_progress_tile_status = "downloading"
  UpdateProgressTile()
end

---
-- @brief  Process the downloading complete event
--- @param gre#context mapargs
function CBFileDownloadComplete(mapargs)
  update_progress_tile_status = nil
  UpdateProgressTile()
end

---
--- @brief  Action taken when user data started event occurs.
---
function CBProcessUpdateStarted(mapargs)
  if SystemRebooting() then return end
  if mapargs.context_event_data.text == nil then
    return
  end
  playStop()
  local layer = "usb_update_in_progress"
  gre.set_value(layer..".rolling_progress.grd_hidden", mapargs.hide_rolling_update or 1)
  local show_progress,info = assert(loadstring(mapargs.context_event_data.text))()
  if show_progress == nil or show_progress == 1 then
    local data = {
      [layer..".progress_bar.progress_width"] = 0,
      [layer..".status.text"] = "",
      ["notification_template_layer.process_update_failed.content.item"] = "",
    }
    
    if info and info.filename then
      local text = info.filename
      local status = GetConnectedSignData(FindStatusItem(info))
      if status and status.equipment_name then
        text = text.." - "..status.equipment_name
      end
      local format = gre.get_value(layer..".status.format")
      data[layer..".status.text"] = string.format(format, text)
      data["notification_template_layer.process_update_failed.content.item"] = text
    end
    gre.set_data(data)
    ScreenTransition("mo_usb_update_in_progress")
    show_notification = true
  else
    if info and info.filename then
      update_progress_tile_status = "updating"
      UpdateProgressTile()
    end
  end
end

---
--- @brief  Process the file transfer progress message.
function cb_notify_file_transfer_in_progress(mapargs)
  local status = loadstring(mapargs.context_event_data.text)()

  local layer = "usb_update_in_progress"
  local control = layer..".status"
  local text = gre.get_value(control.."."..status.type.."_"..status.mode) or gre.get_value(control.."."..status.type) or gre.get_value(control.."."..status.mode) or gre.get_value(control..".unknown")
  local format = gre.get_value(control..".format")

  gre.set_value(control..".text", string.format(format, text) .. " (" .. status.index .. "/".. status.total .. ")")

  local control = layer..".progress_bar"
  local progress = status.index / status.total
  local width = gre.get_value(control..".grd_width")
  gre.set_value(control..".progress_width", math.floor(width * progress + 0.5))
  gre.set_value(layer..".rolling_progress.grd_hidden", 1)

  update_progress_tile_status = "transferring"
  update_progress_tile_info = "(" .. status.index .. "/".. status.total .. ")"
  UpdateProgressTile()

end


---
--- @brief  Replace the default tone for USB update complete if the setting is enabled.
--- @param  gre#context mapargs
--- @param  value - the original tone callback function name
--- @return the original tone callback function name if the setting is disabled, or nil if it is enabled
local function replace_tone_for_usb(mapargs, value)
  local play_tone_path = "notification_template_layer."..mapargs.context_event..".play_tone"
  if value == nil then
    value = gre.get_value(play_tone_path)
  end
  if sbsettings.get_beeps_on_usb_update() == 1 and #fileinfo:scandir('/media') ~= 0 then
    gre.set_value(play_tone_path, "CBPlayRemoveUSB")
  else
    gre.set_value(play_tone_path, value)
  end
  return value
end

---
--- @brief  Action taken when update success event occurs.
---
local store_success_default_beep = nil
function CBProcessUpdateSuccess(mapargs)
  update_progress_tile_status = nil
  UpdateProgressTile()
  if show_notification then
    show_notification = false
    store_success_default_beep = replace_tone_for_usb(mapargs, store_success_default_beep)
    CBNotify(mapargs)
  end
  ScreenTransition(gre.get_value("no_activity_screen"))
  display_sign_update_progress()
end

---
--- @brief  Action taken when update failed event occurs.
---
local store_failed_default_beep = nil
function CBProcessUpdateFailed(mapargs)
  update_progress_tile_status = nil
  UpdateProgressTile()
  show_notification = false
  store_failed_default_beep = replace_tone_for_usb(mapargs, store_failed_default_beep)
  CBNotify(mapargs)
  ScreenTransition(gre.get_value("no_activity_screen"))
  display_sign_update_progress()
end

---
--- @brief  Action taken when user data progress event occurs.
--- @param  gre#context mapargs
---
function CBProcessUserDataProgress(mapargs)
  if mapargs.context_event_data.text == nil then
    return
  end
  local progress = assert(loadstring(mapargs.context_event_data.text))()
  if progress == nil then
    return
  end

  local ctrl = gre.get_control_attrs("usb_update_in_progress.progress_bar", "width")
  gre.set_value("usb_update_in_progress.progress_bar.progress_width", math.floor(ctrl.width * progress + 0.5))
end

---
--- @brief  Reject user data update
--- @param  gre#context mapargs
---
function CBUSBUpdateUserDataRejected(mapargs)
  SendEvent("usb_update_user_data_request_rejected")
  CBCloseNotification(mapargs)
end

---
--- @brief  Accept user data update
--- @param  gre#context mapargs
---
function CBUSBUpdateUserDataConfirmed(mapargs)
  SendEvent("usb_update_user_data_request_confirmed")
  CBCloseNotification(mapargs)
end

---
--- @brief  Reject UI data update
--- @param  gre#context mapargs
---
function CBUSBUpdateUIDataRejected(mapargs)
  SendEvent("usb_update_ui_data_request_rejected")
  CBCloseNotification(mapargs)
end

---
--- @brief  Accept UI data update
--- @param  gre#context mapargs
---
function CBUSBUpdateUIDataConfirmed(mapargs)
  SendEvent("usb_update_ui_data_request_confirmed")
  CBCloseNotification(mapargs)
end

---
--- @brief  Reject software update
--- @param  gre#context mapargs
---
function CBUSBUpdateSoftwareRejected(mapargs)
  SendEvent("usb_update_software_request_rejected")
  CBCloseNotification(mapargs)
end

---
--- @brief  Accept software update
--- @param  gre#context mapargs
---
function CBUSBUpdateSoftwareConfirmed(mapargs)
  SendEvent("usb_update_software_request_confirmed")
  CBCloseNotification(mapargs)
end

---
--- @brief  Process select user data file event.
--- @param  gre#context mapargs
---
function CBProcessSelectUserDataFile(mapargs)
  if SystemRebooting() then return end
  -- Collect the file path list
  user_data_file_list = {}
  for filepath in mapargs.context_event_data.text:gmatch("[^\r\n]+") do
      table.insert(user_data_file_list, filepath)
  end

  -- Sort the table (case insensitive)
  table.sort(user_data_file_list, 
    function(a,b) 
      return a:lower() < b:lower()
    end)

  -- Populate the selection screen
  local data = {}
  local control = "select_user_data_file_layer.file_list"
  for i=1,#user_data_file_list do
    local items = user_data_file_list[i]:split("/")
    data[control..".text."..i..".1"] = items[#items]
    data[control..".fade."..i..".1"] = 0
  end

  data[control..".grd_height"] = #user_data_file_list * gre.get_table_cell_attrs(control, 1, 1, "height").height
  gre.set_table_attrs(control, { rows = #user_data_file_list })

  gre.set_data(data)

  UpdateSelectionScreenShow("mo_select_user_data_file")
end

---
--- @brief  Sends the selected user data file path to the backend.
--- @param  gre#context mapargs
---
function CBUserDataFileSelected(mapargs)
  if ScrollActive() then return end
  SendText("user_data_file_selected", user_data_file_list[mapargs.context_row])
  TransitionBackScreen(mapargs)
  user_data_file_list = nil
end

---
--- @brief  Sends the selected user data file path to the backend.
---
function CBCancelUserDataFileSelected()
  if user_data_file_list ~= nil then
    SendText("user_data_file_selected", "")
    user_data_file_list = nil
  end
end

---
--- @brief  Process select sign font file event.
--- @param  gre#context mapargs
---
function CBProcessSelectSignFontFile(mapargs)
  if SystemRebooting() then return end
  -- Collect the file path list
  sign_font_file_list = {}
  for filepath in mapargs.context_event_data.text:gmatch("[^\r\n]+") do
      table.insert(sign_font_file_list, filepath)
  end

  -- Sort the table (case insensitive)
  table.sort(sign_font_file_list, 
    function(a,b) 
      return a:lower() < b:lower()
    end)

  -- Populate the selection screen
  local data = {}
  local control = "select_sign_font_file_layer.file_list"
  for i=1,#sign_font_file_list do
    local items = sign_font_file_list[i]:split("/")
    data[control..".text."..i..".1"] = items[#items]
  end

  data[control..".grd_height"] = #sign_font_file_list * gre.get_table_cell_attrs(control, 1, 1, "height").height
  gre.set_table_attrs(control, { rows = #sign_font_file_list })

  gre.set_data(data)

  UpdateSelectionScreenShow("mo_select_sign_font_file")
end

---
--- @brief  Sends the selected sign font file path to the backend.
--- @param  gre#context mapargs
---
function CBSignFontFileSelected(mapargs)
  if ScrollActive() then return end
  SendText("sign_font_file_selected", sign_font_file_list[mapargs.context_row])
  TransitionBackScreen(mapargs)
  sign_font_file_list = nil
end

---
--- @brief  Sends the selected sign font file path to the backend.
---
function CBCancelSignFontFileSelected()
  if sign_font_file_list ~= nil then
    SendText("sign_font_file_selected", "")
    sign_font_file_list = nil
  end
end

---
--- @brief  Hide file copy progress and suppress until the time out occurs 
---
local suppress_file_copy_progress = false
local notification_group
function CBHideFileCopyInProgress(mapargs)
  suppress_file_copy_progress = true
  notification_group = nil
  CBCloseNotification(mapargs)
end

---
--- @brief  On event timeout, stop notification suppression and close the notification 
---
local function CBFileCopyInProgressTimeout()
  in_progress_timer = nil
  suppress_file_copy_progress = false
  if notification_group ~= nil then
    local mapargs = {
      context_layer = "notification_layer",
      context_group = notification_group
    }
    notification_group = nil
    CBCloseNotification(mapargs)
  end
end

---
--- @brief  Process the file copy progress message.
---
function CBNotifyFileCopyProgress(mapargs) 

  if mapargs.context_event_data.text == nil then
    return
  end
  local status = assert(loadstring(mapargs.context_event_data.text))()
  if status == nil then return end
  
  -- Restart the current in progress timer
  -- A message should be received every 250ms
  if in_progress_timer ~= nil then
    gre.timer_clear_timeout(in_progress_timer)
  end
  in_progress_timer = gre.timer_set_timeout(CBFileCopyInProgressTimeout, 2000)

  if suppress_file_copy_progress then return end
  
  if notification_group == nil then
    CBNotify(mapargs)
  else
    mapargs.context_group = notification_group
    CBRestartNoActivityTimer()
  end
  notification_group = mapargs.context_group
  
  gre.set_value(mapargs.context_group..".content.text", status.index .. "/".. status.total)

  local width = gre.get_value(mapargs.context_group..".progress_bar.grd_width")
  width = math.floor(width * (status.index / status.total) + 0.5)
  gre.set_value(mapargs.context_group..".progress_bar.progress_width", width)

end

-- Map of firmware variants to filepaths
local firmware_variants = {}

---
--- @brief  Populate the sign firmware update screen with known signs.
---
--- @param gre#context mapargs
function CBStartSelectSignFirmwareUpdates(mapargs)
  if SystemRebooting() then return end
  if not mapargs.context_event_data.text then return end

  firmware_variants = {}
  for line in mapargs.context_event_data.text:gmatch("[^\r\n]+") do
    local items = line:split(",")
    if #items == 2 then
      firmware_variants[items[1]:upper()] = items[2]
    end
  end

  -- Collect known sign information.
  local signs = GetConnectedSignInfo()
  if #signs == 0 then 
    SendText("sign_firmware_updates_selected", "")
    return 
  end

  -- Sort the matched table.
  table.sort(signs,
    function(a, b)
      a = a.equipment_name or ""
      b = b.equipment_name or ""
      return a < b
    end)

  -- Populate signs checkbox table
  local control = "sign_updates_select_layer.group.items"
  local data = {}
  for row,item in ipairs(signs) do
    local cell = control..".%s."..tostring(row)..".1"
    data[string.format(cell, "text")] = item.equipment_name
    data[string.format(cell, "value")] = item.variant_name or ""
    data[string.format(cell, "end_point")] = string.format("%s,%s,%s", item.end_point.trunk, item.end_point.channel or "", item.end_point.device_id)
    -- Check the item if the firmware is available.
    SetCheckboxValue(control, firmware_variants[item.variant_name] and 1 or 0, row)
  end
  gre.set_data(data)
  gre.set_table_attrs(control, { rows = #signs, height = #signs * 81})
  UpdateSelectionScreenShow("mo_select_sign_updates")
end

---
--- @brief  Send the selected sign firmware filenames and endpoints to the backend.
---
--- @param gre#context mapargs
function CBSubmitSelectSignFirmwareUpdates(mapargs)
  local control = "sign_updates_select_layer.group.items"
  local rows = gre.get_table_attrs(control, "rows" ).rows or 0

  local text = ""
  local sep = ""
  for row=1,rows do
    local cell = control..".%s."..tostring(row)..".1"
    local filepath = firmware_variants[gre.get_value(string.format(cell, "value"))]
    if filepath and gre.get_value(string.format(cell, "checked")) ~= 0 then
      text = text..sep..gre.get_value(string.format(cell, "end_point"))..","..filepath
      sep = "\n"
    end
  end

  if text == "" then
    CBNotify({ context_event = "no_sign_updates_selected" })
  end

  SendText("sign_firmware_updates_selected", text)
  TransitionBackScreen(mapargs)
end

---
--- @brief  Initialise the firmware select screen for a specific sign.
---
--- @param gre#context mapargs
function CBInitSelectSignFirmware(mapargs)
  local row = mapargs.context_row
  local screen = "mo_select_sign_firmware"
  local layer = "select_sign_firmware_layer"
  local control = layer..".group.items"
  local cell = mapargs.context_control..".%s."..tostring(row)..".1"
  local value = gre.get_value(string.format(cell, "value"))
  local data = {
    [screen..".banner_heading"] = gre.get_value(string.format(cell, "text")),
    [screen..".selected_firmware"] = value or "",
    [screen..".selected_row"] = row
  }

  local row = 0  
  for firmware_variant,_ in pairs(firmware_variants) do
    row = row + 1
    local cell = control..".%s."..tostring(row)..".1"
    data[string.format(cell, "text")] = firmware_variant
  end
  gre.set_data(data)
  gre.set_table_attrs(control, { rows = row, height = row * 81})

  -- Set the radio to current value.
  SetRadioCollectionByValue(control, value)
  
  ScreenTransition(screen)
end

---
--- @brief  Checkbox guard to ensure that firmware binary is available when trying to check.
---
--- @param gre#context mapargs
function CBCheckSignFirmwareValid(mapargs)
  local value = gre.get_value(mapargs.context_control .. ".value" .. "." .. mapargs.context_row .. ".1")
  for firmware_variant,_ in pairs(firmware_variants) do
    if firmware_variant == value then return true end
  end
  return false
end

---
--- @brief  Store the selected firmware variant 
---
--- @param gre#context mapargs
function CBSignFirmwareVariantSelected(mapargs)
  local text = gre.get_value(mapargs.context_control..".text."..mapargs.context_row..".1")
  gre.set_value(mapargs.context_screen..".selected_firmware", text)
end

---
--- @brief  Update the firmware variant for the selected sign. 
---
--- @param gre#context mapargs
function CBSubmitSelectSignFirmware(mapargs)
  local row = gre.get_value(mapargs.context_screen..".selected_row")
  local value = gre.get_value(mapargs.context_screen..".selected_firmware")
  local control = "sign_updates_select_layer.group.items"
  gre.set_value(control..".value."..row..".1", value)
  SetCheckboxValue(control, 1, row)
  TransitionBackScreen(mapargs)
end

---
--- @brief This function is called to set the HTC update failure code.
--- @param gre#context mapargs
function CBSetHTCUpdateFailureCode(mapargs)
  local group = "notification_template_layer.process_update_failed"
  local item = gre.get_value(group..".content.item")
  local code = mapargs.context_event_data.text
  local text = gre.get_value(group.."."..code)
  gre.set_value(group..".content.item", text or (item.. " - " .. code))
end

---
--- @brief  Actions taken when reboot on update required event occurs.
--- @param  gre#context mapargs
local reboot_on_update_required_notification
function cb_process_reboot_on_update_required(mapargs)
  if #fileinfo:scandir('/media') ~= 0 then
    if sbsettings.get_beeps_on_usb_update() == 1 then
      CBPlayRemoveUSB()
    else
      CBBeep()
    end
    CBNotify({ context_event = "remove_usb_to_reboot", rebooting = true })
    reboot_on_update_required_notification = true
  else
    CBBeep()
    CBNotify({ context_event = "rebooting_device", rebooting = true })
  end
end

---
--- @brief  Actions taken when continue reboot on update required event occurs.
---         Note: This is triggered on USB removal.
--- @param  gre#context mapargs
function cb_continue_reboot_on_update(mapargs)
  if not reboot_on_update_required_notification then return end
  CBNotify({ context_event = "rebooting_device", rebooting = true })
end


-- EOF --