--[[
  @package eg4-crank-ui-2
  @file    digital-io.lua
  @brief   Handles the digital I/O settings and status.
]]


local LIST_LAYER = "digital_io_list_layer"

---
--- @brief  Data source for the digital I/O status tile
function ds_digital_io_status()
  local data = {}

  local list = sbsettings.read_digital_input_list()
  for _,port_id in ipairs(list) do
    local value = sbsettings.get_digital_input_state(port_id)
    if sbsettings.get_digital_input_use(port_id) == 0 then
      data["digital_input_"..port_id..".alpha_undefined"] = 255
      data["digital_input_"..port_id..".alpha_active"   ] = 0
      data["digital_input_"..port_id..".alpha_inactive" ] = 0
    else
      data["digital_input_"..port_id..".alpha_undefined"] = 0
      data["digital_input_"..port_id..".alpha_active"   ] = value == 1 and 255 or 0
      data["digital_input_"..port_id..".alpha_inactive" ] = value == 1 and 0 or 255
    end
  end

  local list = sbsettings.read_digital_output_list()
  for _,port_id in ipairs(list) do
    local value = get_digital_output_state(port_id)
    if sbsettings.get_digital_output_use(port_id) == 0 then
      data["digital_output_"..port_id..".alpha_undefined"] = 255
      data["digital_output_"..port_id..".alpha_active"   ] = 0
      data["digital_output_"..port_id..".alpha_inactive" ] = 0
    else
      data["digital_output_"..port_id..".alpha_undefined"] = 0
      data["digital_output_"..port_id..".alpha_active"   ] = value == 1 and 255 or 0
      data["digital_output_"..port_id..".alpha_inactive" ] = value == 1 and 0 or 255
    end
  end

  return data
end

---
--- @brief  Make the port id option text
--- @param control - control to get format from.
--- @param port_id - text for port ID
--- @param device_type - specfies input or output
---
local function make_io_port_id(control, port_id, device_type)
  local fmt = gre.get_value(control.."."..device_type)
  return string.format(fmt, port_id)
end

---
--- @brief  Make the port id option text
--- @param data - data to populate
--- @param screen - Screen to populate
--- @param port_id - text for port ID
--- @param device_type - specfies input or output
local populated = { count = 0 }
local function update_digital_io_item(data, screen, port_id, device_type)
  local control = get_list_control(screen)

  -- Determine if item is already present.
  local ref = device_type.."."..port_id
  local index = populated[ref]
  if index == nil then
    populated.count = populated.count + 1
    index = populated.count
    populated[ref] = index
    -- Add the new item to the menu list.
    data[string.format(control..".text.%d.1", index)] = make_io_port_id(control, port_id, device_type)
    data[string.format(control..".port_id.%d.1", index)] = port_id
    data[string.format(control..".device_type.%d.1", index)] = device_type
    data[string.format(control..".alpha_pressed.%d.1", index)] = 0
    local height = gre.get_table_cell_attrs(control, 1, 1, "height").height
    gre.set_table_attrs(control, { height = index * height, rows = index })
  end

  local fn = "get_"..device_type.."_use"
  fn = _G[fn] or sbsettings[fn]
  local use = fn(port_id)
  local value = ""
  if type(use) == "table" then
    local sep = ""
    for _,v in ipairs(use) do
      if v.checked == 1 then
        value = value ..sep .. v.text
        sep = ","
      end
    end
  else
    value = gre.get_value(string.format("%s_edit_layer.menu.%s_use.radio.%d.1", device_type, device_type, use + 1))
  end
  data[string.format(control..".value.%d.1", index)] = value

  -- Check if nothing assigned or use is undefined.
  if value == "" or use == 0 then
    data[string.format(control..".state.%d.1", index)] = ""
  else
    local fn = "get_"..device_type.."_state"
    fn = _G[fn] or sbsettings[fn]
    local state = gre.get_value(control.."."..(fn(port_id) ~= 0 and "active" or "inactive").."_text")
    data[string.format(control..".state.%d.1", index)] = state
  end
end

---
--- @brief Update the digital I/O list
--- @param screen - The screen to update
local function update_digital_io_list(screen)
  local data = {}
  local list = sbsettings.read_digital_input_list()
  for _,port_id in ipairs(list) do
    update_digital_io_item(data, screen, port_id, "digital_input")
  end
  local list = sbsettings.read_digital_output_list()
  for _,port_id in ipairs(list) do
    update_digital_io_item(data, screen, port_id, "digital_output")
  end
  gre.set_data(data)
end

---
--- @brief  Callback to update the digital I/O list
--- @param  mapargs  The mapargs from the event
function cb_update_digital_io_list(mapargs)
  update_digital_io_list(mapargs.context_screen)
  cb_update_menu(mapargs)
end

---
--- @brief  Callback to process the selected digital I/O item
--- @param  mapargs  The mapargs from the event
function cb_digital_io_selected(mapargs)
  local control = mapargs.context_control..".%s."..mapargs.context_row..".1"
  mapargs.screen = gre.get_value(control:format("device_type")).."_edit"
  gre.set_value(
    mapargs.screen..".port_id", gre.get_value(control:format("port_id")),
    mapargs.screen..".heading", gre.get_value(control:format("text")))
  cb_maintenance_settings_start(mapargs)
end

---
--- @brief  Check if use is set to message code
function show_custom_announcement_info_code(context_screen)
  local port_id = gre.get_value(context_screen..".port_id")
  return sbsettings.get_digital_input_use(port_id) == 12
end

---
--- @brief  Check if use is set to message code
function show_digital_input_priority_message(context_screen)
  local port_id = gre.get_value(context_screen..".port_id")
  local value = sbsettings.get_digital_input_use(port_id)
  return value == 4 or value == 8
end

---
--- @brief  Check if use is set to battery guard
function show_sign_blanking_level(context_screen)
  local port_id = gre.get_value(context_screen..".port_id")
  return port_id == nil or sbsettings.get_digital_input_use(port_id) == 6
end

---
--- @brief  Check if use is set to battery guard
function show_sign_blanking_timeout(context_screen)
  local port_id = gre.get_value(context_screen..".port_id")
  return port_id == nil or sbsettings.get_digital_input_use(port_id) == 6
end

---
--- @brief  Check if use is set to power save
function show_power_save_low_power_delay(context_screen)
  local port_id = gre.get_value(context_screen..".port_id")
  return sbsettings.get_digital_input_use(port_id) == 7
end

---
--- @brief  Check if use is set to emergency
function show_digital_input_reset_type(context_screen)
  local port_id = gre.get_value(context_screen..".port_id")
  return sbsettings.get_digital_input_use(port_id) == 3
end

---
--- @brief  Callback to update the digital input message code
--- @param  value  The new value
--- @return true if the value is valid
function cb_digital_input_message_code_submit(value)
  if not sbsettings.validate_info_code(value) then return end
  local port_id = gre.get_value(gre.env("active_screen")..".port_id")
  sbsettings.set_digital_input_message_code(value, port_id)
  cb_back_screen()
  return true
end

---
--- @brief  Start the destination code entry
--- @param  mapargs  The mapargs from the event
local function start_destination_code_entry(mapargs)
  gre.set_data({
    [ mapargs.context_control..".entry_box_request"    ] = gre.get_value(mapargs.context_control..".request_destination_code"),
    [ mapargs.context_control..".entry_event"          ] = "settings_updated",
    [ mapargs.context_control..".entry_max_len"        ] = "get_destination_code_len",
    [ mapargs.context_control..".entry_submit"         ] = "cb_digital_input_destination_code_submit",
    [ mapargs.context_control..".entry_start_key_group"] = "keypad",
  })

  cb_text_entry_start(mapargs)
end

---
--- @brief  Start the destination ID entry
--- @param  mapargs  The mapargs from the event
local selected_route_code
local cached_mapargs
function cb_start_destination_id_entry(mapargs)
  selected_route_code = ""

  if get_route_code_len() == 0 then
    start_destination_code_entry(mapargs)
  end

  gre.set_data({
    [ mapargs.context_control..".entry_box_request"    ] = gre.get_value(mapargs.context_control..".request_route_code"),
    [ mapargs.context_control..".entry_event"          ] = "",
    [ mapargs.context_control..".entry_max_len"        ] = "get_route_code_len",
    [ mapargs.context_control..".entry_submit"         ] = "cb_digital_input_route_code_submit",
    [ mapargs.context_control..".entry_start_key_group"] = "keypad",
  })

  cb_text_entry_start(mapargs)
  cached_mapargs = mapargs
end

---
--- @brief  Callback to submit the digital input route code value
--- @param  value  The new value
--- @return true if the value is valid
function cb_digital_input_route_code_submit(value)
  if not sbsettings.validate_route_code(value) then return end
  selected_route_code = string.rep("0", get_route_code_len() - #value) .. value
  start_destination_code_entry(cached_mapargs)
  cached_mapargs = nil
  cb_reset_text_entry({ context_screen = gre.env("active_screen") })
  return true
end

---
--- @brief  Callback to submit the digital input destination code value
--- @param  value  The new value
--- @param  priority - priority
--- @return true if the value is valid
function cb_digital_input_destination_code_submit(value, priority)
  if not sbsettings.check_destination_id(selected_route_code, value) then return end
  value = string.rep("0", get_destination_code_len() - #value) .. value
  sbsettings.set_priority_message_code("", tostring(priority))
  sbsettings.set_priority_route_code(selected_route_code, tonumber(priority))
  sbsettings.set_priority_destination_code(value, tonumber(priority))
  cb_back_screen()
  return true
end

---
--- @brief  Get digital input priority message setting
--- @param priority - priority
--- @return encoded string
function cb_populate_priority_message_value(priority)
  local value = sbsettings.get_priority_message_code(tostring(priority))
  if value == "" then
    local route_code = sbsettings.get_priority_route_code(priority)
    local destination_code = sbsettings.get_priority_destination_code(priority)
    if route_code ~= "" or destination_code ~= "" then
      value = string.format("%s %s", route_code, destination_code):trim()
      value = gre.get_value("digital_input_edit.dest").." "..value
    end
  else
    value = gre.get_value("digital_input_edit.info").." "..value
  end
  return value
end

---
--- @brief  Get digital input priority message setting
--- @return encoded string
function get_digital_input_priority_message(port_id)
  local priority = sbsettings.get_digital_input_message_priority(port_id)
  return string.format("%d [%s]", priority, cb_populate_priority_message_value(priority))
end

---
--- @brief  Start the priority message code entry
--- @param  mapargs  The mapargs from the event
function cb_start_priority_message_edit(mapargs)
  local screen = "digital_input_priority_type"
  gre.set_value(screen..".port_id", mapargs.context_row - 1)
  screen_transition(screen)
end

---
--- @brief  Callback to submit the custom announcement info code value
--- @param  value  The new value
--- @return true if the value is valid
function cb_custom_announcement_info_code_submit(value)
  if not sbsettings.validate_info_code(value) then return end
  sbsettings.set_custom_announcement_info_code(value)
  cb_back_screen()
  return true
end

---
--- @brief  Submits the new digital input message code  
--- @param gre#context mapargs 
---
function cb_submit_digital_input_priority_message_code(value, priority)
  if not sbsettings.validate_info_code(value) then return end
  sbsettings.set_priority_message_code(value, tostring(priority))
  sbsettings.set_priority_route_code("", tonumber(priority))
  sbsettings.set_priority_destination_code("", tonumber(priority))
  cb_back_screen()
  return true
end

---
--- @brief  Fetch table for digital output use settings
--- @param  port_id - port id 
--- @return table of use settings
function get_digital_output_use(port_id)
  local context_control = "digital_output_edit_layer.menu.digital_output_use"
  local value = sbsettings.get_digital_output_use(port_id)
  local list = {}
  local i = 1
  while true do
    local text = gre.get_value(context_control..".checkbox."..i..".1")
    if text == nil then break end
    table.insert(list, { text = text, checked = value % 2 })
    value = math.floor(value / 2)
    i = i + 1
  end
  return list
end

---
--- @brief  Set digital output use setting
--- @param  value - use setting
--- @param  index - index of use setting
--- @param  port_id - port id 
function set_digital_output_use(value, index, port_id)
  local list = get_digital_output_use(port_id)
  list[index].checked = value
  local value = 0
  for i=1,#list do
    value = value + list[i].checked * 2^(i - 1)
  end
  sbsettings.set_digital_output_use(value, port_id)
end

---
--- @brief  Start the linked protocols selection screen
--- @param  gre#context mapargs
function cb_start_digital_output_use_selection(mapargs)
  mapargs.list = get_digital_output_use(mapargs.port_id)
  mapargs.parameter = mapargs.context_control:object_name()
  start_checkbox_list(mapargs)
end
