--[[
  @package    eg4-crank-ui-2
  @file       connected-equipment-status.lua
  @brief      Collates connected equipment status and updates the status layer and icons.
]]

local connected_equipment_status = {}
local control_id_counter = 0

local CONNECTED_EQUIPMENT_STATUS_LIST_SCREEN = "connected_equipment_status_list"
local CONNECTED_EQUIPMENT_STATUS_LIST_LAYER = "connected_equipment_status_list_layer"
local CONNECTED_EQUIPMENT_STATUS_ACTIVE_LIST = CONNECTED_EQUIPMENT_STATUS_LIST_LAYER..".menu"
local CONNECTED_EQUIPMENT_STATUS_LIST_TEMPLATES = CONNECTED_EQUIPMENT_STATUS_LIST_LAYER..".templates"
local CONNECTED_EQUIPMENT_STATUS_SCREEN = "connected_equipment_status"
local CONNECTED_EQUIPMENT_STATUS_LAYER = "connected_equipment_status_layer"
local CONNECTED_EQUIPMENT_STATUS_GROUP = CONNECTED_EQUIPMENT_STATUS_LAYER..".menu"
local NO_CONNECTED_EQUIPMENT = CONNECTED_EQUIPMENT_STATUS_LIST_LAYER..".no_connected_equipment"


---
--- @brief  Get the status data from the info structure.
--- @param  info - connected device information.
--- @return status data table
local function get_status(info)
  return info.sign_data or info.obc_data or info.cibor_data or info.hogia_data or info.ibis_data or info.stft_data
end

---
--- @brief  Clear the connected equipment status list.
--- @note   Happens with the settings are updated
function clear_connected_equipment()
  -- Set all connected equipment status inactive
  for _,item in pairs(connected_equipment_status) do
    item.active = nil
  end
  -- Process to clear all connected equipment status items
  cb_process_update_status_list()

  connected_equipment_status = {}
  control_id_counter = 0
end

---
--- @brief  Determine the status for the specified OBC feature.
--- @param  status - status data
--- @return template name and status
local function determine_obc_feature_status(status)
  if status == "STATUS_NO_RESPONSE" then
    return "status_error", "no_response"
  elseif status == "STATUS_ERROR_FAULT" then
    return "status_error", "fault"
  elseif status == "STATUS_UNDEFINED" then
    return "status_error", "undefined"
  end
  return "status_ok"
end

---
--- @brief  Determine the template and status for the specified info.
--- @param  info - connected device information.
--- @return template name and status
---
local function determine_template_control(info)
  if info.cibor_data then
    if info.cibor_data.responsive_flag == 0 then
      return "status_error", "no_communications"
    end
    return "status_ok"
  end

  if info.hogia_data then
    if info.hogia_data.hogia_available == 0 then
      return "status_error", "no_communications"
    end
    return "status_ok"
  end

  if info.obc_data then
    if info.obc_data.responsive_flag == 0 then
      return "status_error", "no_communications"
    end
    local template, status = determine_obc_feature_status(info.obc_data.ticket_machine)
    if status then return template, "ticket_machine_"..status end
    local template, status = determine_obc_feature_status(info.obc_data.gps)
    if status then return template, "gps_"..status end
    local template, status = determine_obc_feature_status(info.obc_data.internal_sign)
    if status then return template, "internal_sign_"..status end
    return template
  end

  if info.ibis_data then
    if info.ibis_data.responsive_flag == 0 then
      return "status_error", "no_communications"
    elseif info.ibis_data.status_code ~= 0 then
      if info.ibis_data.severity_level >= 4 then
        return "status_error"
      elseif info.ibis_data.severity_level >= 2 then
        return "status_warning"
      end
      return "status_info"
    end
    return "status_ok"
  end

  if info.stft_data then
    if info.stft_data.responsive_flag == 0 then
      return "status_error", "no_communications"
    end
    return "status_ok"
  end

  if info.sign_data == nil then
    -- TODO DO not force this table.
    info.sign_data = {}
  else
    if info.sign_data.responsive_flag == 0 then
      return "status_warning", "no_communications"
    elseif info.sign_data.status_code == 'STATUS_OK' then
      return "status_ok"
    elseif info.sign_data.status_code == 'STATUS_MSG_CONTENT_ERROR' then
      return "status_error", "content_error"
    elseif info.sign_data.status_code == 'STATUS_TX_ERROR' then
      return "status_error", "tx_error"
    elseif info.sign_data.status_code == 'STATUS_LAMP_FAILURE' then
      return "status_error", "lamp_failure"
    elseif info.sign_data.status_code == 'STATUS_COMMS_ERROR' then
      return "status_error", "comms_error"
    end
  end
  return "status_error", "undefined"
end

---
--- @brief  Checks the item to determine if a fault is active.
--- @param  item The item to check
--- @return true if item is indicating a fault.
local function status_fault_active(item)
  local template,_ = determine_template_control(item.info)
  if template == "status_error" or template == "status_warning" then return true end
  return false
end

---
--- @brief  Checks the item to determine if a fault is active.
--- @return map of ok, error, warning and info counts.
local function count_status()
  local counts = {
      ok = 0,
      error = 0,
      warning = 0,
      info = 0,
    }
  for _,item in pairs(connected_equipment_status) do
    local template,_ = determine_template_control(item.info)
    if template == "status_ok" then counts.ok = counts.ok + 1
    elseif template == "status_error" then counts.error = counts.error + 1
    elseif template == "status_warning" then counts.warning = counts.warning + 1
    else counts.info = counts.info + 1 end
  end
  return counts
end

---
--- @brief  check that the hcp address specified has a status that is 
--- @param  hcp_address - address to check
--- @return the current status or nil if undefined, and the associated context_control
function check_hcp_address_has_status(hcp_address)
  for context_control,item in pairs(connected_equipment_status) do
    if item.info and item.info.end_point and item.info.end_point.device_id == tostring(hcp_address) then
      local template,status = determine_template_control(item.info)
      if status == "undefined" then return end
      return template, context_control
    end
  end
end

---
--- @brief  Update the connected equipment status tile with the current counts
local function update_status_info_tile()
  update_connected_equipment_status_tile(count_status())
end

---
--- @brief  check if the faults icon should be displayed.
function show_faults()
  return false
end

---
--- @brief  Update the fault tile status
local function update_fault_tile()
  local TILE_GROUP = "status_tiles_layer.faults"

  local counts = count_status()
  local text = ""
  if counts.error > 0 then
    gre.set_value(TILE_GROUP..".fault_count.text", counts.error)
    gre.set_value(TILE_GROUP..".fault_icon.grd_hidden", 0)
    gre.set_value(TILE_GROUP..".fault_count.grd_hidden", 0)
  else
    gre.set_value(TILE_GROUP..".fault_icon.grd_hidden", 1)
    gre.set_value(TILE_GROUP..".fault_count.grd_hidden", 1)
  end
  if counts.warning > 0 then
    gre.set_value(TILE_GROUP..".warn_count.text", counts.warning)
    gre.set_value(TILE_GROUP..".warn_icon.grd_hidden", 0)
    gre.set_value(TILE_GROUP..".warn_count.grd_hidden", 0)
  else
    gre.set_value(TILE_GROUP..".warn_icon.grd_hidden", 1)
    gre.set_value(TILE_GROUP..".warn_count.grd_hidden", 1)
  end

  if counts.error + counts.warning > 0 then
    activate_tile("faults")
  else
    deactivate_tile("faults")
  end
end

---
--- @brief  Update the number of connected equipment items in the list for set communications option
local function set_communications_value()
  gre.set_value("connected_equipment_settings_menu_layer.menu.set_communications.value", get_set_communications())
end

---
--- @brief  Process update device status list event that occurs after all the sign status
---         has been sent. Removes status for any signs that are no longer present.
function cb_process_update_status_list(mapargs)
  -- Remove all inactive sign status
  local data = {}
  local y = 0

  for context_control,v in pairs(connected_equipment_status) do
    if not v.active then
      -- If currently showing deleted sign, exit the sign status screen
      if gre.env("active_screen") == CONNECTED_EQUIPMENT_STATUS_SCREEN and context_control == gre.get_value(CONNECTED_EQUIPMENT_STATUS_SCREEN..".control_id") then
        cb_back_screen()
      end
      gre.delete_control(context_control)
      connected_equipment_status[context_control] = nil
    end
  end

  if next(connected_equipment_status) then
    if gre.env("active_screen") == CONNECTED_EQUIPMENT_STATUS_LIST_SCREEN then
      cb_update_menu()
    end
  else
    -- Show no active signs status control.
    gre.set_value(NO_CONNECTED_EQUIPMENT..".grd_hidden", 0 )
    gre.set_layer_attrs_global(CONNECTED_EQUIPMENT_STATUS_LIST_LAYER, { yoffset = 0 } )
  end

  update_fault_tile()
  set_communications_value()
  update_status_info_tile()
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 context_control and reference to matching item.
local function find_status_item(info)
  if info.end_point then
    for context_control,item in pairs(connected_equipment_status) do
      local v = item.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 context_control,item
      end
    end
  else
    for context_control,item in pairs(connected_equipment_status) do
      local v = item.info.serial_no
      if v and info.serial_no == v then
        return context_control,item
      end
    end
  end
  return
end

---
--- @brief  Populates the 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 populate_item(data, item, value)
  local control = CONNECTED_EQUIPMENT_STATUS_GROUP.."."..item
  if value == nil or value == "" then
    data[control..".grd_hidden"] = 1
  else
    data[control..".grd_hidden"] = 0
    data[control..".value"] = value
  end
end

---
--- @brief  Create string from the link status
--- @param  status - status data
local function get_link_status(status)
  local active = false
  local value = ""
  if status.link_a_status then
    if status.link_a_status == 1 then
      value = value .. " A"
      active = true
    else
      value = value .. " -"
    end
  end
  if status.link_b_status then
    if status.link_b_status == 1 then
      value = value .. " B"
      active = true
    else
      value = value .. " -"
    end
  end
  if status.link_c_status then
    if status.link_c_status == 1 then
      value = value .. " C"
      active = true
    else
      value = value .. " -"
    end
  end
  if status.link_d_status then
      if status.link_d_status == 1 then
      value = value .. " D"
      active = true
    else
      value = value .. " -"
    end
  end
  if status.link_e_status then
    if status.link_e_status == 1 then
      value = value .. " E"
      active = true
    else
      value = value .. " -"
    end
  end
  return value
end

---
--- @brief  Create string from the sign size
--- @param  status - status data
--- @return String representing the sign size or nil
local function get_sign_size_string(status)
  if not status.sign_height and not status.sign_width then return end
  return string.format(gre.get_value(CONNECTED_EQUIPMENT_STATUS_GROUP..".sign_size.format"), tostring(status.sign_height or "---"), tostring(status.sign_width or "---"))
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 = CONNECTED_EQUIPMENT_STATUS_GROUP..".device_responsive_flag"
  if status_flag == 1 then
    return gre.get_value(control..".yes")
  end
  return gre.get_value(control..".no")
end

---
--- @brief  Create string from the version number
--- @param  version_number - version number data
--- @return String representing the version number or nil
local function get_version_number_string(version_number)
  if not version_number then return end
  local text = ""
  local sep = ""
  if version_number.major and version_number.major ~= "" then
    text = text .. version_number.major
    sep = "."
  end
  if version_number.minor and version_number.minor ~= "" then
    text = text .. sep .. version_number.minor
    sep = "."
  end
  if version_number.build and version_number.build ~= "" then
    text = text .. sep .. version_number.build
  end
  return text
end

---
--- @brief  Create a string representing the equipment name.
--- @param  info - connected device information.
local function make_equipment_name(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  Fecth the text associated with the current status of the OBC feature.
--- @param  status - status data
--- @return String representing the status of the OBC feature or nil if not set
local function fetch_obc_feature_detail(status)
  if status == nil then return end
  local _,detail = determine_obc_feature_status(status)
  if detail == nil then
    return gre.get_value(CONNECTED_EQUIPMENT_STATUS_LIST_TEMPLATES..".status_ok.value")
  end
  return gre.get_value(CONNECTED_EQUIPMENT_STATUS_LIST_TEMPLATES.."."..tostring(detail)) or detail
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
  local control = CONNECTED_EQUIPMENT_STATUS_GROUP..".hogia_state"
  return gre.get_value(control..".hogia_state_"..hogia_state) or tostring(hogia_state)
end

---
--- @brief  Populate the sign status information screen with current data.
function cb_update_sign_status_info(mapargs)
  local item = connected_equipment_status[gre.get_value(CONNECTED_EQUIPMENT_STATUS_SCREEN..".control_id")]
  local info = item.info

  local data = {}

  make_equipment_name(info)
  data[CONNECTED_EQUIPMENT_STATUS_SCREEN..".heading"] = info.equipment_name

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

  data[CONNECTED_EQUIPMENT_STATUS_GROUP..".htc_terminal.grd_hidden"] = info.obc_data and 0 or 1
  local status = get_status(info)

  populate_item(data, "code_image_crc", status.code_image_crc and string.format("%X", status.code_image_crc))
  populate_item(data, "current_brightness", status.current_brightness)
  populate_item(data, "hardware_version", status.hardware_version)
  populate_item(data, "ibis_address", status.ibis_address)
  populate_item(data, "links_fitted", get_link_status(status))
  populate_item(data, "mac_address", status.mac_address)
  populate_item(data, "manufacturing_date", status.manufacturing_date)
  populate_item(data, "max_brightness", status.max_brightness)
  populate_item(data, "product_number", status.product_number)
  populate_item(data, "product_type", status.product_type)
  populate_item(data, "serial_number", status.serial_number or info.serial_no)
  populate_item(data, "sign_address", status.sign_address or status.sign_id)
  populate_item(data, "ip_address", status.ip_address)
  populate_item(data, "status_code", status.status_code and tonumber(status.status_code))
  populate_item(data, "severity_level", status.severity_level)
  populate_item(data, "hogia_state", fetch_hogia_state(status.hogia_state))
  populate_item(data, "hogia_available", get_yes_no_text(status.hogia_available))
  populate_item(data, "status_description", status.status_description)
  populate_item(data, "obc_ticket_machine", fetch_obc_feature_detail(status.ticket_machine))
  populate_item(data, "obc_gps", fetch_obc_feature_detail(status.gps))
  populate_item(data, "obc_internal_sign", fetch_obc_feature_detail(status.internal_sign))
  populate_item(data, "sign_size", get_sign_size_string(status))
  populate_item(data, "page_rendering_supported", get_yes_no_text(status.page_rendering_supported))
  populate_item(data, "device_responsive_flag", get_yes_no_text(status.responsive_flag))
  populate_item(data, "variant_name", status.variant_name)
  populate_item(data, "version_number", get_version_number_string(status.version_number))
  populate_item(data, "x_variant_version_number", get_version_number_string(status.x_variant_version_number))
  populate_item(data, "data_source", gre.get_value(CONNECTED_EQUIPMENT_STATUS_GROUP..".data_source."..tostring(status.data_source)))

  gre.set_data(data)

  -- Reposition all visible items.
  cb_update_menu(mapargs)
end

---
--- @brief  Sort the status list and set the control position for active signs
---
local list_position_update_timer
local function sort_status_list()
  local list = {}
  for key, value in pairs(connected_equipment_status) do
    table.insert(list, { context_control = key, value = value })
  end

  table.sort(list, function(a, b)
    local v1 = a.value.info.equipment_name or ""
    local v2 = b.value.info.equipment_name or ""
    return v1:upper() < v2:upper()
  end)

  local data = {}
  local y = 0
  for index,item in ipairs(list) do
    data[item.context_control..".grd_hidden"] = item.value.active and 0 or 1
    data[item.context_control..".grd_zindex"] = #list - index
    if item.value.active then
      data[item.context_control..".grd_y"] = y
      y = y + gre.get_value(item.context_control..".grd_height")
    end
  end
  gre.set_data(data)

  -- Delay the update to ensure that the zindex is set by a redraw.
  if list_position_update_timer then gre.timer_clear_timeout(list_position_update_timer) end
  list_position_update_timer = gre.timer_set_timeout(function()
    list_position_update_timer = nil
    if gre.env("active_screen") == CONNECTED_EQUIPMENT_STATUS_LIST_SCREEN then
      cb_update_menu()
    end
  end, 250)

end

---
--- @brief  Update the OBC disconnected tile status
--- @param  info - connected device information.
local function update_obc_disconnected_tile(info)
  if not info.cibor_data then return end
  if info.cibor_data.responsive_flag == 0 then
    activate_tile("obc_disconnected")
  else
    deactivate_tile("obc_disconnected")
  end
  update_destinations_display()
end

---
--- @brief  Update the Hogia disconnected tile status
--- @param  info - connected device information.
local function update_hogia_disconnected_tile(info)
  if not info.hogia_data then return end
  if info.hogia_data.hogia_available == 1 then
    deactivate_tile("hogia_offline")
  else
    activate_tile("hogia_offline")
  end
  update_destinations_display()
end

---
--- @brief  Process a sign status info event from the backend
--- @param  gre#context mapargs
function cb_process_status_info(mapargs)
  local info = loadstring(mapargs.context_event_data.text)()

  local do_clone = false

  -- Find existing sign or create a new one.
  local context_control,item = find_status_item(info)
  if not item then
    control_id_counter = control_id_counter + 1
    context_control = CONNECTED_EQUIPMENT_STATUS_ACTIVE_LIST.."."..tostring(control_id_counter)
    item = {}
    connected_equipment_status[context_control] = item
    do_clone = true
  end

  make_equipment_name(info)

  -- Indicate the sign is active and copy the info.
  item.active = true
  item.info = info

  -- Check if template for sign status changed
  local template,detail = determine_template_control(info)
  if template ~= item.template then
    gre.delete_object(context_control)
    item.template = template
    do_clone = true
  end

  if do_clone then
    -- Clone template control for this sign status
    gre.clone_object(CONNECTED_EQUIPMENT_STATUS_LIST_TEMPLATES.."."..template, context_control:object_name(), CONNECTED_EQUIPMENT_STATUS_ACTIVE_LIST, { hidden = 0, y = 0 })
  end

  if info.ibis_data then
    detail = info.ibis_data.status_description or ""
  elseif detail then
    detail = gre.get_value(CONNECTED_EQUIPMENT_STATUS_LIST_TEMPLATES.."."..tostring(detail)) or ""
  else
    detail = ""
  end

  local text = info.equipment_name
  local status = get_status(info)
  local sign_id = status.sign_address or status.sign_id
  if sign_id then
    text = "#"..sign_id..": "..text
  end

  gre.set_value(context_control..".text", text,
                context_control..".detail", detail,
                NO_CONNECTED_EQUIPMENT..".grd_hidden", 1)

  -- If showing, update sign status for this sign.
  local active_screen = gre.env("active_screen")
  if active_screen == CONNECTED_EQUIPMENT_STATUS_SCREEN and context_control == gre.get_value(CONNECTED_EQUIPMENT_STATUS_SCREEN..".control_id") then
    cb_update_sign_status_info(mapargs)
  end

  sort_status_list()
  update_fault_tile()
  set_communications_value()
  update_status_info_tile()
  update_hogia_disconnected_tile(info)
  update_obc_disconnected_tile(info)

  cb_populate_configured_equipment(mapargs)
end

---
--- @brief  Process the updated status event from the backend
function cb_process_connected_equipment_status_updated(mapargs)
  local data_type = mapargs.context_event:remove_trailing("_status_updated").."_data"
  for _,v in pairs(connected_equipment_status) do
    if v.info[data_type] then v.active = nil end
  end
end

---
--- @brief  Processes the error cleared event.
---
function cb_process_connected_equipment_error_cleared()
  local data_type = mapargs.context_event:remove_trailing("_error_cleared").."_data"
  for _,v in pairs(connected_equipment_status) do
    if v.info[data_type] ~= nil then v.severity_level = nil end
  end
  update_fault_tile()
  set_communications_value()
  update_status_info_tile()
end

---
--- @brief  Get identifier text for info item.
--- @param  info - connected device information.
--- @return String representing the identifier either end_point for serial_no
---
local function get_end_point_text(info)
  if info.end_point then
    return string.format("%s %s %s", info.end_point.trunk, info.end_point.channel or "", info.end_point.device_id)
  end
  return string.format("%s", info.serial_no or "")
end

---
--- @brief  Get identifier text for info item.
--- @param  control_id - control id of the selected item
--- @return String representing the identifier either end_point for serial_no
function get_end_point_text_for_control_id(control_id)
  local item = connected_equipment_status[control_id]
  if item then
    return get_end_point_text(item.info)
  end
end

---
--- @brief  Processes the status event from the backend
--- @param  gre#context mapargs
---
function cb_process_connected_equipment_error(mapargs)
  local info = loadstring(mapargs.context_event_data.text)()

  local _,item = find_status_item(info)

  if item then
    -- Update current severity level for item.
    item.severity_level = info.severity_level
    update_fault_tile()
    set_communications_value()
    update_status_info_tile()
  else
    local event = string.format("get_specific_%s_status_info", mapargs.context_event:remove_trailing("_error"))
    send_event(event,  get_end_point_text(info))

    local item = {
      active = false,
      severity_level = info.severity_level,
      info = {
        end_point = info.end_point,
        serial_no = info.serial_no,
      },
    }

    control_id_counter = control_id_counter + 1
    connected_equipment_status[CONNECTED_EQUIPMENT_STATUS_ACTIVE_LIST.."."..tostring(control_id_counter)] = item
  end
end

---
--- @brief  Fetch basic info for signs
--- @return table of available signs
function get_connected_signs_list()
  local list = {}
  for _,item in pairs(connected_equipment_status) do
    if item.info and item.info.sign_data then
      table.insert(list, {
        equipment_name = item.info.equipment_name,
        end_point = item.info.end_point,
        variant_name = item.info.sign_data.variant_name
      })
    end
  end
  return list
end

---
--- @brief  Fetch the sign equipment name for the specified info.
--- @param  info - connected device information.
--- @return String representing the equipment name or nil if not found.
function get_sign_equipment_name(info)
  local _,item = find_status_item(info)
  return item and item.info and item.info.equipment_name
end

---
--- @brief  Select the item and go to detailed view.
--- @param  control_id - control id of the selected item
function connected_equipement_status_select(control_id)
  gre.set_value(CONNECTED_EQUIPMENT_STATUS_SCREEN..".control_id", control_id)
  screen_transition(CONNECTED_EQUIPMENT_STATUS_SCREEN)
end

---
--- @brief  Call back for when touching status selection
--- @param  gre#context mapargs
function cb_connected_equipement_status_select(mapargs)
  connected_equipement_status_select(mapargs.context_control)
end

---
--- @brief  Fetch first OBC control ID with a preference for the first responsive OBC.
--- @return control ID or nil
local current_responsive_obc_control_id
function cb_fetch_first_obc_control_id()
  local local_host_control_id
  local network_host_control_id
  local first_responsive_control_id
  local first_non_responsive_control_id

  -- Categorise OBCs and identify the first responsive OBC and any local or network host OBCs
  local categorize_obc = function(k, v)
    if v.info.obc_data == nil then return end
    if v.info.end_point.trunk == "UNDEFINED" then return end
    if v.info.end_point.trunk:starts_with("udp://127.0.0.1:") or
       v.info.end_point.trunk:starts_with("udp://localhost:") then
      local_host_control_id = k
    elseif v.info.end_point.trunk:starts_with("udp://") then
      network_host_control_id = k
    elseif v.info.obc_data.responsive_flag == 1 then
      first_responsive_control_id = first_responsive_control_id or k
    else
      first_non_responsive_control_id = first_non_responsive_control_id or k
    end
  end

  for k,v in pairs(connected_equipment_status) do
    categorize_obc(k,v)
  end

  -- Update current if a responsive OBC is found, otherwise keep current
  current_responsive_obc_control_id = first_responsive_control_id or current_responsive_obc_control_id

  return local_host_control_id or
         network_host_control_id or
         current_responsive_obc_control_id or
         first_non_responsive_control_id
end

---
--- @brief  Check if the the control id is an OBC
--- @param  control_id - control id to check
--- @return true if the control id is an OBC
function check_control_id_is_obc(control_id)
  if control_id == nil or connected_equipment_status[control_id] == nil then return end
  return connected_equipment_status[control_id].info.obc_data ~= nil
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 check_virtual_obc(control_id)
  if control_id == nil or connected_equipment_status[control_id] == nil then return end
  return tonumber(connected_equipment_status[control_id].info.end_point.trunk) == sbsettings.get_virtual_htc_terminal_port()
end

---
--- @brief  Fetch value for number of connected equipment items in the list.
--- @return String representing the number of connected equipment items.
function get_set_communications()
  local count = 0
  for _,item in pairs(connected_equipment_status) do
    if item.active then
      count = count + 1
    end
  end
  return string.format("[ %d ]",  count)
end

---
--- @brief  Resets all the status errors.
---
function cb_reset_connected_equipment_status_errors()
  for _,item in pairs(connected_equipment_status) do
    if status_fault_active(item) then
      if item.info.stft_data ~= nil then
        local serial_no = item.info.serial_no
        send_event("clear_stft_error", serial_no)
        send_event("get_specific_stft_status_info", serial_no)
      else
        local endpoint_text = get_end_point_text(item.info)
        if item.info.ibis_data then
          send_event("clear_ibis_error", endpoint_text)
          send_event("get_specific_ibis_status_info", endpoint_text)
        elseif item.info.obc_data then
          send_event("clear_obc_error", endpoint_text)
          send_event("get_specific_obc_status_info", endpoint_text)
        elseif item.info.cibor_data then
          send_event("clear_cibor_error", endpoint_text)
          send_event("get_specific_cibor_status_info", endpoint_text)
        elseif item.info.hogia_data then
          send_event("clear_hogia_error", endpoint_text)
          send_event("get_specific_hogia_status_info", endpoint_text)
        else
          send_event("clear_sign_error", endpoint_text)
          send_event("get_specific_sign_status_info", endpoint_text)
        end
      end
      item.active = nil
    end
  end
  update_fault_tile()
  set_communications_value()
end

---
--- @brief  Show status for CIBOR
--- @return control ID or nil
function cb_show_cibor_status(mapargs)
  -- Find first CIBOR control ID
  local control_id
  for k,v in pairs(connected_equipment_status) do
    if v.info.cibor_data ~= nil then
      control_id = k
      break
    end
  end
  if not control_id then return end

  gre.set_value(CONNECTED_EQUIPMENT_STATUS_SCREEN..".control_id", control_id)
  screen_transition(CONNECTED_EQUIPMENT_STATUS_SCREEN)
end

---
--- @brief  Show status for Hogia
--- @return control ID or nil
function cb_show_hogia_status(mapargs)
  -- Find first Hogia control ID
  local control_id
  for k,v in pairs(connected_equipment_status) do
    if v.info.hogia_data ~= nil then
      control_id = k
      break
    end
  end
  if not control_id then return end

  gre.set_value(CONNECTED_EQUIPMENT_STATUS_SCREEN..".control_id", control_id)
  screen_transition(CONNECTED_EQUIPMENT_STATUS_SCREEN)
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

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