---
--- @package   eg4-crank-ui
--- @file      driver-information.lua
--- @brief     functionality related showing driver information on the home screen 
---
--- @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 destinations_present
local current_destination_code_text = ""
local current_destination_driver_text = ""
local current_info_driver_text = ""
local current_route_number = ""
local current_special_route_number = ""
local destination_source_auto
local RECENT_LIST_CONTROL = "recent_destination_list_layer.recent_list"
local MAX_RECENT_LIST_DEPTH = 10
local recent_list_filepath = nil
local freeze_destination_id

local current_driver_pages = {}
local current_driver_images = {}
local current_driver_select_active = false
local current_driver_index = 1

local tile_timer
local tile_layers = {}
local TILE_UPDATE_FREQ    = 25
local TILE_DURATION_MS    = 400
local TILE_UPDATE_STEPS   = math.floor((TILE_DURATION_MS * TILE_UPDATE_FREQ) / 1000)
local TILE_UPDATE_INC     = math.floor(854 / TILE_UPDATE_STEPS)

local substition_text = ""
local telegram_pending = nil
local display_telegram = nil


---
--- @brief  Fetches the current route and destination codes 
--- @return route code, destination code
---
function GetCurrentDestinationID()
  return current_destination_code_text
end

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

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

---
--- @brief  Parses the driver text into pages. Splits the text 
--          as either an image or 2 lines per page.
--- @param  pages - pages to update
--- @param  text - text to parse
---
local function ParseDriverText(pages, text)
  local lines = text:split("\n")
  for i = 1,#lines,2 do
    table.insert(pages, ProcessSubstition(lines[i] .. "\n" .. (lines[i + 1] or "")))
  end
end

---
--- @brief  updates the driver information screen with the destination and information messages.
--- @param  dont_wake - set to true to prevent the screen from waking up.
---
local function UpdateDriverDisplay(dont_wake)

  local selected_destination_id, selected_driver_message = GetScrollSelectedDestination()

  local text
  if selected_destination_id then
    text = selected_destination_id
  elseif 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_text
    if current_route_number ~= "" then
      text = text.." ("..current_route_number..")"
    end
    if current_special_route_number ~= "" then
      text = text.." ("..current_special_route_number..")"
    end
    if current_destination_code_text ~= "" then
      if destination_source_auto then
        text = text.." ("..destination_source_auto..")"
      elseif check_manual_required() then
        text = text.." ("..gre.get_value("destination_info_layer.destination_info_group.source_manual")..")"
      end
    end
  end

  local context_group = "destination_info_layer.destination_info_group"
  local data = {
    [context_group..".destination_code_control.text"] = text,
    [context_group..".background.alpha"] = freeze_destination_id and 64 or 0
  }

  destinations_present = destinations_present or sbsettings.get_destinations_present()
  if destinations_present == 1 then
    data[context_group..".no_data.grd_hidden"] = 1
    if text == "" then
      if freeze_destination_id then
        data[context_group..".destination_freeze.grd_hidden"] = 0
        data[context_group..".no_destination_prompt.grd_hidden"] = 1
        data[context_group..".destination_code_control.grd_hidden"] = 1
        data["clear_destination_layer.clear_control.grd_hidden"] = 1
        data[context_group..".clear_destination.grd_hidden"] = 0
        data[context_group..".clear_destination_border.grd_hidden"] = 0
      else
        data[context_group..".destination_freeze.grd_hidden"] = 1
        data[context_group..".no_destination_prompt.grd_hidden"] = 0
        data[context_group..".destination_code_control.grd_hidden"] = 1
        data["clear_destination_layer.clear_control.grd_hidden"] = 1
        data[context_group..".clear_destination.grd_hidden"] = 1
        data[context_group..".clear_destination_border.grd_hidden"] = 1
      end
    else    
      data[context_group..".destination_freeze.grd_hidden"] = 1
      data[context_group..".no_destination_prompt.grd_hidden"] = 1
      data[context_group..".destination_code_control.grd_hidden"] = 0
      data["clear_destination_layer.clear_control.grd_hidden"] = 0
      local destination_clear_control_mode = get_destination_clear_control_mode() == 0 and 1 or 0
      data[context_group..".clear_destination.grd_hidden"] = destination_clear_control_mode
      data[context_group..".clear_destination_border.grd_hidden"] = destination_clear_control_mode
    end
  else
    data[context_group..".destination_freeze.grd_hidden"] = 1
    data[context_group..".no_data.grd_hidden"] = 0
    data[context_group..".no_destination_prompt.grd_hidden"] = 1
    data[context_group..".destination_code_control.grd_hidden"] = 1
    data["clear_destination_layer.clear_control.grd_hidden"] = 1
    data[context_group..".clear_destination.grd_hidden"] = 1
    data[context_group..".clear_destination_border.grd_hidden"] = 1
  end
  gre.set_data(data)

  -- Dump all the existing images
  if current_driver_images ~= nil then
    for i = 1,#current_driver_images do
      gre.dump_resource("image", current_driver_images[i])
    end
  end

  current_driver_pages = {}
  if selected_driver_message then
    current_driver_select_active = true
    ParseDriverText(current_driver_pages, selected_driver_message)
  else
    current_driver_select_active = false
    ParseDriverText(current_driver_pages, current_destination_driver_text)
    ParseDriverText(current_driver_pages, current_info_driver_text)
  end
  current_driver_index = 1;
  
  CBScrollDriverMessageText(mapargs)

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

  gre.redraw()    
end

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

  if telegram_pending then
    telegram_pending = nil
    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
  UpdateDriverDisplay(true)
end

---
--- @brief  Forces update for the destinations present status.
---
function UpdateDestinationsPresent()
  destinations_present = nil
  UpdateDriverDisplay()
end

---
--- @brief  Update the current information driver message.
--- @param  text - new value
---
function UpdateDriverInformationMessage(text)
  current_info_driver_text = text
  UpdateDriverDisplay()
end

---
--- @brief  Process the bad destination id message.
---
local bad_destination_notification
function CBNotifyBadDestinationID(mapargs) 
  if mapargs.context_event_data.text == nil then
    return
  end
  CBNotify(mapargs)  
  gre.set_value(mapargs.context_group..".content.text", mapargs.context_event_data.text)
  bad_destination_notification = {
    context_layer = mapargs.context_layer,
    context_group = mapargs.context_group
  }
end

---
--- @brief  Closes the bad destination dialog
---
local function CloseBadDestinationDialog()
  if bad_destination_notification == nil then return end
  CBCloseNotification(bad_destination_notification)
  bad_destination_notification = nil
end

---
--- @brief  Update the current destination driver message.
--- @param  text - new value
---
local function UpdateDestination(text)
  -- Close dialog if destination active.
  if text ~= "" then
    CloseBadDestinationDialog()
  end

  current_destination_driver_text = text
  current_driver_images = {}
  UpdateDriverDisplay()
end

---
--- @brief  Process the substitution text update
--- @param gre#context mapargs
function CBUpdateSubstitionText(mapargs) 
  substition_text = mapargs.context_event_data.text
  SetDepartureTime(substition_text)
  UpdateDriverDisplay()
end

---
-- @brief  Reads the recent destination list from the specified file.
--
local function ReadRecentList()
  if recent_list_filepath ~= nil then return end
  recent_list_filepath = DATA_FOLDER .. "recent-list-destination-id"
  local control = RECENT_LIST_CONTROL
  local file = io.open(recent_list_filepath, "r")
  if file ~= nil then
    local data = {}
    local rows = 0
    for line in file:lines() do
      if line ~= "" then
        rows = rows + 1
        data[control..".text."..rows..".1"] = line
      end
    end
    file:close()
    data[control..".grd_height"] = rows * gre.get_table_cell_attrs(control, 1, 1,"height").height
    gre.set_data(data)
    gre.set_table_attrs(control, { rows = rows })
  else
    gre.set_data({ [control..".grd_height"] = 0 })
  end
end

---
--- @brief  Get the number of recent items
---
local function GetQtyRecentItems()
  local control = RECENT_LIST_CONTROL
  if gre.get_value(control..".grd_height") == 0 then return 0 end
  return gre.get_table_attrs(control, "rows").rows
end

---
--- @brief  Write the recent list to file
---
local function WriteRecentList()
  if recent_list_filepath == nil then return end
  local file = io.open(recent_list_filepath, "w")
  if file ~= nil then
    local control = RECENT_LIST_CONTROL
    for i=1,GetQtyRecentItems() do
      local item = gre.get_value(control..".text."..i..".1")
      file:write(item .. "\n")
    end
    file:close()
  end
end

---
--- @brief  Clean the recent list 
---
local function CleanRecentList()
  ReadRecentList()

  local control = RECENT_LIST_CONTROL
  local data = {}
  local rows = GetQtyRecentItems()
  local index = 0
  for i = 1, rows do
    local item = gre.get_value(control..".text."..i..".1")
    if ValidateRecentItem(item) then
      index = index + 1
      data[control..".text."..index..".1"] = item
    end  
  end
  
  if index ~= rows then 
    data[control..".grd_height"] = index * gre.get_table_cell_attrs(control, 1, 1,"height").height
    gre.set_data(data)
    gre.set_table_attrs(control, { rows = index })
    WriteRecentList() 
  end
end

--- 
-- @brief  Adds value text to the recent lists file, removing the oldest item if required.
-- @param  value
--
local function DoAddRecentList(value)
  local control = RECENT_LIST_CONTROL
  local rows = GetQtyRecentItems()
  if rows >= MAX_RECENT_LIST_DEPTH then rows = MAX_RECENT_LIST_DEPTH - 1 end

  -- Remove image line from 
  value = value:remove_trailing("\n")

  -- Modify string to make it TAB delimited.
  local items = value:split("\n")
  local count = 0
  local sep = ""
  value = ""
  for i=1,#items do
    value = value..sep..items[i]
    if sep == "" then sep = "  " else sep = " " end
    count = count + 1
    if count == 3 then break end
  end

  -- Reject invalid destination IDs
  if not ValidateRecentItem(value) then
    return
  end

  if recent_list_filepath == nil then
    ReadRecentList()
  end

  -- Add value to recent list
  local data = {}
  local index = 1
  data[control..".text.1.1"] = value
  for i=1,rows do
    local item = gre.get_value(control..".text."..i..".1")
    if item ~= value then
      index = index + 1
      data[control..".text."..index..".1"] = item
    end
  end  
  data[control..".grd_height"] = index * gre.get_table_cell_attrs(control, 1, 1,"height").height
  gre.set_data(data)
  gre.set_table_attrs(control, { rows = index })
  
  WriteRecentList()
end

---
--- @brief  Clear the recent list
function ClearRecentDestinationsList()
  gre.set_value(RECENT_LIST_CONTROL..".grd_height", 0)
  if recent_list_filepath then
    os.remove(recent_list_filepath)
    recent_list_filepath = nil
  end
end

---
--- @brief  Adds value text to the recent lists file, removing the oldest item if required.
--- @param  value
---
local recent_delay_timer
local function AddRecentList(value)
  if not value or value == "" then return end
  
  -- Do not add this item to the recent list unless a new item has not been received for 3 seconds
  if recent_delay_timer then
    gre.timer_clear_timeout(recent_delay_timer)
  end
  recent_delay_timer = gre.timer_set_timeout(function()
    DoAddRecentList(value)
    recent_delay_timer = nil
  end, 3000)
end
  
---
--- @brief  Call back to set the destination driver message from received event
--- @param  mapargs - callback arguments
---
function CBSetDriverDestinationMessage(mapargs)
  local ev_data = mapargs.context_event_data
  if ev_data.text ~= nil then
    ClearTelegram()
    local driver_text = ""
    local items = string.split(ev_data.text, "\n", 1)
    if #items == 0 then
      current_destination_code_text = ""
    else
      current_destination_code_text = items[1]
      if #items > 1 then
        driver_text = items[2]:remove_trailing("\n")
      end
      AddRecentList(ev_data.text)
    end
    SetToXYControl(ev_data.text)
    UpdateDestination(driver_text)
  end
end

function CBSetDestinationSourceLocal()
  if destination_source_auto then
    destination_source_auto = nil
    UpdateDriverDisplay()
    CBRestartNoActivityTimer()
  end
end

function CBSetDestinationSourceRemote()
  if not destination_source_auto then
    destination_source_auto = gre.get_value("destination_info_layer.destination_info_group.source_auto")
    ClearRoundTripActive()
    UpdateDriverDisplay()
  end
end

---
-- @brief  Sets the height of the drive display box to be 2 lines of text 
---        high. Based on the font size. 
--- @param gre#context mapargs
---
function CBSetDriverDisplayBoxHeight(mapargs) 
  -- Get height for 2 lines of text
  local font_size = gre.get_value("destination_info_layer.destination_info_group.current_destination_control.font_size")
  local info = gre.get_string_size(gre.get_value("font_bold"), font_size, "d\nj")
  
  gre.set_value("destination_info_layer.destination_info_group.current_destination_image.grd_height", info.line_height * 2)
  gre.set_value("destination_info_layer.destination_info_group.current_destination_control.grd_height", info.line_height * 2)
  gre.set_value("destination_info_layer.destination_info_group.current_destination_control.y_offset", 0)
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.  
---         
--- @param gre#context mapargs
---
function CBScrollDriverMessageText(mapargs)
  local group = "destination_info_layer.destination_info_group"

  if #current_driver_images ~= 0 and not current_driver_select_active then
    gre.set_value(group..".current_destination_image.image", current_driver_images[current_driver_index])
    gre.set_value(group..".current_destination_image.grd_hidden", 0)
    gre.set_value(group..".current_destination_control.grd_hidden", 1)
    current_driver_index = current_driver_index % #current_driver_images + 1
  elseif #current_driver_pages ~= 0 then
    gre.set_value(group..".current_destination_control.text", current_driver_pages[current_driver_index])
    gre.set_value(group..".current_destination_control.grd_hidden", 0)
    gre.set_value(group..".current_destination_image.grd_hidden", 1)
    current_driver_index = current_driver_index % #current_driver_pages + 1
  else
    gre.set_value(group..".current_destination_control.text", "")
    gre.set_value(group..".current_destination_control.grd_hidden", 0)
    gre.set_value(group..".current_destination_image.grd_hidden", 1)
  end
end

---
-- @brief  Initialises the recent list
--
function CBReadRecentList(mapargs)
  CleanRecentList()
end

---
-- @brief  Update the clear control for the route number screen
--
function UpdateRouteNumberClearControl()
  local special = gre.get_value(gre.env("active_screen")..".special")
  if special == nil then return end
  local route_number = special and current_special_route_number or current_route_number
  gre.set_value("route_number_layer.clear_control.grd_hidden", route_number == "" and 1 and 0)
end

---
-- @brief  Process the route number event
-- @param gre#context mapargs
--
function CBRouteNumberChanged(mapargs)
  current_route_number = mapargs.context_event_data.text
  UpdateRouteNumberClearControl()
  UpdateDriverDisplay()
end

---
-- @brief  Process the route number event
-- @param gre#context mapargs
--
function CBSpecialRouteNumberChanged(mapargs)
  current_special_route_number = mapargs.context_event_data.text
  UpdateRouteNumberClearControl()
  UpdateDriverDisplay()
end

---
-- @brief  Update the home screen tiles
--
local function UpdateHomeTiles()
  local destination_tile_height = 364
  local layer = "home_tiles_layer"
  local objects = GetChildObjectList(layer)
  local x = 45
  local data = {}
  for i = #objects,1,-1 do
    local group = tostring(objects[i])
    if group:sub(-6) == "_group" and gre.get_value(group..".grd_hidden") == 0 then
      destination_tile_height = 240
      data[group..".grd_x"] = x
      x = x + 747
    end      
  end
  data["home_tiles_layer.scroll_width.grd_width"] = x + 45
  data["destination_info_layer.destination_info_group.background.grd_height"] = destination_tile_height
  data["destination_info_layer.destination_info_group.background.height"] = destination_tile_height - 36
  gre.set_data(data)
end

---
-- @brief  Update the home screen tiles
-- @param gre#context mapargs
--
function CBUpdateHomeTiles(mapargs)
  UpdateHomeTiles()
end

---
-- @brief  Stop the tile layer from scrolling
-- @param  layer - layer to stop.
--
local function TileUpdateTimerStop(layer)
  if tile_layers[layer] ~= nil then
    tile_layers[layer] = nil
    if next(tile_layers) then
      gre.timer_clear_interval(tile_timer)
      tile_timer = nil
    end  
  end
end

---
-- @brief  Activate the required tile. 
-- @param  group - tile group to activate.
-- @param  allow_display_off - Set to true to suppress activating backlight and 
--         prevent it from switching off.
--
function ActivateTile(group, allow_display_off)
  if gre.get_value("home_tiles_layer."..group..".grd_hidden") == 1 then
    gre.set_value("home_tiles_layer."..group..".grd_hidden", 0)
    UpdateHomeTiles()
    if allow_display_off ~= true then
      StartHoldAtDim(group)
    end
  end
end

---
-- @brief  Deactivate the required tile. 
-- @param  group - tile group to activate.
--
function DeactivateTile(group)
  gre.set_value("home_tiles_layer."..group..".grd_hidden", 1)
  UpdateHomeTiles()
  CancelHoldAtDim(group)
end

---
--- @brief   Action for freeze destination in force event
---
function CBFreezeDestinationInForce(mapargs)
  freeze_destination_id = mapargs.context_event_data.text
  local control = "home_tiles_layer.destination_freeze_group.message"
  gre.set_value(control..".text", string.format(gre.get_value(control..".format"), freeze_destination_id))
  ActivateTile("destination_freeze_group", true)
  UpdateDriverDisplay()
end

---
--- @brief   Action for freeze destination in force event
---
function CBDestinationFreezeStopped(mapargs)
  freeze_destination_id = nil
  DeactivateTile("destination_freeze_group")
  UpdateDriverDisplay()
end

---
--- @brief   Global access to update the driver display
---
function CBUpdateDestinationsDisplay()
  UpdateDriverDisplay()
end

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

---
--- @brief  Process the telegram cancelled event.
function CBTelegramCancelled()
  ClearTelegram()
  UpdateDriverDisplay()
end

-- EOF --
