---
-- @package   eg4-crank-ui
-- @file      sign-status.lua
-- @brief     Functionality to related processing sign status
--
-- @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 CONNECTED_EQUIPMENT_STATUS_LIST_LAYER = "configured_equipment_list_layer"
local CONNECTED_EQUIPMENT_STATUS_CONTROL = CONNECTED_EQUIPMENT_STATUS_LIST_LAYER..".group.items"

local connected_equipment_status = {}
local control_id_counter = 0

--- 
--- @brief  Clear the connected equipment status list.
--- @note   Happens with the settings are updated
---
function ClearConnectedEquipment()
  -- Set all sign status inactive
  for i=1,#connected_equipment_status do
    connected_equipment_status[i].active = nil
  end
  CBProcessUpdateStatusList()

  connected_equipment_status = {}
  control_id_counter = 0
end

---
--- @brief  Fetch basic info for sign
--- @param  index - table index
--- @return table of available signs
---
function GetConnectedSignData(index)
  if not index then return end 
  local item = connected_equipment_status[index]
  if not item.info or not item.info.sign_data then return end

  return  {
    ["equipment_name"] = item.info.equipment_name,
    ["end_point"] = item.info.end_point,
    ["variant_name"] = item.info.sign_data.variant_name
  }
end

---
--- @brief  Fetch basic info for signs
--- @return table of available signs
---
function GetConnectedSignInfo()
  local list = {}
  for index=1,#connected_equipment_status do
    local item = GetConnectedSignData(index)
    if item then
      table.insert(list, item)      
    end
  end
  return list
end

---
--- @brief  Find matching item in the sign status list
--- @param  info- item to locate (has either end_point or serial_no)
--- @return nil or index of matching sign status item.
---
function FindStatusItem(info)
  if info.end_point then
    for i=1,#connected_equipment_status do
      local v = connected_equipment_status[i].info.end_point
      if v and v.trunk == info.end_point.trunk -- and v.channel == info.end_point.channel 
           and v.device_id == info.end_point.device_id then
        -- Note: channel is not matched as it sometimes changes when a sign is reset.
        return i
      end
    end
  else
    for i=1,#connected_equipment_status do
      local v = connected_equipment_status[i].info.serial_no
      if v and info.serial_no == v then
        return i
      end
    end
  end
  return nil
end

---
--- @brief  Find the index of the item in sign status to with a specific control ID
--- @param  control_id - id to locate
--- @return nil or index of matching sign status item.
---
function FindControlID(control_id)
  local index
  for index = 1,#connected_equipment_status do
    if connected_equipment_status[index].control_id == control_id then
      return index
    end
  end
  return nil
end

---
--- @brief  Get identifier of indexed ite.
--- @param  index - index into the status table.
--- @return String representing the identifier either end_point for serial_no
---
function GetEndPointTextByIndex(index)
  local end_point = connected_equipment_status[index].info.end_point
  if not end_point then
    return string.format("%s", connected_equipment_status[index].info.serial_no)
  end
  return string.format("%s %s %s", end_point.trunk, end_point.channel or "", end_point.device_id)
end

---
--- @brief  Updates the status of the home screen fault icon.
--- @param  index - index into the status table.
--- @return true if item is indicating a fault.
---
local function StatusFaultActive(index)
  local info = connected_equipment_status[index].info
  if info.sign_data then
    return info.sign_data.responsive_flag ~= 1 or
        info.sign_data.status_code ~= 'STATUS_OK'
  elseif info.obc_data then
    if info.obc_data.responsive_flag ~= 1 then return true end
    if info.obc_data.gps and info.obc_data.gps ~= "STATUS_OK" then return true end
    if info.obc_data.ticket_machine and info.obc_data.ticket_machine ~= "STATUS_OK" then return true end
    if info.obc_data.internal_sign and info.obc_data.internal_sign ~= "STATUS_OK" then return true end
  elseif info.cibor_data then
    if info.cibor_data.responsive_flag ~= 1 then return true end
  elseif info.hogia_data then
    if info.hogia_data.hogia_available ~= 1 then return true end
  elseif info.ibis_data then
    return info.ibis_data.responsive_flag ~= 1 or 
           (info.ibis_data.status_code ~= 0 and
            info.ibis_data.severity_level >= 2)
  elseif info.stft_data ~= nil then
    return info.stft_data.responsive_flag ~= 1
  end
  return false
end

---
--- @brief  Updates the status of the home screen fault icon.
---
local function UpdateFaultIconControl()
  local faults_active = false
  for index = 1,#connected_equipment_status do
    if StatusFaultActive(index) then
      faults_active = true
      break
    end
  end
  
  if faults_active then
    SetHomeScreenIcon("faults_control", 0)
    gre.set_control_attrs("clear_sign_errors_layer.clear_control", { hidden = 0 })
  else
    SetHomeScreenIcon("faults_control", 1)
    gre.set_control_attrs("clear_sign_errors_layer.clear_control", { hidden = 1 })
  end
  gre.set_value("sign_settings_control_layer.menu_items.sign_communications.value", string.format("[ %d ]", #connected_equipment_status))
end

---
--- @brief  Process update sign and OBC status list event that occurs after all the sign status
---         has been sent. Removes status for any signs that are no longer present.
---

--- @param  gre#context mapargs
function CBProcessUpdateStatusList(mapargs)
  -- Remove all inactive sign status
  local i = 1
  local y = 0
  local last_control = nil
  while i <= #connected_equipment_status do
    local control_id = connected_equipment_status[i].control_id
    if connected_equipment_status[i].active then
      if control_id then
        control_id = "sign_status_list_layer.signs."..control_id
        gre.set_value(control_id..".grd_y", y)
        if control_id ~= nil then
          y = y + gre.get_value(control_id..".grd_height")
        end
        last_control = control_id
      end
      i = i + 1
    else
      if control_id then
        -- If currently showing deleted sign, exit the sign status screen
        if control_id == gre.get_value("mo_sign_status.control_id") then
          gre.send_event("exit_status_screen")
        end
        gre.delete_control("sign_status_list_layer.signs."..control_id)
      end
      table.remove(connected_equipment_status, i)
    end
  end

  if last_control == nil then
    -- Show no active signs status control.
    gre.set_control_attrs("sign_status_list_layer.no_active_signs", { hidden = 0 })
    gre.set_layer_attrs("sign_status_list_layer",{ yoffset = 0 })
  else
      -- Check if all controls are within layer area and therefore scrolling disabled.
    local data = gre.get_control_attrs("sign_status_list_layer", "height", "y")
    if data ~= nil then
      if y + gre.get_control_attrs(last_control, "height").height < data.y + data.height then
        gre.set_layer_attrs("sign_status_list_layer",{ yoffset = 0 })
      end
    end
  end
  UpdateFaultIconControl()
end

---
--- @brief  Determine which template control to usage for the specified info.
--- @param  info - sign status information.
--  @return name of template to use, with basic status or nil
---
local function DetermineTemplateControl(info)
  if info.obc_data ~= nil then
    if info.obc_data.responsive_flag == 0 then
      return "obc_status_no_communications", "error"
    elseif info.obc_data.ticket_machine == "STATUS_NO_RESPONSE" then
      return "obc_status_ticket_machine_no_response", "error"
    elseif info.obc_data.ticket_machine == "STATUS_ERROR_FAULT" then
      return "obc_status_ticket_machine_fault", "error"
    elseif info.obc_data.ticket_machine == "STATUS_UNDEFINED" then
      return "obc_status_ticket_machine_undefined", "error"
    elseif info.obc_data.gps == "STATUS_NO_RESPONSE" then
      return "obc_status_gps_no_response", "error"
    elseif info.obc_data.gps == "STATUS_ERROR_FAULT" then
      return "obc_status_gps_fault", "error"
    elseif info.obc_data.gps == "STATUS_UNDEFINED" then
      return "obc_status_gps_undefined", "error"
    elseif info.obc_data.internal_sign == "STATUS_NO_RESPONSE" then
      return "obc_status_internal_sign_no_response", "error"
    elseif info.obc_data.internal_sign == "STATUS_ERROR_FAULT" then
      return "obc_status_internal_sign_fault", "error"
    elseif info.obc_data.internal_sign == "STATUS_UNDEFINED" then
      return "obc_status_internal_sign_undefined", "error"
    end
    return "obc_status_ok", "ok"
  end

  if info.cibor_data ~= nil then
    if info.cibor_data.responsive_flag == 0 then
      return "obc_status_no_communications", "error"
    end
    return "obc_status_ok", "ok"
  end

  if info.hogia_data ~= nil then
    if info.hogia_data.hogia_available == 0 then
      return "obc_status_no_communications", "error"
    end
    return "obc_status_ok", "ok"
  end

  if info.ibis_data ~= nil then
    if info.ibis_data.responsive_flag == 0 then
      return "ibis_status_no_communications", "error"
    elseif info.ibis_data.status_code ~= 0 then
      if info.ibis_data.severity_level >= 4 then
        return "ibis_status_error", "error"
      elseif info.ibis_data.severity_level >= 2 then
        return "ibis_status_warning", "warning"
      end
      return "ibis_status_info", "info"
    end
    return "ibis_status_ok", "ok"
  end

  if info.stft_data ~= nil then
    if info.stft_data.responsive_flag == 0 then
      return "stft_status_no_communications", "error"
    end
    return "stft_status_ok", "ok"
  end

  if info.sign_data == nil then
    info.sign_data = {}
  else
    if info.sign_data.responsive_flag == 0 then
      return "sign_status_no_communications", "error"
    elseif info.sign_data.status_code == 'STATUS_OK' then
      return "sign_status_ok", "ok"
    elseif info.sign_data.status_code == 'STATUS_MSG_CONTENT_ERROR' then
      return "sign_status_content_error", "error"
    elseif info.sign_data.status_code == 'STATUS_TX_ERROR' then
      return "sign_status_tx_error", "error"
    elseif info.sign_data.status_code == 'STATUS_LAMP_FAILURE' then
      return "sign_status_lamp_failure", "error"
    elseif info.sign_data.status_code == 'STATUS_COMMS_ERROR' then
      return "sign_status_comms_error", "error"
    end
  end
  return "sign_status_undefined"
end

---
--- @brief  Sort the sign and OBC status list and set the control position for active signs
---
local function SortStatusList()
  table.sort(connected_equipment_status,
    function(a, b)
      v1 = a.info.equipment_name or ""
      v2 = b.info.equipment_name or ""
      return v1 < v2
    end)

  -- Update HTC terminal option on the home menu.
  CBHomeMenuUpdate()

  -- Set position of active sign status controls.
  local y = 0
  local last_control = nil
  for _,v in ipairs(connected_equipment_status) do
    if v.active == true then
      if last_control ~= nil then
        y = y + gre.get_control_attrs(last_control, "height").height
      end
      local control = "sign_status_list_layer.signs."..v.control_id
      gre.set_control_attrs(control, { y = y })
      last_control = control
    end
  end

  -- Check if all controls are within layer area and therefore scrolling disabled.
  local data = gre.get_control_attrs("sign_status_list_layer", "height", "y")
  if data ~= nil then
    if y + gre.get_control_attrs(last_control, "height").height < data.y + data.height then
      gre.set_layer_attrs("sign_status_list_layer",{ yoffset = 0 })
    end
  end
end


---
--- @brief  Create a string representing the equipment name.
--- @param  info - connected device information.
local function MakeEquipmentName(info)
  if info.equipment_name then return end
  if info.stft_data then
    info.equipment_name = "STFT "..tostring(info.stft_data.sign_id or 0)
  elseif info.ibis_data then
    info.equipment_name = "IBIS "..info.end_point.device_id
  elseif info.obc_data then
    info.equipment_name = "OBC"
  elseif info.cibor_data then
    info.equipment_name = "OBC"
  elseif info.hogia_data then
    info.equipment_name = "Hogia"
  elseif info.sign_data then
    info.equipment_name = "SIGN "..tostring(info.sign_data.sign_address or 0)
  end
end

---
--- @brief  Process a sign status info event from the backend
--- @param  gre#context mapargs
---
function CBProcessStatusInfo(mapargs)
  if mapargs.context_event_data.text == nil then
    return
  end

  local info = assert(loadstring(mapargs.context_event_data.text))()
  if info == nil then
    return
  end

  -- Find existing sign or create a new one.
  local index = FindStatusItem(info)
  if index == nil then
    index = #connected_equipment_status + 1
    connected_equipment_status[index] = {}
  end

  MakeEquipmentName(info)

  -- Indicate the sign is active and copy the info.
  connected_equipment_status[index].active = true
  connected_equipment_status[index].info = info

  local do_clone = false

  -- Create new control id if required
  if connected_equipment_status[index].control_id == nil then
    control_id_counter = control_id_counter + 1
    connected_equipment_status[index].control_id = control_id_counter
    do_clone = true
  end

  local control = "sign_status_list_layer.signs."..connected_equipment_status[index].control_id

  -- Check if template for sign status changed
  local template = DetermineTemplateControl(info)
  if template ~= connected_equipment_status[index].template then
    gre.delete_object(control)
    connected_equipment_status[index].template = template
    do_clone = true
  end

  if do_clone then
    -- Clone template control for this sign status
    gre.clone_object("sign_status_list_layer.templates."..template, tostring(connected_equipment_status[index].control_id), "sign_status_list_layer.signs", { hidden = 0 })
    gre.set_value(control..".control_id", connected_equipment_status[index].control_id)
  end

  gre.set_value(control..".text", info.equipment_name, 
                control..".description", info.ibis_data and info.ibis_data.status_description)

  UpdateFaultIconControl()
  
  -- If showing, update sign status for this sign.
  if connected_equipment_status[index].control_id == gre.get_value("mo_sign_status.control_id") then
    CBUpdateSignStatusInfo()
  end

  gre.set_control_attrs("sign_status_list_layer.no_active_signs", { hidden = 1 })

  -- Note: Do this last as it re-orders the sign status table and invalidates the index used in this function
  SortStatusList()

  CBPopulateConfiguredEquipment()

  if info.cibor_data or info.hogia_data then
    CBUpdateDestinationsDisplay()
  end
end

---
--- @brief  Process the sign updated status event from the backend
---
function CBProcessSignStatusUpdated()
  -- Clear active flag for all signs. It is expected that all sign status info messages will follow this event.
  for _,v in ipairs(connected_equipment_status) do
    if v.info.sign_data ~= nil then
      v.active = nil
    end
  end
end

---
--- @brief  Process the OBC updated status event from the backend
---
function CBProcessOBCStatusUpdated()
  -- Clear active flag for all signs. It is expected that all sign status info messages will follow this event.
  for _,v in ipairs(connected_equipment_status) do
    if v.info.obc_data ~= nil then
      v.active = nil
    end
  end
end

---
--- @brief  Process the STFT updated status event from the backend
---
function CBProcessSTFTStatusUpdated()
  -- Clear active flag for all signs. It is expected that all sign status info messages will follow this event.
  for _,v in ipairs(connected_equipment_status) do
    if v.info.stft_data ~= nil then
      v.active = nil
    end
  end
end

---
--- @brief  Process the IBIS updated status event from the backend
---
function CBProcessIBISStatusUpdated()
  -- Clear active flag for all signs. It is expected that all status info messages will follow this event.
  for _,v in ipairs(connected_equipment_status) do
    if v.info.ibis_data ~= nil then
      v.active = nil
    end
  end
end

---
--- @brief  Processes the sign error cleared event.
---
function CBProcessSignErrorCleared()
  -- Clear all severity level for all signs.
  for _,v in ipairs(connected_equipment_status) do
    if v.info.sign_data ~= nil then
      v.severity_level = nil
    end
  end
  UpdateFaultIconControl()
end

---
--- @brief  Processes the OBC error cleared event.
---
function CBProcessOBCErrorCleared()
  -- Clear all severity level for all signs.
  for _,v in ipairs(connected_equipment_status) do
    if v.info.obc_data ~= nil then
      v.severity_level = nil
    end
  end
  UpdateFaultIconControl()
end

---
--- @brief  Processes the IBIS error cleared event.
---
function CBProcessIBISErrorCleared()
  -- Clear all severity level for all signs.
  for _,v in ipairs(connected_equipment_status) do
    if v.info.ibis_data ~= nil then
      v.severity_level = nil
    end
  end
  UpdateFaultIconControl()
end

---
--- @brief  Processes the STFT error cleared event.
---
function CBProcessSTFTErrorCleared()
  -- Clear all severity level for all signs.
  for _,v in ipairs(connected_equipment_status) do
    if v.info.stft_data ~= nil then
      v.severity_level = nil
    end
  end
  UpdateFaultIconControl()
end

---
--- @brief  Processes the sign error status event from the backend
--- @param  gre#context mapargs
---
function CBProcessSignOBCError(mapargs)
  if mapargs.context_event_data.text == nil then
    return
  end

  local info = assert(loadstring(mapargs.context_event_data.text))()
  if info == nil then
    return
  end

  local index = FindStatusItem(info)
  if index == nil then
    -- Endpoint not found create new end_point.
    index = #connected_equipment_status + 1

    connected_equipment_status[index] = {}
    connected_equipment_status[index].info = {}
    connected_equipment_status[index].info.end_point = info.end_point
    connected_equipment_status[index].info.serial_no = info.serial_no

    -- Get status status for previously unknown end_point.
    connected_equipment_status[index].active = false
    if mapargs.context_event == "sign_error" then
      SendText("get_specific_sign_status_info", GetEndPointTextByIndex(index))
    elseif mapargs.context_event == "obc_error" then
      SendText("get_specific_obc_status_info", GetEndPointTextByIndex(index))
    elseif mapargs.context_event == "ibis_error" then
      SendText("get_specific_ibis_status_info", GetEndPointTextByIndex(index))
    else
      SendText("get_specific_stft_status_info", GetEndPointTextByIndex(index))
    end
  end

  -- Added current severity level to item.
  connected_equipment_status[index].severity_level = info.severity_level

  UpdateFaultIconControl()
end

---
--- @brief  Populates the sign status item or hides it if no data present
--- @param  data - map to populate
--- @param  item - name of item being populated.
--- @param  value - value of item being populated.
---
local function PopulateSignStatusItem(data, item, value)
  local control = "sign_status_layer."..item
  if value == nil or (type(value) == "string" and value == "") then
    data[control..".grd_hidden"] = 1
  else
    data[control..".grd_hidden"] = 0
    data[control..".value"] = value
  end
end

---
--- @brief  Populate the sign link status
---
local function PopulateSignLinkStatus(data, control, status)
  local active = false
  local value = ""
  if status.link_a_status ~= nil then
    if status.link_a_status == 1 then
      value = value .. " A"
      active = true
    else 
      value = value .. " -"
    end
  end
  if status.link_b_status ~= nil then
    if status.link_b_status == 1 then
      value = value .. " B"
      active = true
    else 
      value = value .. " -"
    end
  end
  if status.link_c_status ~= nil then
    if status.link_c_status == 1 then
      value = value .. " C"
      active = true
    else 
      value = value .. " -"
    end
  end
  if status.link_d_status ~= nil then
      if status.link_d_status == 1 then
      value = value .. " D"
      active = true
    else 
      value = value .. " -"
    end
  end
  if status.link_e_status ~= nil then
    if status.link_e_status == 1 then
      value = value .. " E"
      active = true
    else 
      value = value .. " -"
    end
  end
  control = "sign_status_layer." .. control
  if value == "" then
    data[control..".grd_hidden"] = 1
  else
    data[control..".grd_hidden"] = 0
    if not active then
      value = gre.get_value(control..".none")
    end
    data[control..".value"] = value
  end
end

---
--- @brief  Fecth the text associated with the current Hogia state.
--- @param  hogia_state - hogia state data
--- @return String representing the Hogia state or nil if not set
local function fetch_hogia_state(hogia_state)
  if hogia_state == nil then return end
  return gre.get_value("sign_status_layer.hogia_state.hogia_state_"..hogia_state) or tostring(hogia_state)
end

---
--- @brief  Create string yes/no string
--- @param  status_flag - status flag data
--- @return String representing the status flag or nil if flag is not set
local function get_yes_no_text(status_flag)
  if status_flag == nil then return end
  local control = "sign_status_layer.device_responsive_flag"
  if status_flag == 1 then
    return gre.get_value(control..".yes")
  end
  return gre.get_value(control..".no")
end

---
--- @brief  Populate the sign status information screen with current data.
---
--- @param gre#context mapargs
function CBUpdateSignStatusInfo(mapargs) 
  local control_id = gre.get_value("mo_sign_status.control_id")
  local index = FindControlID(control_id)
  if index == nil then
    return
  end
  local info = connected_equipment_status[index].info
  
  MakeEquipmentName(info)
  
  local data = {}
  
  data["mo_sign_status.banner_heading"] = info.equipment_name

   -- latching_error_flag not presented to user
  PopulateSignStatusItem(data, "latching_error_flag", nil)

  data["sign_status_layer.htc_terminal.grd_hidden"] = info.obc_data and 0 or 1
  local status = info.sign_data or info.obc_data or info.cibor_data or info.hogia_data or info.ibis_data or info.stft_data

  if status.code_image_crc == nil then
    PopulateSignStatusItem(data, "code_image_crc", nil)
  else
    PopulateSignStatusItem(data, "code_image_crc", string.format("%X", status.code_image_crc))
  end 
  PopulateSignStatusItem(data, "current_brightness", status.current_brightness)
  PopulateSignStatusItem(data, "hardware_version", status.hardware_version)
  PopulateSignStatusItem(data, "ibis_address", status.ibis_address)
  PopulateSignLinkStatus(data, "links_fitted", status)
  PopulateSignStatusItem(data, "mac_address", status.mac_address)
  PopulateSignStatusItem(data, "manufacturing_date", status.manufacturing_date)
  PopulateSignStatusItem(data, "max_brightness", status.max_brightness)
  PopulateSignStatusItem(data, "product_number", status.product_number)
  PopulateSignStatusItem(data, "product_type", status.product_type)
  PopulateSignStatusItem(data, "serial_number", status.serial_number or info.serial_no)
  PopulateSignStatusItem(data, "sign_address", status.sign_address or status.sign_id)
  PopulateSignStatusItem(data, "ip_address", status.ip_address)
  PopulateSignStatusItem(data, "status_code", status.status_code and tonumber(status.status_code))
  PopulateSignStatusItem(data, "severity_level", status.severity_level)
  PopulateSignStatusItem(data, "hogia_state", fetch_hogia_state(status.hogia_state))
  PopulateSignStatusItem(data, "hogia_available", get_yes_no_text(status.hogia_available))
  PopulateSignStatusItem(data, "status_description", status.status_description)
  PopulateSignStatusItem(data, "obc_ticket_machine", status.ticket_machine)
  PopulateSignStatusItem(data, "obc_gps", status.gps)
  PopulateSignStatusItem(data, "obc_internal_sign", status.internal_sign)
  PopulateSignStatusItem(data, "data_source", gre.get_value("sign_status_layer.data_source."..tostring(status.data_source)))

  if status.sign_height == nil and status.sign_width == nil then
    PopulateSignStatusItem(data, "sign_size", nil) 
  else
    PopulateSignStatusItem(data, "sign_size", string.format(gre.get_value("sign_status_layer.sign_size.format"), tostring(status.sign_height or "---"), tostring(status.sign_width or "---"))) 
  end

  PopulateSignStatusItem(data, "page_rendering_supported", get_yes_no_text(status.page_rendering_supported))
  PopulateSignStatusItem(data, "device_responsive_flag", get_yes_no_text(status.responsive_flag))
  PopulateSignStatusItem(data, "variant_name", status.variant_name)
  
  if status.version_number == nil then
    PopulateSignStatusItem(data, "version_number", nil)
  else
    PopulateSignStatusItem(data, "version_number", (status.version_number.major or "-") .. "." .. (status.version_number.minor or "-") .. "." .. (status.version_number.build or "-"))
  end
  
  if status.x_variant_version_number == nil then
    PopulateSignStatusItem(data, "x_variant_version_number", nil)
  else
    PopulateSignStatusItem(data, "x_variant_version_number", (status.x_variant_version_number.major or "-") .. "." .. (status.x_variant_version_number.minor or "-"))
  end

  gre.set_data(data)

  RepositionObjectChildren("sign_status_layer")
  
  UpdateFaultIconControl()
end

---
--- @brief  Call back for when touching sign status selection
--- @param  gre#context mapargs
---
function CBSignStatusItemSelect(mapargs)
  gre.set_value("mo_sign_status.control_id", gre.get_value(mapargs.context_control .. ".control_id"))
  gre.set_value("mo_sign_status.back_screen", mapargs.context_screen)
  ScreenTransition("mo_sign_status")
end

---
--- @brief  Callback to start editing of the equipment name.
--- @param  gre#context mapargs
---
function CBStartEquipmentNameEdit(mapargs)
  local control_id = gre.get_value("mo_sign_status.control_id")
  local index = FindControlID(control_id)
  if index == nil then
    return
  end
  local info = connected_equipment_status[index].info
  -- Only allow editing of equipment name for signs and obc
  if info.sign_data or info.obc_data then
    CBMaintenanceSettingsStart(mapargs)
  end
end

---
--- @brief  Resets all the sign status errors.
---
function CBResetSignStatusErrors()
  CBMaintenanceOperatorLoggedIn()
  for index = 1,#connected_equipment_status do
    if StatusFaultActive(index) then
      if connected_equipment_status[index].info.stft_data ~= nil then
        local serial_no = connected_equipment_status[index].info.serial_no
        SendText("clear_stft_error", serial_no)
        SendText("get_specific_stft_status_info", serial_no)
      else
        local endpoint_text = GetEndPointTextByIndex(index)
        if connected_equipment_status[index].info.ibis_data then
          SendText("clear_ibis_error", endpoint_text)
          SendText("get_specific_ibis_status_info", endpoint_text)
        elseif connected_equipment_status[index].info.obc_data then
          SendText("clear_obc_error", endpoint_text)
          SendText("get_specific_obc_status_info", endpoint_text)
        elseif connected_equipment_status[index].info.cibor_data then
          SendText("clear_cibor_error", endpoint_text)
          SendText("get_specific_cibor_status_info", endpoint_text)
        elseif connected_equipment_status[index].info.hogia_data then
          SendText("clear_hogia_error", endpoint_text)
          SendText("get_specific_hogia_status_info", endpoint_text)
        else
          SendText("clear_sign_error", endpoint_text)
          SendText("get_specific_sign_status_info", endpoint_text)
        end
      end
      connected_equipment_status[index].active = false
    end
  end
  UpdateFaultIconControl()
end

---
--- @brief  Sends the start sign self test
---
--- @param gre#context mapargs
function CBStartSignSelfTest(mapargs)
  if ScrollActive() then return end
  CBNotify({ context_event = "start_sign_self_test" })
  SendEvent("start_sign_self_test")
end

---
--- @brief  Sends the stop sign self test
---
--- @param gre#context mapargs
function CBStopSignSelfTest(mapargs)
  if ScrollActive() then return end
  SendEvent("stop_sign_self_test")
  CBCloseNotification(mapargs)
end

---
--- @brief  Fetch first OBC control ID with a preference for the first responsive OBC, then the last to communicate.
--- @return index of first OBC control ID or nil if none found
---
local last_responsive_obc_control_id
function CBFetchFirstOBCControlID()
  local first_non_responsive_obc_control_id
  for i,v in ipairs(connected_equipment_status) do
    -- Ignore if not an obc, the trunk id is UNDEFINED, which indicates that it has never been seen.
      if v.info.obc_data ~= nil and v.info.end_point.trunk ~= "UNDEFINED" then
      -- Check if OBC is communicating
      if v.info.obc_data.responsive_flag == 1 then
        last_responsive_obc_control_id = v.control_id
        return v.control_id
      end
      if first_non_responsive_obc_control_id == nil then
        first_non_responsive_obc_control_id = v.control_id
      end
    end
  end
  return last_responsive_obc_control_id or first_non_responsive_obc_control_id
end

---
--- @brief  Check if the the control id is a virtual OBC
--- @param  control_id - control id to check
--- @return true if the control id is a virtual OBC
---
function CheckVirtualOBC(control_id)
  local index = FindControlID(control_id)
  if index == nil then return end
  return tonumber(connected_equipment_status[index].info.end_point.trunk) == sbsettings.get_virtual_htc_terminal_port()
end

---
--- @brief  Get setting for sign id
---
local function GetSignMappingSettingText(sign_id)
  local type = sbsettings.get_sign_default_device_type(sign_id)
  local index = sbsettings.get_sign_default_database_index(sign_id)
  if type == "DESTINATION_SIGN" and (index == nil or index == "" or index == sign_id) then
    return gre.get_value("sign_mapping_list_layer.group.items.default")
  end
  return "["..type.."]".." "..index 
end

---
--- @brief  Getter function for sign mapping text. Returns more complex text than 
--          is available from backend
--- @param  port_id - sign to get mapping text.
function get_sign_mapping_setting_text(port_id)
  return GetSignMappingSettingText(port_id)
end

---
--- @brief  Initialises the sign settings mappings list screen.
--- @param  gre#context mapargs
function CBInitSignSettingsMappings(mapargs)
  local rows = 0
  local data = {}
  local format = gre.get_value("sign_mapping_list_layer.group.items.sign_id_format")
  for i = 0, 14 do
    rows = rows + 1
    local sign_id = tostring(i)
    data["sign_mapping_list_layer.group.items.sign_id."..rows..".1"] = sign_id
    data["sign_mapping_list_layer.group.items.text."..rows..".1"] = string.format(format, sign_id)
    local text = GetSignMappingSettingText(sign_id)
    data["sign_mapping_list_layer.group.items.value."..rows..".1"] = text
  end
  data["sign_mapping_list_layer.group.items.grd_height"] = rows * gre.get_table_cell_attrs("sign_mapping_list_layer.group.items",1,1,"height").height
  gre.set_data(data)
  gre.set_table_attrs("sign_mapping_list_layer.group.items", {rows = rows})
end

---
--- @brief  Writes the sign mapping type to the backend
--- @param gre#context mapargs
function CBWriteSignMappingType(mapargs)
  if ScrollActive() then return end
  local sign_id = gre.get_value("mo_sign_settings_mapping.port_id")
  local value = gre.get_value(mapargs.context_control..".text."..mapargs.context_row..".1")
  if value == "DESTINATION_SIGN" then
    ScreenTransition("mo_sign_settings_mapping_address")
  else
    sbsettings.set_sign_default_database_index("", sign_id)
    TransitionBackScreen(mapargs)
  end
end

--- @param gre#context mapargs
function CBWriteSignMappingAddress(mapargs) 
  local port_id = gre.get_value("mo_sign_settings_mapping.port_id")
  local value = (mapargs.value == "") and port_id or mapargs.value
  sbsettings.set_sign_default_database_index(value, port_id)
  SendEvent("settings_updated")
  TransitionBackScreen(mapargs)
end

---
--- @brief  Populates the sign mapping types list
--- @param gre#context mapargs
function CBPopulateSignMappingTypes(mapargs)
  local control = "sign_default_device_type_layer.group.items"
  local list = sbsettings.get_sign_default_device_type_list()
  local data = {}
  for i = 1, #list do
    data[control..".text."..i..".1"] = list[i]
  end
  data[control..".grd_height"] = #list * gre.get_table_cell_attrs(control,1,1,"height").height
  gre.set_data(data)
  gre.set_table_attrs(control, {rows = #list})
end

---
--- @brief  Writes the sign mapping sign height to the backend
--- @param gre#context mapargs
function CBWriteSignMappingHeight(mapargs)
  if mapargs.value == "" then return end
  local port_id = gre.get_value("mo_sign_settings_mapping.port_id")
  sbsettings.set_sign_default_configured_height(tonumber(mapargs.value), port_id)
  SendEvent("settings_updated")
  TransitionBackScreen(mapargs)
end

---
--- @brief  Writes the sign mapping sign width to the backend
--- @param gre#context mapargs
function CBWriteSignMappingWidth(mapargs) 
  if mapargs.value == "" then return end
  local port_id = gre.get_value("mo_sign_settings_mapping.port_id")
  sbsettings.set_sign_default_configured_width(tonumber(mapargs.value), port_id)
  SendEvent("settings_updated")
  TransitionBackScreen(mapargs)
end

---
--- @brief  Writes the sign mapping extra configuration string to the backend
--- @param gre#context mapargs
function CBWriteSignMappingConfigString(mapargs) 
  local port_id = gre.get_value("mo_sign_settings_mapping.port_id")
  sbsettings.set_sign_default_config_string(mapargs.value, port_id)
  SendEvent("settings_updated")
  TransitionBackScreen(mapargs)
end

---
--- @brief  Writes the virtual HTC port number to the backend
--- @param gre#context mapargs
function CBWriteVirtualHTCPortNo(mapargs)
  local value = tonumber(mapargs.value)
  if value >= 0 and value <= 65535 then
    sbsettings.set_virtual_htc_terminal_port(value)
    SendEvent("settings_updated")
    TransitionBackScreen(mapargs)
  end
end

---
--- @brief  Process the sign blanking activated event
--- @param gre#context mapargs
function CBProcessSignBlankingActivated(mapargs) 
  ActivateTile("sign_blanking_active_group")
end

---
--- @brief  Process the sign blanking deactivated event
--- @param gre#context mapargs
function CBProcessSignBlankingDeactivated(mapargs) 
  DeactivateTile("sign_blanking_active_group")
end

---
--- @brief  Process the emergency message activated event
--- @param gre#context mapargs
function CBProcessDisplayEmergencyMessage(mapargs)
  local text = sbsettings.get_emergency_driver_message()
  if not text or text:len() == 0 then return end
  gre.set_value("home_tiles_layer.emergency_message_group.message.text", text)
  ActivateTile("emergency_message_group")
end

---
--- @brief  Process the emergency message deactivated event
--- @param gre#context mapargs
function CBProcessCancelEmergencyMessage(mapargs) 
  DeactivateTile("emergency_message_group")
end

---
--- @brief  Process the temperature alert message activated event
--- @param gre#context mapargs
function CBProcessDisplayTemperatureAlert(mapargs)
  local route_code = ""
  local destination_code
  local item = mapargs.context_event_data.text:split(" ", 2)
  if get_route_code_len() > 0 then
    route_code = item[1]
    destination_code = item[2]
  else
    destination_code = item[1]
  end

  local text = sbsettings.get_destination_driver_message(route_code, destination_code)
  if text == nil or text == "" then
    return
    -- text = gre.get_value("home_tiles_layer.temperature_alert_group.message.defaut")
  end

  gre.set_value("home_tiles_layer.temperature_alert_group.message.text", text)
  ActivateTile("temperature_alert_group")
end

---
--- @brief  Process the temperature alert message deactivated event
--- @param gre#context mapargs
function CBProcessCancelTemperatureAlert(mapargs) 
  DeactivateTile("temperature_alert_group")
end

---
--- @brief  Process the bus stopping activated event
--- @param gre#context mapargs
function CBProcessDisplayBusStoppingMessage(mapargs)
  local text = sbsettings.get_bus_stopping_driver_message()
  if not text or text:len() == 0 then return end
  gre.set_value("home_tiles_layer.bus_stopping_group.message.text", text)
  ActivateTile("bus_stopping_group")
end

---
--- @brief  Process the bus stopping deactivated event
--- @param gre#context mapargs
function CBProcessCancelBusStoppingMessage(mapargs) 
  DeactivateTile("bus_stopping_group")
end

---
--- @brief  Process the bus stopping activated event
--- @param gre#context mapargs
function CBProcessDoorAssistanceRequested(mapargs)
  ActivateTile("assistance_requested_group")
end

---
--- @brief  Process the bus stopping deactivated event
--- @param gre#context mapargs
function CBProcessCancelDoorAssistanceRequest(mapargs) 
  DeactivateTile("assistance_requested_group")
end

---
--- @brief  Add information to data about sign mapping.
---
local function AddConfiguredDeviceItem(data, row, hcp_address, info)
  local control = CONNECTED_EQUIPMENT_STATUS_CONTROL

  data[control..".fade."..row..".1"] = 0
  data[control..".selectable."..row..".1"] = 0
  data[control..".text."..row..".1"] = "#"..hcp_address
  data[control..".sizing."..row..".1"] = "---"
  data[control..".value."..row..".1"] = ""
  data[control..".ok."..row..".1"] = 0
  data[control..".info."..row..".1"] = 0
  data[control..".warning."..row..".1"] = 0
  data[control..".error."..row..".1"] = 0

  if info == nil then return end

  if info.sign_page_lookup ~= nil and tonumber(info.sign_page_lookup) ~= hcp_address then
    data[control..".text."..row..".1"] = data[control..".text."..row..".1"].." ["..info.sign_page_lookup.."]"
  end
  if info.type then
    data[control..".value."..row..".1"] = gre.get_value(CONNECTED_EQUIPMENT_STATUS_LIST_LAYER..".device_type."..(info.type + 1)..".1") or ""
  end

  local height = info.height
  local width = info.width
  if height or width then
    if width == nil or width == -1 then width = "?" end
    if height == nil or height == -1 then height = "?" end
    data[control..".sizing."..row..".1"] = string.format("%s x %s",  tostring(width), tostring(height))
  end

  for _,item in ipairs(connected_equipment_status) do
    if item.info and item.info.end_point and item.info.end_point.device_id == tostring(hcp_address) then
      local _,status = DetermineTemplateControl(item.info)
      if status then
        data[control.."."..status.."."..row..".1"] = 255
        data[control..".selectable."  ..row..".1"] = 255
        data[control..".hcp_address."  ..row..".1"] = tostring(hcp_address)
      end
    end
  end
end

---
--- @brief  Populate the screen with configured equipment
--- @param gre#context mapargs
function CBPopulateConfiguredEquipment(mapargs)
  local context_screen = mapargs and mapargs.context_screen or gre.env("active_screen")
  if context_screen ~= "mo_configured_equipment_list" then return end

  local info = sbsettings.read_configured_devices()['*'] or {}
  local data = {}
  local row = 0
  for hcp_address=0,14 do
    row = row + 1
    AddConfiguredDeviceItem(data, row, hcp_address, info[hcp_address])
  end

  local control = CONNECTED_EQUIPMENT_STATUS_CONTROL
  local height = gre.get_table_cell_attrs(control, 1, 1, "height").height
  gre.set_table_attrs(control, { height = row * height, rows = row })

  gre.set_data(data)
  gre.set_table_attrs(control, { height = row * 81, rows = row })
end

---
--- @brief  Determine whether the selected configured equipment item is selectable.
--- @param gre#context mapargs
local function ConfiguredEquipmentItemSelectable(mapargs)
  local selectable = mapargs.context_control..".selectable."..mapargs.context_row..".1"
  return gre.get_value(selectable) == 255
end

---
--- @brief  Handle press for the selected item. Trigger click and blue fade is selectable.
--- @param gre#context mapargs
function CBPressConfiguredEquipmentItem(mapargs)
  if not ConfiguredEquipmentItemSelectable(mapargs) then return end
  CBClickOnScreenPress(mapargs)
  CBMenuItemTouchFade(mapargs)
end

---
--- @brief  Handle touch for the selected item. Find sign status and transition.
--- @param gre#context mapargs
function CBTouchConfiguredEquipmentItem(mapargs)
  if not ConfiguredEquipmentItemSelectable(mapargs) then return end
  if ScrollActive() then return end

  local hcp_address = tostring(gre.get_value(mapargs.context_control..".hcp_address."..mapargs.context_row..".1"))
  for _,item in ipairs(connected_equipment_status) do
    if item.info and item.info.end_point and item.info.end_point.device_id == tostring(hcp_address) then
      gre.set_value(mapargs.context_control .. ".control_id", item.control_id)
      CBSignStatusItemSelect(mapargs)
      break
    end
  end
end
 
---
--- @brief Populate the controlled destination ID screen
--- @param gre#context mapargs
function CBPopulateStateOfChargeDestinations(mapargs)
  local items = sbsettings.get_state_of_charge_destination_ids():split(",")
  local context_control = "state_of_charge_destination_ids_layer.destination_ids.items"

  local data = {}
  data[context_control..".grd_height"] = #items * 81
  for index=1,#items do
    data[context_control..".text."..index..".1"] = items[index]
  end
  gre.set_data(data)
  gre.set_table_attrs(context_control, { rows = #items })
end

---
--- @brief Remove selected destination ID from state of charge destination ID list
--- @param gre#context mapargs
function CBRemoveStateOfChargeDestinationID(mapargs)
  if ScrollActive() then return end

  -- Limit clear to end of item.
  if mapargs.context_event_data.x < gre.get_value(mapargs.context_control..".grd_x") + gre.get_value(mapargs.context_control..".grd_width") - 100 then return end

  local value = gre.get_value(mapargs.context_control..".text."..mapargs.context_row..".1")
  sbsettings.del_state_of_charge_destination_id(value)
  SendEvent("settings_updated")
  CBPopulateStateOfChargeDestinations()
end

---
--- @brief Start add state of charge destination ID entry
--- @param gre#context mapargs
local selected_route_code
function CBStartAddStartOfChargeDestinationID(mapargs)
  selected_route_code = ""
  if get_route_code_len() > 0 then
    ScreenTransition("mo_sign_settings_state_of_charge_destinations_route_code")
  else
    ScreenTransition("mo_sign_settings_state_of_charge_destinations_destination_code")
  end
end

---
--- @brief Route code entered for state of charge destination ID
--- @param gre#context mapargs
function CBAddStateOfChargeDestinationRouteCode(mapargs)
  selected_route_code = mapargs.value
  ScreenTransition("mo_sign_settings_state_of_charge_destinations_destination_code")
end

---
--- @brief Destination code entered for state of charge destination ID
--- @param gre#context mapargs
function CBAddStateOfChargeDestinationIDDestinationCode(mapargs)
  local value = selected_route_code.." "..mapargs.value
  sbsettings.add_state_of_charge_destination_id(value)
  SendEvent("settings_updated")
  TransitionBackScreen(mapargs)
end

---
--- @brief  Check if configuration requires (MAN) display for local destinations.
--- @return true if yes
function check_manual_required()
  for k,v in pairs(connected_equipment_status) do
    if v.info.cibor_data or v.info.hogia_data then
      return true
    end
  end
  return false
end

---
--- @brief  Check if Hogia is present in the connected equipment status.
--- @return true if yes
function check_hogia_present()
  for k,v in pairs(connected_equipment_status) do
    if v.info.hogia_data then
      return true
    end
  end
  return false
end

-- EOF --
