--[[
  @package    eg4-crank-ui-2
  @file       destination-select.lua
  @brief      Handles the user destination selection functions
]]

-- Local table for storing information
local info = {
  select = {},
  navigate = nil,
  local_round_trip = true,
}


---
--- @brief  Clears the local round trip destination
function clear_local_round_trip()
  info.local_round_trip = nil
end

---
--- @brief  Sets the local round trip destination
function set_local_round_trip()
  info.local_round_trip = true
end

---
--- @brief  Sets the local round trip destination
function local_round_trip_active()
  return info.local_round_trip
end

---
--- @brief  Indicate that the destination icon is visible
--- @return true - show destination control
function show_destination()
  return sbsettings.get_allow_control_of_destination() == 1
end

---
--- @brief  Indicate that the destination round trip icon is visible
--- @return true - show icon
function show_round_trip()
  return (sbsettings.get_allow_control_of_destination() == 1) and (sbsettings.get_round_trip_enable() == 1)
end

---
--- @brief  Indicate that the destination icon is visible
--- @return true - show destination control
function active_destination()
  return get_destinations_present() == 1
end

---
--- @brief  Fetch length of route and destination code
--- @return length destination id plus space separator if required
function get_destination_id_len()
  if get_route_code_len() == 0 then
    return get_destination_code_len()
  end
  return get_destination_code_len() + get_route_code_len() + 1
end

---
--- @brief  Check if round trip selection is in progress
--- @return true - in progress
function select_round_trip_active()
  return info.select.round_trip
end

---
--- @brief  Fetch selected round trip out destination
--- @return value or nil
function select_round_trip_out()
  return select_round_trip_active() and info.select.round_trip_out
end

---
--- @brief  initialises destination selection
--- @param  mapargs - gre#context
--- @param  by_browse - true if selection is by browse
local function init_destination_selection(mapargs, by_browse)
  -- Only update round trip if on the home screen or round trip is not present
  local round_trip = info.select.round_trip
  if mapargs.context_screen == "home" then
    round_trip = gre.get_value("destination_info_layer.select.round_trip.grd_hidden") == 0
  end

  info.select = {
    by_browse = by_browse,
    round_trip = round_trip
  }
end

---
--- @brief  Fetches the currently selected route.
--- @return route code or ""
function selected_route_code()
  return info.select.route_code_entry_value or ""
end

---
--- @brief  Trigger destination change event automatically while in test mode.
--- @param  value - text for destination
local function test_set_destination_id(value)
  if not sbsettings.test_mode then return end
  if value ~= "" then
    local route_code, destination_code, destination_id = parse_destination_id(value)
    value = destination_id.."\n"..sbsettings.get_destination_driver_message(route_code, destination_code)
  end
  gre.send_event_data("set_destination_driver_message", "1s0 text", { text = value })
end

---
--- @brief  Sends out the selected destination ID event to backend
--- @param  destination_id - text for destination
local function set_destination_id(destination_id)
  send_event("set_destination_id", destination_id)
  test_set_destination_id(destination_id)
end

---
--- @brief  Sends out the selected destination ID event to backend
--- @param  destination_id - text for destination
local function set_destination_id_local(destination_id)
  -- Set the last local value stripped of the driver message
  destination_id = destination_id:trim_destination_id()
  clear_local_round_trip()
  sbsettings.set_destination_local_last(destination_id)
  set_destination_id(destination_id)
end

---
--- @brief  Sends out the selected destination ID event to backend
--- @param  destination_id - text for destination
local function set_round_trip_id_local(round_trip_out, round_trip_return)
  -- Set the last local value stripped of the driver message
  round_trip_out = round_trip_out:trim_destination_id()
  round_trip_return = round_trip_return:trim_destination_id()
  sbsettings.set_destination_round_trip(round_trip_out.."\n"..round_trip_return)
  set_local_round_trip()
  set_destination_id(round_trip_out)
end

---
--- @brief  Trigger destination change event automatically while in test mode.
--- @param  value - text for destination
local function test_set_destination_text(value)
  if not sbsettings.test_mode then return end
  gre.send_event_data("set_destination_driver_message", "1s0 text", { text = "\n" .. value })
end

---
--- @brief  Sends out the selected destination as a destination text event to backend
--- @param  destination_id - text for destination
local function set_destination_text(destination_id)
  send_event("set_destination_text", destination_id)
  test_set_destination_text(destination_id)
end

---
--- @brief  Processes the pressing of the home screen main panel when no destination is selected
--- @param  mapargs - gre#context
function cb_no_destination_select(mapargs)
  cb_toggle_destination_select(mapargs)
end

---
--- @brief  Processes the destination select icon on the home screen
--- @param  mapargs - gre#context
function cb_toggle_destination_select(mapargs)
  if get_destinations_present() == 0 then return end

  -- Populate the route and destination code entry start values with the last local destination
  local destination_id = sbsettings.get_destination_local_last()
  local route_code, destination_code = parse_destination_id(destination_id)
  gre.set_value("select_route_code.start_value", route_code)
  gre.set_value("select_destination_code.start_value", destination_code)

  toggle_select(mapargs, "destination", "cb_start_destination_entry", "cb_start_destination_browse", sbsettings.get_destination_selection_mode())
end

---
--- @brief  Processes the pressing of the home screen main panel when a destination or round trip is selected
---         which times out after 5 seconds
function cb_home_destination_select(mapargs)
  if get_destinations_present() == 0 then return end

  -- Check if round trip matched and toggle.
  if local_round_trip_active() then
    local round_trip = sbsettings.get_destination_round_trip():lines()
    if #round_trip == 2 then
      local current_id = get_scroll_selected_destination() or get_current_destination_id()
      if round_trip[1] == current_id then
        set_destination_id(round_trip[2])
        return
      elseif round_trip[2] == current_id then
        set_destination_id(round_trip[1])
        return
      end
    end
  end

  cb_toggle_destination_select(mapargs)
end

---
--- @brief  toggles the code/browse button display on the home screen
---         which times out after 5 seconds
function cb_toggle_round_destination_select(mapargs)
  if get_destinations_present() == 0 then return end

  local route_code, destination_code

  local round_trip = sbsettings.get_destination_round_trip():lines()
  if #round_trip == 2 then
    route_code, destination_code = parse_destination_id(round_trip[1])
    if route_code and parse_destination_id(round_trip[2]) then
      if not local_round_trip_active() then
        local destination_id = sbsettings.get_destination_local_last()
        local current_id = get_current_destination_id()
        if destination_id == current_id then

          cb_driver_display_show()

          -- If the timer is active, clear it  and store the current navigated item.
          if info.navigate and info.navigate.timer_id then
            gre.timer_clear_timeout(info.navigate.timer_id)
            sbsettings.set_destination_local_last(get_destination_list_item(info.navigate.index):trim_destination_id())
            info.navigate = nil
          end
          set_round_trip_id_local(round_trip[1], round_trip[2])
          return
        end
      end
    end
  end

  gre.set_value("select_route_code.start_value", route_code or "")
  gre.set_value("select_destination_code.start_value", destination_code or "")

  toggle_select(mapargs, "round_trip", "cb_start_destination_entry", "cb_start_destination_browse", sbsettings.get_destination_selection_mode())
end

---
--- @brief  Move/Scroll to next destination
--- @param  dir - direction to move
local function move_to_next_destination(dir)
  -- Only update the list if more that 5s since last navigation occurred.
  if info.navigate == nil or info.navigate.index == nil then
    info.navigate = {}
    info.navigate.size, info.navigate.index = update_destination_list(current_route_code(), "", get_current_destination_id())
    if info.navigate.size == 0 then return end
    if info.navigate.index == nil then info.navigate.index = 0 end
  end

  info.navigate.index = info.navigate.index + dir
  if info.navigate.index == 0 then info.navigate.index = info.navigate.size
  elseif info.navigate.index > info.navigate.size then info.navigate.index = 1 end

  if info.navigate.timer_id then
    gre.timer_clear_timeout(info.navigate.timer_id)
  end
  info.navigate.timer_id = gre.timer_set_timeout(function()
    set_destination_id_local(get_destination_list_item(info.navigate.index))
    info.navigate = nil
  end, 1500)

  update_destinations_display()
end

---
--- @brief  Select the next destination list up/down.
function cb_destination_navigate(mapargs)
  move_to_next_destination(mapargs.dir)
end

---
--- @brief  Select the next destination list up/down.
function cb_destination_navigate_round_trip_out(mapargs)
  local round_trip = sbsettings.get_destination_round_trip():lines()
  if #round_trip == 2 then
    set_destination_id(round_trip[1])
  end
end

---
--- @brief  Select the next destination list up/down.
function cb_destination_navigate_round_trip_return(mapargs)
  local round_trip = sbsettings.get_destination_round_trip():lines()
  if #round_trip == 2 then
    set_destination_id(round_trip[2])
  end
end

---
--- @brief Fetch the destination ID and driver message for the currently selected scroll item
--- @return destination_id, driver_message
function get_scroll_selected_destination()
  if not info.navigate then return end
  local item = get_destination_list_item(info.navigate.index)
  if not item then return end
  local destination_id = item:trim_destination_id()
  return destination_id, item:sub(destination_id:len() + 2)
end

---
--- @brief  Validates the route code value as it is entered into the text field.
--- @param  value - Current text entered for the route code
--- @param  context_screen - The context screen
--- @return true if the value is valid, nil otherwise
function cb_validate_route_code_entry(value, context_screen)
  update_extended_keys_for_numeric_entry({ context_layer = "code_entry_layer", context_screen = context_screen, value = value })
  return value == "" or sbsettings.validate_route_code(value)
end

---
--- @brief  Validates the destination code value as it is entered into the text field.
--- @param  value - Current text entered for the destination code
--- @param  context_screen - The context screen
--- @return true if the value is valid, nil otherwise
function cb_validate_destination_code_entry(value, context_screen)
  update_extended_keys_for_numeric_entry({ context_layer = "code_entry_layer", context_screen = context_screen, value = value })
  return value == "" or sbsettings.validate_destination_id(selected_route_code(), value) or tonumber(value) == 0
end

---
--- @brief  Checks and submits the selected route code storing it in the info table.
--- @param  value - route code
function cb_route_code_submit(value)
  if value == "" then return end
  value = string.rep("0", get_route_code_len() - #value)..value
  if not sbsettings.validate_route_code(value) then
    cb_notify_bad_destination_id({
      event = "bad_destination_id",
      text = value,
      timeout = 1000
    })
    return
  end
  info.select.route_code_entry_value = value
  screen_transition("browse_destinations")
  return true
end

---
--- @brief  Checks and submits the selected destination code and sends it to the backend.
--- @param  value - destination code
function cb_destination_code_submit(value)
  if value == "" then return end

  local route_code,destination_code,destination_id
  if get_use_destination_code_as_sign_data() == 0 and selected_route_code() == "" and tonumber(value) == 0 then
    -- Do not attempt to parse destination code 0. This should be sent to the backend as is.
    route_code = selected_route_code()
    destination_code = string.rep("0", get_destination_code_len() - value:len())..value
    destination_id = value
  else
    route_code,destination_code,destination_id = parse_destination_id(tostring(selected_route_code().." "..value):trim())
    if destination_code == nil then
      cb_notify_bad_destination_id({
        event = "bad_destination_id",
        text = selected_route_code().." "..value,
        timeout = 1000
      })
      return
    end
  end

  if info.select.round_trip then
    if info.select.round_trip_out then
      set_round_trip_id_local(info.select.round_trip_out, destination_id)
    else
      info.select.round_trip_out = destination_id

      gre.set_value("select_route_code.start_value", route_code or "")
      local round_trip = sbsettings.get_destination_round_trip():lines()
      local return_route_code, return_destination_code = parse_destination_id(round_trip[2])
      gre.set_value("select_destination_code.start_value", return_route_code == route_code and return_destination_code or "")

      local screen = get_route_code_len() == 0 and "select_destination_code" or "select_route_code"
      if gre.env("active_screen") == screen then
        cb_init_route_destination_code_entry({ context_screen = screen })
      else
        screen_transition(screen)
      end
      return true
    end
  else
    set_destination_id_local(destination_id)
  end

  screen_transition_idle()
  return true
end

---
--- @brief  Start the destination entry process
--- @param  mapargs - gre#context
function cb_start_destination_entry(mapargs)
  init_destination_selection(mapargs)
  start_driver_login(sbsettings.get_destination_requires_passcode(), get_route_code_len() == 0 and "select_destination_code" or "select_route_code")
end

---
--- @brief  Initialise the display text entry
--- @param  mapargs - gre#context
function cb_init_route_destination_code_entry(mapargs)
  if info.select.round_trip then
    if info.select.round_trip_out then
      gre.set_value(mapargs.context_screen..".box_request", gre.get_value(mapargs.context_screen..".box_request_return"))
    else
      gre.set_value(mapargs.context_screen..".box_request", gre.get_value(mapargs.context_screen..".box_request_out"))
    end
  else
    gre.set_value(mapargs.context_screen..".box_request", gre.get_value(mapargs.context_screen..".box_request_dest"))
  end
  cb_reset_text_entry(mapargs)
end

---
--- @brief  Start the destination browse process
--- @param  mapargs - gre#context
function cb_start_destination_browse(mapargs)
  init_destination_selection(mapargs, true)
  cb_initialise_destination_list()
  start_driver_login(sbsettings.get_destination_requires_passcode(), "browse_destinations")
end

---
--- @brief  Parses and validates a destination ID string
--- @param  value - string to parse
--- @return nil or route code, destination code, regularised value
function parse_destination_id(value)
  if not value or value == "" then return end

  local route_code
  local destination_code
  if get_route_code_len() > 0 then
    route_code, destination_code = value:match("^(%w+) (%w+)")
    if route_code == nil then return end
  else
    route_code = ""
    destination_code = value:match("^(%w+)")
  end
  if destination_code == nil or destination_code == "" then return end

  if not sbsettings.check_destination_id(route_code, destination_code) then
    if get_use_destination_code_as_sign_data() == 0 then return end
    -- return destination unpadded
    return route_code, destination_code, route_code..(route_code == "" and "" or " ")..destination_code, true
  end

  route_code = string.rep("0", get_route_code_len() - route_code:len())..route_code
  destination_code = string.rep("0", get_destination_code_len() - destination_code:len())..destination_code
  return route_code, destination_code, route_code..(route_code == "" and "" or " ")..destination_code
end

---
--- @brief  Clears the destination
---
function clear_destination()
  test_set_destination_id("")
  send_event("clear_destination_id")
end

---
--- @brief  Clear the destination and route numbers
--
function clear_destination_and_route_number()
  send_event("set_special_route_number", "")
  send_event("set_route_number", "")
  send_event("cancel_telegram")
  clear_destination()
end

---
--- @brief  Call back to send requests to clear the destination
---
function cb_clear_destination()
  clear_destination()
  screen_transition_idle()
end

---
--- @brief  Call back to send requests to clear the destination and route numbers
--
function cb_clear_destination_and_route_number()
  -- If round trip is active, go to last local destination
  if local_round_trip_active() then
    local current_id = get_current_destination_id()
    local round_trip = sbsettings.get_destination_round_trip():lines()
    if round_trip[1] == current_id or round_trip[2] == current_id then
      local destination_id = sbsettings.get_destination_local_last()
      if destination_id ~= "" then
        set_destination_id_local(destination_id)
        return
      end
    end
  end
  clear_destination_and_route_number()
  screen_transition_idle()
end

---
--- @brief  Call back to clear all destinations and info messages
--
function cb_clear_all_destination_and_info(mapargs)
  cb_clear_destination_and_route_number(mapargs)
  cb_clear_all_info_messages(mapargs)
end

---
--- @brief  Call back to start options query to clear destination and info messages
--
function cb_query_clear_all_destination_and_info(mapargs)
  if get_destination_clear_control_mode() == 2 then
    notify("confirm_destination_clear")
  else
    cb_clear_all_destination_and_info()
  end
end

---
-- @brief  Callback for setting destination
--- @param gre#context mapargs
--
function cb_set_destination(mapargs)
  local value = gre.get_value(mapargs.context_control..".destination_id."..mapargs.context_row..".1"):trim_destination_id()
  if info.select.round_trip then
    if not info.select.round_trip_out then
      info.select.round_trip_out = value
      if get_route_code_len() == 0 or info.select.by_browse then
        cb_initialise_destination_list()
      else
        gre.set_value("select_route_code.start_value", value:match("^(%w+)"))
        screen_transition("select_route_code")
      end
      return
    end
    set_round_trip_id_local(info.select.round_trip_out, value)
  else
    set_destination_id_local(value)
  end
  screen_transition_idle()
end

---
--- @brief  Check if round trip feature enabled and if round trip active, cancel it.
function update_round_trip()
  if show_round_trip() then return end
  if not local_round_trip_active() then return end
  local current_id = get_current_destination_id()
  local round_trip = sbsettings.get_destination_round_trip():lines()
  if round_trip[1] == current_id or round_trip[2] == current_id then
    cb_clear_destination_and_route_number()
  end
end

---
--- @brief  Start the destination code entry or browse
--- @param  mapargs - table containing the map arguments
function cb_icons_menu_destination(mapargs)
  info.select.round_trip = tonumber(mapargs.round_trip) == 1
  if sbsettings.get_destination_selection_mode() == 1 then
    cb_start_destination_browse(mapargs)
  else
    cb_start_destination_entry(mapargs)
  end
end
