--[[
  @package    eg4-crank-ui-2
  @file       slider.lua
  @brief      Handles the slider control screen
]]


local SCREEN = "general_slider"
local LAYER = "slider_layer"

-- Local table for slider control states
local info = {}

---
--- @brief  Calculate the percentage of the slider from the current x position
--- @param  mapargs.context_event_data.x  The x position of the event
--- @return The percentage indicated by the slider
local function calc_percent(mapargs)
  local x = mapargs.context_event_data.x
  local left = gre.get_value(mapargs.context_layer..".foreground.grd_x")
  local width = gre.get_value(mapargs.context_layer..".foreground.grd_width")
  local pc = (x - left) / width
  if pc < 0 then pc = 0 end
  if pc > 1 then pc = 1 end
  return pc
end

---
--- @brief  Updates the slider control positions as a result of a value change.
--- @param  mapargs The mapargs from the event
function cb_slider_update(mapargs)
  local a = info.value_a
  local b = info.value_b
  local mark = info.mark
  local format = info.format or "%d"
  local left = a or 0
  local right = b or 0

  if left > right then
    local tmp = left
    left = right
    right = tmp
  end

  if info.step_size then
    left = math.floor(left / info.step_size + 0.5) * info.step_size
    right = math.floor(right / info.step_size + 0.5) * info.step_size
  end

  local layer = mapargs.context_layer or LAYER
  local width = gre.get_value(layer..".foreground.grd_width")
  local base = gre.get_value(layer..".foreground.grd_x")

  local data = {
    [layer..".a.grd_hidden"] = a and 0 or 1,
    [layer..".b.grd_hidden"] = b and 0 or 1,
    [layer..".a.grd_x"] = base + right * width - gre.get_value(layer..".a.grd_width") / 2,
    [layer..".b.grd_x"] = base + left * width - gre.get_value(layer..".b.grd_width") / 2,
    [layer..".mark.grd_hidden"] = info.reference and 0 or 1,
    [layer..".foreground.left"] = left * width,
    [layer..".foreground.width"] = (right - left) * width,
  }

  if b == nil then
    right = math.floor(right * info.scale + 0.5)
    data[layer..".value.text"] = string.format(format, right)
    info.set(right, info.port_id)
  else
    right = math.floor(right * info.scale + 0.5)
    left = math.floor(left * info.scale + 0.5)
    data[layer..".value.text"] = string.format(format, left, right)
    info.set(left, right, info.port_id)
  end
  gre.set_data(data)

    -- If specified, run callback
  if info.callback then
    info.callback(info.port_id, info.value_a, info.value_b)
  end

  if info.event and mapargs.context_event ~= "gre.screenshow.pre" then
    send_event(info.event)
  end
end

---
--- @brief  Handle the slider motion event
--- @param  mapargs.context_event_data.x  The x position of the event
function cb_slider_motion(mapargs)
  if not info.active then return end
  info[info.variable] = calc_percent(mapargs)
  cb_slider_update(mapargs)
end

---
--- @brief  Handle the slider press event. Initiates the slider control movement.
--- @param  mapargs The mapargs from the event
function cb_slider_press(mapargs)
  local pc = calc_percent(mapargs)
  local layer = mapargs.context_layer
  local a = info.value_a
  local b = info.value_b
  if b == nil then
    if a == nil then return end
    info.control = layer..".a"
    info.variable = "value_a"
  elseif a == nil then
    info.control = layer..".b"
    info.variable = "value_b"
  else
    if math.abs(pc - a) < math.abs(pc - b) then
      info.control = layer..".a"
      info.variable = "value_a"
    else
      info.control = layer..".b"
      info.variable = "value_b"
    end
  end

  info.value = info[info.variable]
  info[info.variable] = pc
  info.active = true
  cb_slider_update(mapargs)
end

---
--- @brief  Handle the slider release event. Finalizes the slider control movement.
--- @param  mapargs The mapargs from the event
function cb_slider_release(mapargs)
  info[info.variable] = calc_percent(mapargs)
  info.active = nil
  cb_slider_update(mapargs)
end

---
--- @brief  Handle the slider outbound event. Resets the slider control values and positions.
--- @param  mapargs The mapargs from the event
function cb_slider_outbound(mapargs)
  if not info.active then return end
  info[info.variable] = info.value
  info.active = nil
  cb_slider_update(mapargs)
end

---
--- @brief  Clear the slider control update timer
function cb_stop_slider_reference_update()
  if not info.update_timer then return end
  gre.timer_clear_interval(info.update_timer)
  info.update_timer = nil
end

---
--- @brief  Initialise and start the slider control screen
--- @param  mapargs The mapargs from the event
function cb_start_slider_setting(mapargs)
  info = {}

  local data = {
    [SCREEN..".heading"] = gre.get_value(mapargs.context_control..".text"),
    [SCREEN..".high_mark"] = gre.get_value(mapargs.context_control..".high_mark_text") or "",
    [SCREEN..".low_mark"] = gre.get_value(mapargs.context_control..".low_mark_text") or "",
  }
  gre.set_data(data)

  local setting = mapargs.context_control:object_name()

  local fn = "set_"..setting
  info.set = _G[fn] or sbsettings[fn]
  info.format = gre.get_value(mapargs.context_control..".format") or "%d"
  info.callback = _G[gre.get_value(mapargs.context_control..".callback")]
  info.event = gre.get_value(mapargs.context_control..".event")
  info.step_size = gre.get_value(mapargs.context_control..".step_size")
  info.scale = gre.get_value(mapargs.context_control..".slider_scaling") or 1
  info.port_id = gre.get_value(mapargs.context_screen..".port_id")
  info.reference = gre.get_value(mapargs.context_control..".reference")

  if info.reference then
    info.reference = _G["get_"..info.reference] or sbsettings["get_"..info.reference]
    local update = function()
      local value = info.reference(info.port_id)
      value = math.floor(value / (info.scale * info.step_size) + 0.5) * info.step_size
      local width = gre.get_value("slider_layer.foreground.grd_width")
      gre.set_value("slider_layer.mark.x", value * width)
    end
    update()
    info.update_timer = gre.timer_set_interval(update, 1000)
  end

  local fn = "get_"..setting
  info.value_a, info.value_b = (_G[fn] or sbsettings[fn])(info.port_id)

  info.value_a = info.value_a / info.scale
  if info.value_b then info.value_b = info.value_b / info.scale end

  screen_transition(SCREEN)
end

---
--- @brief  Start the numeric entry screen for slider control item
--- @param  mapargs  The mapargs from the control
function cb_slider_manual_numeric_entry(mapargs)

  -- Select which value to edit
  info.variable = "value_a"
  if info.value_b then
    if mapargs.context_event_data.x > get_screen_width() / 2 then
      info.variable = "value_b"
    end
  end

  -- Setup numeric entry
  local screen = gre.env("active_screen")
  local data = {
    [mapargs.context_control..".entry_event"] = info.event,
    [mapargs.context_control..".entry_heading"] = gre.get_value(screen..".heading"),
    [mapargs.context_control..".value"] = math.floor(info[info.variable] * info.scale + 0.5),
    [mapargs.context_control..".entry_max_len"] = 3,
    [mapargs.context_control..".entry_box_hint"] = tostring("%d - %d"):format(0, math.floor(info.scale)),
    [mapargs.context_control..".entry_submit"] = "cb_slider_manual_numeric_entry_submit",
  }
  gre.set_data(data)
  cb_numeric_entry_start(mapargs)
end

---
--- @brief  |Process the entered value for the slider control item numeric entry
--- @param  value - The entered value
function cb_slider_manual_numeric_entry_submit(value)
  value = tonumber(value)
  if value > info.scale then return end
  info[info.variable] = value / info.scale

  local a = info.value_a
  local b = info.value_b

  local left = a or 0
  local right = b or 0

  if left > right then
    local tmp = left
    left = right
    right = tmp
  end

  right = math.floor(right * info.scale + 0.5)
  if b == nil then
    info.set(right, info.port_id)
  else
    left = math.floor(left * info.scale + 0.5)
    info.set(left, right, info.port_id)
  end

  if info.callback then
    info.callback(info.port_id, info.value_a, info.value_b)
  end

  if info.event then
    send_event(info.event)
  end

  -- Return to the slider control screen
  cb_back_screen()

  return true
end
