---
-- @package   eg4-crank-ui
-- @file      datetime.lua
-- @brief     Handles date/time updates to the display
--
-- @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.
--
--

--- 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 = 1
local TIME_ZONE_FOLDER = "/usr/share/zoneinfo"
local LOCALTIME_SYMLINK = "/mnt/hos-state/root-overlay/etc/localtime"
local time_zone_selection


---
-- @brief  Updates the date and time on the display.
--
function CBUpdateDateTime()
  time_format = time_format or (sbsettings and sbsettings.get_time_format()) or 1
  local format
  if time_format == 0 then
    format = "%I:%M %p"
  else
    format = "%H:%M"
  end

  format = format.."\n"

  date_format = date_format or (sbsettings and sbsettings.get_date_format()) or 1
  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.."%m/%d/%y"
  else
    format = format.."%d/%m/%Y"
  end

  local value = os.date(format)
  value = value:split("\n")

  if time_format == 0 then
    value[1] = string.lower(value[1])
    -- strip the leading zero.
    if (string.sub(value[1], 1, 1) == '0') then
      value[1] = string.sub(value[1], 1 - #value[1])
    end
  end

  if last_time ~= value[1] then
    last_time = value[1]
    gre.set_data({ ["current_time"] = value[1], ["current_date"] = value[2] })
  end
end

---
-- @brief  Update time format.
--
function CBUpdateTimeFormat()
  -- Force read setting and update.
  time_format = nil
  last_time = ""
end

---
-- @brief  Initialise date format.
--
function CBUpdateDateFormat() 
  date_format = nil
  last_time = ""
end

---
-- @brief  Read time zone setting
--
local function ReadTimeZoneSetting()
  local value = fileinfo:execute('readlink -f '..LOCALTIME_SYMLINK)
  if #value >= 1 then
    value = string.split(value[1], TIME_ZONE_FOLDER.."/", 1)
    if #value ~= 2 then return nil end
    return value[2];
  end
  return nil
end

---
--- @brief  Time zone has changed externally, force update.
---
function CBTimeZoneChanged()
  SetTimeZone(ReadTimeZoneSetting())
end

---
-- @brief  Initialise the clock system menu
--
--- @param gre#context mapargs
function CBInitClockSettingsMenu(mapargs)
  gre.set_value("date_time_settings_layer.menu_items.time_zone.value", ReadTimeZoneSetting() or gre.get_value("date_time_settings_layer.menu_items.time_zone.utc_text"))
  CBUpdateClockSettingsMenu(mapargs)
end

---
-- @brief  Do the periodic update the clock setting menu 
--
--- @param gre#context mapargs
function CBUpdateClockSettingsMenu(mapargs)
  local day = tonumber(os.date("%d"))
  local month = tonumber(os.date("%m"))
  local year = tonumber(os.date("%Y"))
  gre.set_value("date_time_settings_layer.menu_items.set_date.value", tostring(day).." "..gre.get_value(string.format("set_date_layer.month.value.text.%d.1", month + 1)).." "..tostring(year))
end

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

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

---
-- @brief  Update the date spinner control for new month/year
--
--- @param gre#context mapargs
local function UpdateDateControl(month, year)
  data = {}

  days = GetDaysInMonth(month, year)
  if GetRow("set_date_layer.date.value") > days then
    SetRow(data, "set_date_layer.date.value", days)
  end

  InitialiseRoller(data, "set_date_layer.date.value", "%02d", 1, days)

  gre.set_data(data)
end

---
-- @brief  Initialise all the items in the year control.
--
--- @param gre#context mapargs
local function InitYearControl()
  data = {}
  InitialiseRoller(data, "set_date_layer.year.value", "%d", YEAR_START, YEAR_END)
  gre.set_data(data)
end

---
-- @brief  Initialise the set date screen
--
--- @param gre#context mapargs
function CBInitDateScreen(mapargs)
  InitYearControl()

  local now = os.date("*t")

  UpdateDateControl(now.month, now.year)

  local data = {}
  SetRow(data, "set_date_layer.date.value", now.day)
  SetRow(data, "set_date_layer.month.value", now.month)
  SetRow(data, "set_date_layer.year.value", now.year - YEAR_START + 1)
  gre.set_data(data)
end

---
-- @brief  Initialise the a time setting layer
-- @param  layer - layer containing time setting controls
-- @param  date - date/time to display in the structure provided by os.date("*t")
--
function InitTimeScreen(layer, date)
  -- Set the items in hours according to 
  data = {}
  if time_format == 1 then
    InitialiseRoller(data, layer..".hour.value", "%02d", 0, 23)
    data[layer..".am_pm.grd_hidden"] = 1
    data[layer..".select_line.grd_width"] = gre.get_value(layer..".minute.grd_x") + gre.get_value(layer..".minute.value.grd_width") - gre.get_value(layer..".select_line.grd_x")
    SetRow(data, layer..".hour.value", date.hour + 1)
  else
    InitialiseRoller(data, layer..".hour.value", "%d", 1, 12)
    data[layer..".am_pm.grd_hidden"] = 0
    data[layer..".select_line.grd_width"] = gre.get_value(layer..".am_pm.grd_x") + gre.get_value(layer..".am_pm.value.grd_width") - gre.get_value(layer..".select_line.grd_x")
    if date.hour < 12 then
      if date.hour == 0 then
        date.hour = 12
      end
      SetRow(data, layer..".hour.value", date.hour)
      SetRow(data, layer..".am_pm.value", 1)
    else
      date.hour = date.hour - 12
      if date.hour == 0 then
        date.hour = 12
      end
      SetRow(data, layer..".hour.value", date.hour)
      SetRow(data, layer..".am_pm.value", 2)
    end
  end
  SetRow(data, layer..".minute.value", date.min + 1)
  gre.set_data(data)
end

---
-- @brief  Initialise the set time screen
--
--- @param gre#context mapargs
function CBInitTimeScreen(mapargs)
  InitTimeScreen("set_time_layer", os.date("*t"))
end

---
-- @brief  Sets the new date using the format requested
--
--- @param gre#context mapargs
local function SetDate(format)
  -- Define a function to run the os.execute() command
  local function cmd()
    os.execute(string.format("date --set=\"$(date +'%s')\"", format)..' ; hwclock -u -w')
  end

  -- Create a coroutine to run the command
  local co = coroutine.create(cmd)

  -- Resume the coroutine to start the command
  coroutine.resume(co)

  last_time = ""
  CBUpdateDateTime()
  
end

---
-- @brief  Sets the day of the month
--
--- @param gre#context mapargs
function CBSetDateDay(mapargs)
  local value = GetRow(mapargs.context_control)
  SetDate(string.format("%%Y-%%m-%02d %%H:%%M:%%S", value))
end

---
-- @brief  Sets the month
--
--- @param gre#context mapargs
function CBSetDateMonth(mapargs)
  local value = GetRow(mapargs.context_control)
  local year = GetRow("set_date_layer.year.value") + YEAR_START - 1
  UpdateDateControl(value, year)
  SetDate(string.format("%%Y-%02d-%%d %%H:%%M:%%S", value))
end

---
-- @brief  Sets the year
--
--- @param gre#context mapargs
function CBSetDateYear(mapargs)
  local value = GetRow(mapargs.context_control) + YEAR_START - 1
  local month = GetRow("set_date_layer.month.value")
  UpdateDateControl(month, value)
  SetDate(string.format("%04d-%%m-%%d %%H:%%M:%%S", value))
end

---
-- @brief  Sets the hour
--
--- @param gre#context mapargs
function CBSetTimeHour(mapargs)
  local value = GetRow(mapargs.context_control)
  if gre.get_value("set_time_layer.am_pm.grd_hidden") == 1 then
    value = value - 1
  else
    local am_pm = GetRow("set_time_layer.am_pm.value")
    if am_pm == 2 then
      value = value + 12
    end
  end
  SetDate(string.format("%02d:%%M:%%S", value))
end

---
-- @brief  Sets the minute
--
--- @param gre#context mapargs
function CBSetTimeMinute(mapargs)
  local value = GetRow(mapargs.context_control) - 1
  SetDate(string.format("%%H:%02d", value))
end

---
-- @brief  Sets AM/PM
--
--- @param gre#context mapargs
function CBSetTimeAMPM(mapargs)
  local hour = GetRow("set_time_layer.hour.value")
  local value = GetRow(mapargs.context_control)
  if value == 2 then
    hour = hour + 12
  end
  SetDate(string.format("%02d:%%M:%%S", hour))
end

---
-- @brief  Populate Time Zone control from currently selected time zone regions and the zone1970.tab file
--
local function PopulateTimeZones(control)
  local filepath = TIME_ZONE_FOLDER.."/zone1970.tab"
  if not fileinfo:isfile(filepath) then return end

  -- Collate time zones for the currently selected time zone region(s).
  local time_zones = {}
  for line in io.lines(filepath) do 
    if string.sub(line, 1, 1) ~= "#" then
      local table_fields = string.split(line, "\t")
      if #table_fields >= 3 then
        local time_zone = string.split("/"..table_fields[3], time_zone_selection, 1)
        if #time_zone >= 2 then
          local sub_time_zones = 0
          time_zone = string.split(time_zone[2], "/")
          if #time_zone > 1 then
            sub_time_zones = 255
          end 
          time_zones[time_zone[1]] = sub_time_zones
        end  
      end
    end 
  end

  -- Add collated regions to the table control
  local data = {}
  local rows = 0
  for text,alpha in sorted_pairs(time_zones) do 
    if fileinfo:exists(TIME_ZONE_FOLDER..time_zone_selection..text) then
      rows = rows + 1
      data[control..".text."..rows..".1"] = text
      data[control..".alpha."..rows..".1"] = alpha
      data[control..".fade."..rows..".1"] = 0
    end
  end

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

---
-- @brief  Selected time zone region, check if sub regions available
--
function CBTimeZoneSelectItem(mapargs)
  if ScrollActive() then return end
  
  -- Get the selected region
  time_zone_selection = time_zone_selection..gre.get_value(mapargs.context_control..".text."..mapargs.context_row..".1")
  if gre.get_value(mapargs.context_control..".alpha."..mapargs.context_row..".1") == 255 then
    -- Populate list with items from selected time zone region.
    time_zone_selection = time_zone_selection.."/"
    PopulateTimeZones(mapargs.context_control)
    gre.set_layer_attrs_global(mapargs.context_layer, { yoffset = 0 })
    SBCInitialiseScrollbar(mapargs)
  else  
    -- Set the new time zone.
    os.execute("ln -snf "..TIME_ZONE_FOLDER..time_zone_selection.." "..LOCALTIME_SYMLINK)
    -- Reload time zone data (strip leading forward slash)
    SetTimeZone(time_zone_selection:sub(2))
    SendEvent("time_zone_changed")
    TransitionBackScreen(mapargs)
  end
end

---
-- @brief  Start the time zone select process
--
function CBInitTimeZoneScreen(mapargs)
  time_zone_selection = "/"
  PopulateTimeZones("time_zone_layer.time_zone_group.items")
end

---
-- @brief  Initialise network time servers screen
--
function CBInitNetworkTimeServersScreen(mapargs)
  local control = "network_time_servers_layer.group.items"
  local list = sbsettings.read_network_time_user_servers()
  local rows = 0
  local data = {}
  for _,text in pairs(list) do 
    rows = rows + 1
    data[control..".text."..rows..".1"] = text
  end
  gre.set_data(data)
  if rows == 0 then
    gre.set_table_attrs(control, {hidden = 1})
  else
    gre.set_table_attrs(control, {hidden = 0, rows = rows, height = rows * 81})
  end
end

---
-- @brief  Add network time server
--
function CBAddNetworkTimeServer(mapargs)
  sbsettings.add_network_time_user_server(mapargs.value)
  SendEvent("network_time_settings_updated")
  TransitionBackScreen(mapargs)
end

---
-- @brief  Remove network time server
--- @param gre#context mapargs
function CBRemoveNetworkTimeUserServer(mapargs)
  sbsettings.remove_network_time_user_server(gre.get_value(mapargs.context_group..".port_id"))
  SendEvent("network_time_settings_updated")
  TransitionBackScreen(mapargs)
end

-- EOF --
