local classlib = require("core.class")
local tablex = require("core.tablex")
local colors = require("ui.colors")
local consts = require("ui.consts")
local hooks = require("ui.hooks")
local tutorialUtil = require("ui.tutorialUtil")
local util = require("ui.util")
local lamsIDs = require("ui.lamsConsts")
local Audio = game.Audio
local UI = game.UI
local Slider = classlib.Class("Slider")
local HasReleaseEvents = function(slider)
  return #slider._decrementReleaseEvents > 0 or 0 < #slider._incrementReleaseEvents
end
local SetAccelerationMultiplier = function(slider)
  local multiplier = 1
  local minMult = 1
  local maxMult = 20
  local frameTime = UI.GetFrameTime()
  if slider._isAccelerating then
    local multIncrement = 45 * frameTime
    multiplier = slider._accelerationMultiplier + multIncrement
  end
  slider._accelerationMultiplier = util.Clamp(multiplier, minMult, maxMult)
  if maxMult <= slider._accelerationMultiplier then
    local countIncrement = 30 * frameTime
    slider._accerlationCount = slider._accerlationCount + countIncrement
  else
    slider._accerlationCount = 1
  end
end
local GetAccelerationDeltaMultiplier = function(slider)
  local minMult = 1
  local maxMult = slider._maxDeltaMultiplier
  local currMult = math.floor(slider._accerlationCount)
  return util.Clamp(currMult, minMult, maxMult)
end
local UpdatePadRepeatRate = function(slider)
  SetAccelerationMultiplier(slider)
  slider._state:WantPadEvents(false)
  slider._state:WantPadEvents(true)
  slider._state:PadRepeat(slider._padRepeatRate * slider._accelerationMultiplier)
end
local IncrementEventHandler = function(slider)
  if slider.keephidden then
    return
  end
  if slider:get_value() == slider:get_maxValue() then
    return
  end
  slider:PlaySound()
  UpdatePadRepeatRate(slider)
  slider:Increment()
  slider._isAccelerating = HasReleaseEvents(slider)
end
local IncrementReleaseEventHandler = function(slider)
  slider._isAccelerating = false
  UpdatePadRepeatRate(slider)
end
local DecrementEventHandler = function(slider)
  if slider.keephidden then
    return
  end
  if slider:get_value() == slider:get_minValue() then
    return
  end
  slider:PlaySound()
  UpdatePadRepeatRate(slider)
  slider:Decrement()
  slider._isAccelerating = HasReleaseEvents(slider)
end
local DecrementReleaseEventHandler = function(slider)
  slider._isAccelerating = false
  UpdatePadRepeatRate(slider)
end
local MouseClickHandler = function(slider)
  local selected = UI.GetEventSenderGameObject()
  if selected == slider._goSliderChild then
    local value = slider:get_value()
    local min = slider:get_minValue()
    local max = slider:get_maxValue()
    local delta = slider:get_delta()
    local part = UI.GetEventArgInt(3)
    if part == 0 then
      if value < max then
        value = value + delta
      end
    elseif part == 1 then
      if min < value then
        value = value - delta
      end
    else
      local range = max - min
      local position = UI.GetEventArgFloat(2)
      if position < 0.05 then
        position = 0
      end
      if 0.95 < position then
        position = 1
      end
      value = math.floor((position * range + min) / delta + 0.5) * delta
    end
    if value ~= slider:get_value() then
      slider:PlaySound()
      slider:SetValue(value, 1)
    end
  end
end
local MouseDragHandler = function(slider)
  local selected = UI.GetEventSenderGameObject()
  if selected == slider._goSliderChild then
    local value = slider:get_value()
    local part = UI.GetEventArgInt(3)
    if part == 2 or part == 3 then
      local min = slider:get_minValue()
      local max = slider:get_maxValue()
      local range = max - min
      local position = UI.GetEventArgFloat(2)
      local d = slider:get_delta()
      value = math.floor((position * range + min) / d + 0.5) * d
    end
    if value ~= slider:get_value() then
      slider:PlaySound()
      slider:SetValue(value, 1)
    end
  end
end
function Slider:init(state, args)
  assert(state ~= nil, "Attempted to create new Slider without state!")
  assert(args ~= nil, "Attempted to create new Slider without args!")
  self._state = state
  self._active = false
  self._animFrameCount = 30
  self._animBaseTime = 30
  self._name = args.Name
  self._value = 0
  self._isAccelerating = false
  self._accelerationMultiplier = 1
  self._accerlationCount = 0
  self._goSlider = args.SliderObject
  self._extraSlidersTable = args.ExtraSliders
  self._default = args.Default
  self._textFormat = util.GetValueWithDefault(args.TextFormat, "%0.2f")
  self._padRepeatRate = util.GetValueWithDefault(args.PadRepeatRate, 7)
  self._sound = util.GetValueWithDefault(args.Sound, "SND_UX_Menu_Tick_Horizontal_Small")
  self._minValue = util.GetValueWithDefault(args.MinValue, 0)
  self._maxValue = util.GetValueWithDefault(args.MaxValue, 100)
  self._delta = util.GetValueWithDefault(args.Delta, 1)
  self._maxDeltaMultiplier = util.GetValueWithDefault(args.MaxDeltaMultiplier, 3)
  self._color = args.Color
  self._material = util.GetValueWithDefault(args.Material, "statBar_Fill")
  self._layer = util.GetValueWithDefault(args.Layer, "Layer0")
  self._attribute = util.GetValueWithDefault(args.Attribute, "cst_EmissiveTint")
  self._hookTokens = {}
  self._decrementReleaseEvents = {}
  self._incrementReleaseEvents = {}
  if args.DecrementEvents ~= nil then
    self._decrementEvents = args.DecrementEvents
  else
    local dpadEvent = self._padRepeatRate == 0 and "EVT_Left_Release" or "EVT_Left_Press"
    self._decrementEvents = {
      dpadEvent,
      "EVT_LeftStick_Left"
    }
  end
  for _, eventName in ipairs(self._decrementEvents) do
    if consts.RELEASE_EVENTS_SLIDER[eventName] ~= nil then
      tablex.FastInsert(self._decrementReleaseEvents, consts.RELEASE_EVENTS_SLIDER[eventName], #self._decrementReleaseEvents + 1)
    end
  end
  if args.IncrementEvents ~= nil then
    self._incrementEvents = args.IncrementEvents
  else
    local dpadEvent = self._padRepeatRate == 0 and "EVT_Right_Release" or "EVT_Right_Press"
    self._incrementEvents = {
      dpadEvent,
      "EVT_LeftStick_Right"
    }
  end
  for _, eventName in ipairs(self._incrementEvents) do
    if consts.RELEASE_EVENTS_SLIDER[eventName] ~= nil then
      tablex.FastInsert(self._incrementReleaseEvents, consts.RELEASE_EVENTS_SLIDER[eventName], #self._incrementReleaseEvents + 1)
    end
  end
  self._Get = args.Get
  self._Set = args.Set
  self:SetGameObject(self._goSlider)
  if self._color ~= nil then
    self:SetColor(self._color)
  end
  if args.Value ~= nil then
    self:SetValue(args.Value, nil, true, true)
  else
    self:Setup()
  end
end
function Slider:Activate(show)
  if not self._active then
    util.InstallEventHooks(hooks, self, self._decrementEvents, function(slider)
      tutorialUtil.HandleEvent(slider, self._decrementEvents, DecrementEventHandler)
    end)
    util.InstallEventHooks(hooks, self, self._incrementEvents, function(slider)
      tutorialUtil.HandleEvent(slider, self._incrementEvents, IncrementEventHandler)
    end)
    util.InstallEventHooks(hooks, self, {
      "EVT_MOUSE_CLICKED"
    }, function(slider)
      tutorialUtil.HandleEvent(slider, self._incrementEvents, MouseClickHandler)
    end)
    util.InstallEventHooks(hooks, self, {
      "EVT_MOUSE_DRAGGED"
    }, function(slider)
      tutorialUtil.HandleEvent(slider, self._incrementEvents, MouseDragHandler)
    end)
    if #self._decrementReleaseEvents > 0 then
      util.InstallEventHooks(hooks, self, self._decrementReleaseEvents, function(slider)
        tutorialUtil.HandleEvent(slider, self._decrementReleaseEvents, DecrementReleaseEventHandler)
      end)
    end
    if 0 < #self._incrementReleaseEvents then
      util.InstallEventHooks(hooks, self, self._incrementReleaseEvents, function(slider)
        tutorialUtil.HandleEvent(slider, self._incrementReleaseEvents, IncrementReleaseEventHandler)
      end)
    end
    self._active = true
    if show == true then
      self:Show()
    end
  end
end
function Slider:Deactivate(hide)
  if self._active then
    self._isAccelerating = false
    util.UninstallEventHooks(hooks, self, self._decrementEvents)
    util.UninstallEventHooks(hooks, self, self._incrementEvents)
    util.UninstallEventHooks(hooks, self, {
      "EVT_MOUSE_CLICKED"
    })
    util.UninstallEventHooks(hooks, self, {
      "EVT_MOUSE_DRAGGED"
    })
    if #self._decrementReleaseEvents > 0 then
      util.UninstallEventHooks(hooks, self, self._decrementReleaseEvents)
    end
    if 0 < #self._incrementReleaseEvents then
      util.UninstallEventHooks(hooks, self, self._incrementReleaseEvents)
    end
    self._active = false
    tablex.Clear(self._hookTokens)
  end
  if hide == true then
    self:Hide()
  end
end
function Slider:Show(clearhidden)
  if clearhidden then
    self.keephidden = nil
  end
  if not self.keephidden and self._goSlider ~= nil then
    self._goSlider:Show()
    if self._goSliderChild ~= nil then
      self._goSliderChild:Show()
    end
    local goText = self:GetTextGameObject()
    if goText ~= nil then
      goText:Show()
    end
  end
end
function Slider:Hide(keephidden)
  if keephidden then
    self.keephidden = true
  end
  if self._goSlider ~= nil then
    self._goSlider:Hide()
    if self._goSliderChild ~= nil then
      self._goSliderChild:Hide()
    end
    local goText = self:GetTextGameObject()
    if goText ~= nil then
      goText:Hide()
    end
  end
end
function Slider:Setup()
  self:Update()
end
function Slider:SetColor(color)
  assert(color ~= nil, "SetColor called without a color!")
  util.SetGameObjectColor(self._goSliderChild, color, self._material, self._layer, self._attribute)
  self._color = color
end
function Slider:SetDefault()
  if self._default ~= nil then
    local default_value = self._default()
    if default_value ~= nil then
      self:SetValue(default_value)
    end
  end
end
function Slider:SetValue(newValue, rate, skipSet, forceSet)
  if self._value ~= newValue or forceSet == true then
    local clampedValue = util.Clamp(newValue, self._minValue, self._maxValue)
    local goToAnimate = self._goSliderChild
    if goToAnimate ~= nil then
      local currPercent = self:GetPercentByValue(self._value)
      local newPercent = self:GetPercentByValue(clampedValue)
      if rate == nil then
        UI.Anim(goToAnimate, consts.AS_Forward, "", 0, newPercent)
      elseif currPercent < newPercent then
        UI.Anim(goToAnimate, consts.AS_Forward, "", rate, currPercent, newPercent)
      elseif currPercent > newPercent then
        UI.Anim(goToAnimate, consts.AS_Reverse, "", rate, currPercent, newPercent)
      end
    end
    self._value = clampedValue
    if self._Set ~= nil and skipSet ~= true then
      self._Set(self._value)
    end
    self:UpdateText()
  end
end
function Slider:SetRange(minValue, maxValue)
  assert(minValue ~= nil and maxValue ~= nil, "SetRange requires both a min and max value!")
  assert(minValue <= maxValue, "SetRange called with max less than the min value!")
  self._minValue = minValue
  self._maxValue = maxValue
  self:Update()
end
function Slider:Increment(desiredTime)
  local newValue = self._value + self:get_delta(true)
  local rate = desiredTime == nil and 1 or self:GetAnimRateForTime(desiredTime)
  if newValue > self._maxValue then
    newValue = self._maxValue
  end
  self:SetValue(newValue, rate)
end
function Slider:Decrement(desiredTime)
  local newValue = self._value - self:get_delta(false)
  local rate = desiredTime == nil and 1 or self:GetAnimRateForTime(desiredTime)
  if newValue < self._minValue then
    newValue = self._minValue
  end
  self:SetValue(newValue, rate)
end
function Slider:PlaySound()
  if not util.IsStringNilOrEmpty(self._sound) and self._value <= self._maxValue and self._value >= self._minValue then
    Audio.PlaySound(self._sound)
  end
end
function Slider:GetRange()
  local range = self._maxValue - self._minValue
  assert(range ~= 0, "Slider " .. tostring(self._name) .. "'s range is zero!")
  return range
end
function Slider:GetPercentByValue(value)
  local range = self:GetRange()
  local amount = value - self._minValue
  local percent = amount / range
  return util.Clamp(percent, 0, 1)
end
function Slider:GetAnimRateForTime(desiredTime)
  local changePercent = self:get_delta() / self:GetRange()
  local framesToChange = changePercent * self._animFrameCount
  return framesToChange / (desiredTime * self._animBaseTime)
end
function Slider:get_active()
  return self._active
end
function Slider:get_color()
  return self._color
end
function Slider:get_minValue()
  return self._minValue
end
function Slider:get_maxValue()
  return self._maxValue
end
function Slider:get_value()
  return self._value
end
function Slider:get_delta(increment)
  local deltaMultiplier = GetAccelerationDeltaMultiplier(self)
  if type(self._delta) == "function" then
    return self._delta(increment) * deltaMultiplier
  else
    return self._delta * deltaMultiplier
  end
end
function Slider:Update()
  local newValue = self._value
  if self._Get ~= nil then
    newValue = self._Get()
  end
  if newValue ~= nil then
    self:SetValue(newValue, nil, true, true)
  end
end
function Slider:GetTextHandle(textObjectName)
  local goTextHandleParent, thText
  if textObjectName == nil then
    textObjectName = "text"
  end
  if textObjectName == "text" then
    goTextHandleParent = self:GetTextGameObject()
  else
    goTextHandleParent = self._goSliderChild
  end
  if goTextHandleParent ~= nil then
    thText = util.GetTextHandle(goTextHandleParent, textObjectName)
  end
  return thText
end
function Slider:SetText(text)
  local thText = self:GetTextHandle()
  if thText ~= nil then
    UI.SetText(thText, util.InternationalizeTime(text))
  end
end
function Slider:UpdateText()
  if self._value <= self._minValue and self._textFormat:find("%%s") then
    self:SetText(util.GetLAMSMsg(lamsIDs.Off))
  else
    self:SetText(string.format(self._textFormat, self._value))
  end
end
function Slider:SetGameObject(goSlider)
  if goSlider ~= nil then
    self._goSlider = goSlider
    self._goSliderChild = util.GetGOFromChildrenByName(self._goSlider, "slider")
    UI.SetIsClickable(self._goSliderChild)
    assert(self._goSliderChild ~= nil, "Slider " .. tostring(self._name) .. " not instanced properly! Missing root named slider.")
  end
end
function Slider:SetExtraSlidersTable(table)
  assert(type(table) == "table")
  if self.extraSliderTable ~= nil then
    for _, currentSlider in ipairs(self.extraSliderTable) do
      UI.Anim(currentSlider.GameObject, consts.AS_Forward, "", 0, 0)
    end
  end
  self._extraSlidersTable = table
end
function Slider:GetInstancedChildObject()
  return self._goSliderChild
end
function Slider:GetTextGameObject()
  local goText
  if self._goSlider ~= nil then
    goText = self._goSliderChild:FindSingleGOByName("text")
  end
  return goText
end
function Slider:AnimFill(...)
  if self._goSlider ~= nil then
    UI.Anim(self._goSliderChild, unpack({
      ...
    }))
  end
  if self._extraSlidersTable ~= nil then
    for _, slider in ipairs(self._extraSlidersTable) do
      local args = {
        ...
      }
      args[2] = slider.AnimName
      UI.Anim(slider.GameObject, unpack(args))
    end
  end
end
return {Slider = Slider}
