--[[
  @package eg4-crank-ui-2
  @file    date-time.lua
  @brief   Handles date/time updates to the display
]]

--- Record of last time the display was updated.
local YEAR_START = 2020
local YEAR_END = 2099
local last_time = "--"
local time_format
local date_format
local real_time_clock_enabled

---
--- @brief  Determine whether the home screen clock should be visible based on the current setting and hardware support
local function is_home_screen_clock_visible()
  return get_real_time_clock_enabled() and get_show_home_screen_date_time() ~= 0
end

---
--- @brief  Update the visibility of the home screen clock based on the current setting and hardware support
function update_home_screen_clock_visibility()
  gre.set_value("home_status_bar_layer.clock.grd_hidden", is_home_screen_clock_visible() and 0 or 1)
end

---
--- @brief  Get real-time clock is enabled
--- @return true if enabled
function get_real_time_clock_enabled()
  real_time_clock_enabled = real_time_clock_enabled or #fileinfo:find_folder("/sys/class/rtc/rtc0")
  return real_time_clock_enabled ~= 0
end

---
--- @brief  Determine whether the date/time settings are available
--- @return true if enabled
function show_date_time_settings()
  return real_time_clock_enabled
end

---
--- @brief  Updates the date and time on the display.
function cb_update_clock()
  time_format = get_time_format()
  date_format = get_date_format()

  local format = time_format == 0 and "%I:%M %p," or "%H:%M,"
  if date_format == 3 then
    format = format.."%Y/%m/%d"
  elseif date_format == 2 then
    format = format.."%m/%d/%Y"
  elseif date_format == 0 then
    format = format.."%d/%m/%y"
  else
    format = format.."%d/%m/%Y"
  end

  local value = os.date(format)
  if last_time == value then return end
  last_time = value

  value = value:split()

  if time_format == 0 then
    -- Strip the leading zeros and convert to lower case
    value[1] = value[1]:gsub("^0", ""):lower()
  end

  gre.set_value("clock_time", value[1], "clock_date", value[2])
end

---
--- @brief  Force the clock to be updated on the next tick
function force_clock_update()
  last_time = ""
end

---
--- @brief  Writes the new date to the hardware clock
--- @note   The hardware clock is updated 5s after the last change to the clock.
local update_rtc_timer = nil
local function update_hardware_rtc()
  -- Update the hardware clock 5s after the last change to the clock.
  if update_rtc_timer then gre.timer_clear_timeout(update_rtc_timer) end
  update_rtc_timer = gre.timer_set_timeout(5000, function()
    update_rtc_timer = nil
    os.execute("hwclock -u -w &")
  end)
end

---
--- @brief  Sets the new date using the format requested
--- @param format - format string for date command
local function set_date(format)
  os.execute(string.format("date --set=\"$(date +'%s')\"", format))
  update_hardware_rtc()
  last_time = ""
end

---
--- @brief  Check for leap year
local function is_leap_year(year)
  return year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0)
end

---
--- @brief  Check number of days in month
local function days_in_month(month, year)
  return month == 2 and is_leap_year(year) and 29
    or ("\31\28\31\30\31\30\31\31\30\31\30\31"):byte(month)
end

---
--- @brief  Initialise the set date screen
--- @param gre#context mapargs
function cb_init_set_date(mapargs)
  local context_layer = mapargs.context_layer or "date_setting_layer"
  local now = os.date("*t")
  local data = {}
  data[context_layer..".day.selected.text"] = now.day
  data[context_layer..".month.selected.value"] = now.month
  data[context_layer..".month.selected.text"] = gre.get_value(context_layer..".month.selected.months."..now.month..".1")
  data[context_layer..".year.selected.text"] = now.year
  gre.set_data(data)
end

---
--- @brief Update the year value and set the new date
--- @param mapargs
function cb_date_year_change(mapargs)
  local context_control = mapargs.context_group..".selected"
  local value = tonumber(gre.get_value(context_control..".text"))
  value = value + tonumber(mapargs.dir)
  if value < 2020 then value = 2099 end
  if value > 2099 then value = 2020 end
  gre.set_value(context_control..".text", value)

  -- Correct the date value if out of range
  cb_date_day_change({
    context_group = mapargs.context_layer..".day",
    dir = 0,
    context_layer = mapargs.context_layer
  })

  set_date(string.format("%04d-%%m-%%d %%H:%%M:%%S", value))
end

---
--- @brief Update the month value and set the new date
--- @param mapargs
function cb_date_month_change(mapargs)
  local context_control = mapargs.context_group..".selected"
  local value = tonumber(gre.get_value(context_control..".value"))
  value = value + tonumber(mapargs.dir)
  if value < 1 then value = 12 end
  if value > 12 then value = 1 end
  gre.set_value(context_control..".value", value,
                context_control..".text", gre.get_value(context_control..".months."..value..".1"))

  -- Correct the date value if out of range
  cb_date_day_change({
    context_group = mapargs.context_layer..".day",
    dir = 0,
    context_layer = mapargs.context_layer
  })

  if value == os.date("*t").month then return end
  set_date(string.format("%%Y-%02d-%%d %%H:%%M:%%S", value))
end

---
--- @brief Update the day value and set the new date
--- @param mapargs
function cb_date_day_change(mapargs)
  local month = tonumber(gre.get_value(mapargs.context_layer..".month.selected.value"))
  local year = tonumber(gre.get_value(mapargs.context_layer..".year.selected.text"))
  local days = days_in_month(month, year)
  local context_control = mapargs.context_group..".selected"
  local value = tonumber(gre.get_value(context_control..".text"))
  value = value + tonumber(mapargs.dir)
  if value < 1 then value = days end
  if value > days then value = 1 end
  gre.set_value(context_control..".text", value)

  if value == os.date("*t").day then return end
  set_date(string.format("%%Y-%%m-%02d %%H:%%M:%%S", value))
end

---
--- @brief  Initialise the a time setting layer
--- @param  context_layer - layer containing time setting controls
--- @param  date - date/time to display in the structure provided by os.date("*t")
function init_set_time(context_layer, date)
  -- Set the items in hours according to
  local data = {}
  if time_format == 1 or not gre.get_value(context_layer..".am_pm.grd_x") then
    data[context_layer..".hour.selected.text"] = string.format("%02d", date.hour)
    data[context_layer..".minute.selected.text"] = string.format("%02d", date.min)
    data[context_layer..".am_pm.grd_hidden"] = 1
  else
    local hour = date.hour % 12
    if hour == 0 then hour = 12 end
    data[context_layer..".hour.selected.text"] = hour
    data[context_layer..".minute.selected.text"] = string.format("%02d", date.min)
    data[context_layer..".am_pm.selected.data"] = date.hour < 12 and 1 or 2
    data[context_layer..".am_pm.selected.value"] = gre.get_value(context_layer..".am_pm.selected.text."..(date.hour < 12 and 1 or 2)..".1")
    data[context_layer..".am_pm.grd_hidden"] = 0
  end
  gre.set_data(data)
end

---
--- @brief  Initialise the set time screen
function cb_init_set_time()
  init_set_time("time_setting_layer", os.date("*t"))
end

---
--- @brief Update the hour and am/pm values
--- @param mapargs
--- @return hour value
function cb_time_hour_edit(mapargs)
  local hour = tonumber(gre.get_value(mapargs.context_group..".selected.text"))
  hour = hour + tonumber(mapargs.dir)
  if time_format == 1 or not gre.get_value(mapargs.context_layer..".am_pm.grd_x") then
    if hour < 0 then hour = 23
    elseif hour > 23 then hour = 0 end
    gre.set_value(mapargs.context_group..".selected.text", string.format("%02d", hour))
  else
    if hour < 1 then
      hour = 12
    elseif hour > 12 then
      hour = 1
    end

    local am_pm = tonumber(gre.get_value(mapargs.context_layer..".am_pm.selected.data"))

    -- check for am/pm rollover
    if (hour == 11 and tonumber(mapargs.dir) == -1) or (hour == 12 and tonumber(mapargs.dir) == 1) then
      am_pm = 3 - am_pm
    end
    gre.set_value(mapargs.context_group..".selected.text", hour,
                  mapargs.context_layer..".am_pm.selected.data", am_pm,
                  mapargs.context_layer..".am_pm.selected.value", gre.get_value(mapargs.context_layer..".am_pm.selected.text."..(am_pm)..".1"))

    -- Convert to 24hr clock
    if am_pm == 1 then
      if hour == 12 then hour = 0 end
    else
      if hour < 12 then hour = hour + 12 end
    end
  end
  return hour
end

---
--- @brief Update the hour and am/pm values and set the new time
--- @param mapargs
function cb_time_hour_change(mapargs)
  set_date(string.format("%02d:%%M:%%S", cb_time_hour_edit(mapargs)))
end

---
--- @brief Update the minute value
--- @param mapargs
--- @return minute value
function cb_time_minute_edit(mapargs)
  local value = tonumber(gre.get_value(mapargs.context_group..".selected.text"))
  value = value + tonumber(mapargs.dir)
  if value < 0 then value = 59
  elseif value > 59 then value = 0 end
  gre.set_value(mapargs.context_group..".selected.text", string.format("%02d", value))
  return value
end

---
--- @brief Update the minute value and set the new time
--- @param mapargs
function cb_time_minute_change(mapargs)
  set_date(string.format("%%H:%02d", cb_time_minute_edit(mapargs)))
end


---
--- @brief  Fetches the hour:minute from the setting controls
--- @param  layer - layer containing time setting controls
--- @return string formatted as HH:MM in 24 hout clock.
function get_time_setting(layer)
  local hour = tonumber(gre.get_value(layer..".hour.selected.text"))
  if gre.get_value(layer..".am_pm.grd_hidden") == 0 then
    -- Convert to 24hr clock
    local am_pm = tonumber(gre.get_value(layer..".am_pm.selected.data"))
    if am_pm == 1 then
      if hour == 12 then hour = 0 end
    else
      if hour < 12 then hour = hour + 12 end
    end
  end
  local minute = tonumber(gre.get_value(layer..".minute.selected.text"))
  return string.format("%02d:%02d", hour, minute)
end

---
--- @brief  Initialise network time servers screen
--- @param gre#context mapargs
function cb_initialise_network_time_servers(mapargs)
  local control = get_list_control(mapargs.context_screen)
  local list = sbsettings.read_network_time_user_servers()
  table.sort(list)
  local data = {}
  for i,text in ipairs(list) do
    data[control..".text."..i..".1"] = text
    data[control..".alpha_pressed."..i..".1"] = 0
  end
  gre.set_data(data)
  local height = gre.get_table_cell_attrs(control, 1, 1, "height").height
  gre.set_table_attrs(control,{ rows = #list, height = #list * height })
  cb_update_menu(mapargs)
end

---
--- @brief  Initialise the edit network time server screen
--- @param gre#context mapargs
local EDIT_SCREEN = "network_time_server_edit"
function cb_network_time_server_selected(mapargs)
  local server = gre.get_value(mapargs.context_control..".text."..mapargs.context_row..".1")
  gre.set_value(EDIT_SCREEN..".heading", server,
                EDIT_SCREEN..".port_id", server)
  screen_transition(EDIT_SCREEN)
end

---
--- @brief  Add network time server
--- @param  value - server name
function cb_add_network_time_server(value)
  gre.set_value(EDIT_SCREEN..".heading", value,
                EDIT_SCREEN..".port_id", value)
  sbsettings.add_network_time_user_server(value)
  send_event("network_time_settings_updated")
  screen_transition(EDIT_SCREEN)
  return true
end

---
--- @brief  Remove network time server
--- @param gre#context mapargs
function cb_remove_network_time_server(mapargs)
  sbsettings.remove_network_time_user_server(gre.get_value(mapargs.context_screen..".port_id"))
  send_event("network_time_settings_updated")
  cb_back_screen(mapargs)
end

