--[[
  @package    eg4-crank-ui-2
  @file       driver-display.lua
  @brief      Handles updating the driver destination display
]]

-- Local table for storing information about the current destination
local current = {
  destination = {
    code = "",
    driver_message = "",
    route_number = "",
    special_route_number = "",
    source_auto = false,
    present = nil,
  },
  info = {
    driver_message = ""
  },
  driver = {
    pages = {},
    images = {},
    select_active = false,
    index = 1
  },
  substition_text = "",
  select = {},
  scroll_timer = nil,
  freeze_destination_id = nil,
  hogia_available_tile_status = nil
}

-- Tag the indicates that the current page is an image
local INFO_LAYER = "destination_info_layer"

---
--- @brief  Fetches the current destination id.
--- @return string
function get_current_destination_id()
  return current.destination.code
end

---
--- @brief  Returns the current route code
--- @return route code
function current_route_code()
  return get_route_code_len() == 0 and "" or get_current_destination_id():match("^(%w+)")
end

---
--- @brief  Replaces all occurrences of specifier in the string
--- @param  text - string to process
--- @return processed string
function process_substition(text)
  if current.substition_text == "" then
    return text
  end
  text = text:gsub("%"..sbsettings.get_departure_time_specifier(), current.substition_text)
  return text
end

---
--- @brief  Clears the current telegram status if active.
local function clear_telegram()
  if not current.display_telegram then return end
  current.display_telegram = nil
  current.telegram_pending = nil
  current.driver.images = {}
end

---
--- @brief Dump all the existing images
local function dump_driver_page_images()
  for i = 1,#current.driver.images do
    gre.dump_resource("image", current.driver.images[i])
  end
end

---
--- @brief  Parses the driver text into pages. Splits the text
--          as either an image or 2 lines per page.
--- @param  text - text to parse
local function parse_driver_message(text)
  if not text or text == "" then return end
  local lines = text:lines()
  local skip = false
  local ignore_text = false
  for i=1,#lines,2 do
    table.insert(current.driver.pages, process_substition(lines[i] .. "\n" .. (lines[i + 1] or "")))
  end
end

---
--- @brief  Scrolls the driver message box 2 lines at a time. Emulates
---         operation of EG3 and adheres to the general way driver information
---         messages are configured.
---
local function scroll_driver_message_text()
  local group = INFO_LAYER..".current"

  if #current.driver.images ~= 0 and not current.driver.select_active then
    gre.set_value(
      group..".sign.image", current.driver.images[current.driver.index],
      group..".sign.grd_hidden", 0,
      group..".driver_message.grd_hidden", 1)
    current.driver.index = current.driver.index % #current.driver.images + 1
  elseif #current.driver.pages ~= 0 then
    gre.set_value(
      group..".driver_message.text", current.driver.pages[current.driver.index],
      group..".sign.grd_hidden", 1,
      group..".driver_message.grd_hidden", 0)
    current.driver.index = current.driver.index % #current.driver.pages + 1
  else
    gre.set_value(
      group..".sign.grd_hidden", 1,
      group..".driver_message.grd_hidden", 1)
  end
end

---
--- @brief   Restarts the driver page scroll timer
--- @param   selected_driver_message - optional driver message to display
local function restart_driver_message_scroll_timer(selected_driver_message)
  current.driver.pages = {}
  current.driver.index = 1;
  if selected_driver_message then
    current.driver.select_active = true
    parse_driver_message(selected_driver_message)
  else
    current.driver.select_active = false
    parse_driver_message(current.destination.driver_message)
    parse_driver_message(current.info.driver_message)
  end
  if current.scroll_timer then
    gre.timer_clear_interval(current.scroll_timer)
  end
  current.scroll_timer = gre.timer_set_interval(scroll_driver_message_text, 1000)
  scroll_driver_message_text()
end

---
--- @brief   Update the Hogia available tile display
--- @param   manual_active - indicates if manual destination source is active
local function update_hogia_available(manual_active)
  local show_tile = false
  if manual_active and check_hogia_available() then
    show_tile = true
  end
  if show_tile == current.hogia_available_tile_status then
    return
  end
  current.hogia_available_tile_status = show_tile
  if show_tile then
    activate_tile("hogia_available", true)
  else
    deactivate_tile("hogia_available")
  end
end

---
--- @brief   Update the home screen with the new destination information
--- @param   dont_wake - optional flag to prevent waking the display
local function update_driver_display(dont_wake)

  local selected_destination_id, selected_driver_message = get_scroll_selected_destination()

  local manual_active = false
  local text
  if selected_destination_id then
    text = selected_destination_id
  elseif current.display_telegram then
    if check_hogia_present() then
      -- Set text to indicate source as auto when Hogia is present
      text = "("..gre.get_value("destination_info_layer.selected.code.destination_source_auto")..")"
    else
      -- Set text to space to ensure that displaying the telegram is not show an existing destination ID
      text = " "
    end
  else
    text = current.destination.code
    if current.destination.route_number ~= "" then
      text = text.." ("..current.destination.route_number..")"
    end
    if current.destination.special_route_number ~= "" then
      text = text.." ("..current.destination.special_route_number..")"
    end
    
    if current.destination.code ~= "" then
      if current.destination.source_auto then
        text = text.." ("..current.destination.source_auto..")"
      elseif check_manual_required() then
        text = text.." ("..gre.get_value("destination_info_layer.selected.code.destination_source_manual")..")"
        manual_active = true
      end
    end
  end

  update_hogia_available(manual_active)

  local layer = INFO_LAYER
  local data = {
    [layer..".selected.code.text"] = text
  }

  restart_driver_message_scroll_timer(selected_driver_message)

  local driver_message_not_present = (text == "" and #current.driver.pages == 0) and 1 or 0

  data[layer..".current.grd_hidden"] = driver_message_not_present
  data[layer..".no_data.grd_hidden"] = (get_destinations_present() ~= 0) or (text ~= "")
  data[layer..".no_destination.grd_hidden"] = (get_destinations_present() == 0) or (text ~= "")
  data[layer..".destination_freeze_border.grd_hidden"] = current.freeze_destination_id == nil
  data[layer..".current.cancel.grd_hidden"] = (current.display_telegram or get_destination_clear_control_mode() == 0) and 1 or 0

  if text == "" then
    data[layer..".selected.grd_hidden"] = 1
  else
    data[layer..".selected.grd_hidden"] = 0

    local outbound_active
    local inbound_active
    if local_round_trip_active() then
      local round_trip = sbsettings.get_destination_round_trip():lines()
      outbound_active = round_trip[1] == get_current_destination_id()
      inbound_active = round_trip[2] == get_current_destination_id()
    end
    data[layer..".selected.out.grd_hidden"] = (outbound_active or inbound_active) and 0 or 1
    data[layer..".selected.return.grd_hidden"] = (outbound_active or inbound_active) and 0 or 1
    data[layer..".selected.up.grd_hidden"] = (current.display_telegram or outbound_active or inbound_active) and 1 or 0
    data[layer..".selected.down.grd_hidden"] = (current.display_telegram or outbound_active or inbound_active) and 1 or 0
    data[layer..".selected.out.alpha_disabled"] = inbound_active and 255 or 0
    data[layer..".selected.out.grd_active"] = inbound_active and 1 or 0
    data[layer..".selected.return.alpha_disabled"] = outbound_active and 255 or 0
    data[layer..".selected.return.grd_active"] = outbound_active and 1 or 0
    data[layer..".selected.sep.grd_hidden"] = current.display_telegram and 1 or 0
  end

  if get_destinations_present() == 0 or get_use_home_code_browse_controls() == 0 then
    data[layer..".select.grd_hidden"] = 1
  else
    if driver_message_not_present == 1 then
      data[layer..".select.grd_hidden"] = 0
    else
      data[layer..".select.grd_hidden"] = current.select.item and 0 or 1
    end
  end
  data[layer..".select.round_trip.grd_hidden"] = (current.select.item == "round_trip") and 0 or 1
  data[layer..".select.destination.grd_hidden"] = (current.select.item == "destination" or current.select.item == nil) and 0 or 1
  data[layer..".select.information.grd_hidden"] = (current.select.item == "information") and 0 or 1

  gre.set_data(data)

  dump_driver_page_images()

  if not dont_wake and get_wake_on_remote_action() == 1 then
    cb_activity_monitoring_restart()
  end

  gre.redraw()
end

---
--- @brief  Process the display content event.
--- @param  mapargs - callback arguments
function cb_display_content(mapargs)
  if mapargs.context_event_data.text == nil then return end

  if current.telegram_pending then
    current.telegram_pending = nil
    current.display_telegram = true
  end

  current.driver.images = mapargs.context_event_data.text:remove_trailing("\n"):split("\n")
  -- Update the driver display, but do not wake the screen as this may occur repeatedly when showiung renders from a sign
  update_driver_display(true)
end

---
--- @brief  Process the bad destination id message.
local bad_destination_notification_group
function cb_notify_bad_destination_id(mapargs)
  local group = cb_notify(mapargs)
  bad_destination_notification_group = group
  gre.set_value(group..".status.text", mapargs.text or (mapargs.context_event_data and mapargs.context_event_data.text) or "")
end

---
--- @brief  Closes the bad destination dialog
---
local function close_bad_destination_notification()
  if bad_destination_notification_group == nil then return end
  cb_close_notification({ context_group = bad_destination_notification_group })
  bad_destination_notification_group = nil
end

---
--- @brief   Processes the driver destination event.
function cb_set_driver_destination_message(mapargs)
  clear_telegram()
  local text = mapargs.context_event_data.text:parse_text_for_code_page()
  add_recent_list(text)
  -- TODO: SetToXYControl(text)
  local items = text:split("\n", 1)
  if #items > 1 then close_bad_destination_notification() end
  current.destination.code = #items > 0 and items[1] or ""
  current.destination.driver_message = #items > 1 and items[2] or ""
  current.driver.images = {}
  update_driver_display()
end

---
--- @brief   Processes the destination destination source to local event.
function cb_set_destination_source_local(mapargs)
  if not current.destination.source_auto then return end
  current.destination.source_auto = nil
  update_driver_display()
  cb_activity_monitoring_restart()
end

---
--- @brief   Processes the destination destination source to remote event.
function cb_set_destination_source_remote(mapargs)
  clear_local_round_trip()
  if current.destination.source_auto then return end
  -- TODO: ClearRoundTripActive()
  current.destination.source_auto = gre.get_value("destination_info_layer.selected.code.destination_source_auto")
  update_driver_display()
end

---
--- @brief  Processes the drive information event.
function cb_driver_info_message(mapargs)
  local driver_info_message = mapargs.context_event_data.text:parse_text_for_code_page()
  if current.info.driver_message == driver_info_message then return end
  current.info.driver_message = driver_info_message
  update_driver_display()
end

---
--- @brief  Process the route number event.
function cb_route_number_changed(mapargs)
  current.destination.route_number = mapargs.context_event_data.text
  update_driver_display()
end

---
--- @brief  Process the special route number event.
function cb_special_route_number_changed(mapargs)
  current.destination.special_route_number = mapargs.context_event_data.text
  update_driver_display()
end

---
--- @brief  Process the substitution text event.
function cb_update_substition_text(mapargs)
  current.substition_text = mapargs.context_event_data.text
  set_departure_time(current.substition_text)
  update_driver_display()
end

---
--- @brief  Forces update for the destinations present status.
function update_destinations_display()
  update_driver_display()
end

---
--- @brief  Update the item select display
--- @param  item - item being selected
--- @param  value - value to set or nil to toggle
local function update_select(item, value)
  if current.select.default == nil then
    -- Store the default functions
    current.select.default = {
      code = gre.get_value(INFO_LAYER..".select.code.function"),
      browse = gre.get_value(INFO_LAYER..".select.browse.function")
    }
  else
    -- Restore the default functions
    gre.set_value(
      INFO_LAYER..".select.code.function", current.select.default.code,
      INFO_LAYER..".select.browse.function", current.select.default.browse)
  end

  if value == nil then
    if current.select.item ~= item then
      current.select.item = item
    else
      current.select.item = nil
    end
  elseif type(value) == "boolean" then
    if value then
      current.select.item = item
    else
      current.select.item = nil
    end
  end
  update_driver_display()
  return current.select.item == item
end

---
--- @brief  Handle the item select toggle with timeout
--- @param  item - item being selected
--- @param  code - function to call for the code button
--- @param  browse - function to call for the browse button
--- @param  selection_mode - selection mode
function toggle_select(mapargs, item, code, browse, selection_mode)
  if current.select.entry_mode_timer then
    gre.timer_clear_timeout(current.select.entry_mode_timer)
    current.select.entry_mode_timer = nil
  end
  if update_select(item) then
    gre.set_value(
      INFO_LAYER..".select.code.function", code,
      INFO_LAYER..".select.browse.function", browse)
    current.select.entry_mode_timer = gre.timer_set_timeout(function()
      update_select(item, false)
      current.select.entry_mode_timer = nil
    end, 5000)
  end

  if get_use_home_code_browse_controls() == 0 then 
    if selection_mode == 1 then
      _G[browse](mapargs)
    else
      _G[code](mapargs)
    end
    return
  end
end

---
--- @brief  Hides the destination select buttons and updates the driver display
function cb_driver_display_show()
  update_select()
end

---
--- @brief   Indicates if the freeze destination button should be shown
function show_freeze_destination()
  return sbsettings.get_destination_freeze_enabled() == 1
end

---
--- @brief   Action for freeze destination in force event
function cb_freeze_destination_in_force(mapargs)
  current.freeze_destination_id = mapargs.context_event_data.text
  local control = "status_tiles_layer.destination_freeze.message"
  gre.set_value(control..".text", string.format(gre.get_value(control..".format"), current.freeze_destination_id))
  activate_tile("destination_freeze", true)
  update_driver_display()
  end

---
--- @brief   Action for freeze destination in force event
function cb_destination_freeze_stopped(mapargs)
  current.freeze_destination_id = nil
  deactivate_tile("destination_freeze")
  update_driver_display()
end

---
--- @brief  Process the display telegram event.
function cb_display_telegram()
  if current.display_telegram then return end
  current.telegram_pending = true
  update_driver_display()
end

---
--- @brief  Process the telegram cancelled event.
function cb_telegram_cancelled()
  clear_telegram()
  update_driver_display()
end
