local classlib = require("core.class")
local tablex = require("core.tablex")
local consts = require("ui.consts")
local hooks = require("ui.hooks")
local util = require("ui.util")
local Audio = game.Audio
local SetText = game.UI.SetText
local UI = game.UI
local Meter = classlib.Class("Meter")
local MeterChangedEventHandler = function(meter)
  local markerID = UI.GetEventArgInt(1)
  local idHash = UI.GetEventArgStringHash(2)
  if meter:ShouldHandleMeterChangedEvent(markerID, idHash) and meter._OnMeterChanged ~= nil then
    meter:_OnMeterChanged()
  end
end
function Meter:init(state, args)
  assert(state ~= nil, "Attempted to create new Meter without state!")
  assert(args ~= nil, "Attempted to create new Meter without args!")
  self._state = state
  self._active = false
  self._name = args.Name
  self._percent = 0
  self._goMeter = nil
  self._goMeterChild = nil
  self._goFill = nil
  self._goFillChild = nil
  self._meterInstanceChildName = args.MeterInstanceChildName
  self._markerID = args.MarkerID
  self._idHash = args.IDHash
  self._color = args.Color
  self._fullSound = args.FullSound
  self._minValue = util.GetValueWithDefault(args.MinValue, 0)
  self._maxValue = util.GetValueWithDefault(args.MaxValue, 100)
  self._fillObjectName = args.FillObjectName
  self._fillInstanceChildName = util.GetValueWithDefault(args.FillInstanceChildName, "meter")
  self._fillAnimName = util.GetValueWithDefault(args.FillAnimName, "")
  self._flourishAnimName = util.GetValueWithDefault(args.FlourishAnimName, "")
  self._textHandle = args.TextHandle
  self._textIndex = util.GetValueWithDefault(args.TextIndex, 1)
  self._textFormat = util.GetValueWithDefault(args.TextFormat, "%0.2f")
  self._textMaterial = args.TextMaterial
  self._textLayer = util.GetValueWithDefault(args.TextLayer, "LayerX")
  self._textAttribute = util.GetValueWithDefault(args.TextAttribute, "cst_EmissiveTint")
  self._fillAnimStyleForward = consts.AS_Forward_NoReset
  self._fillAnimStyleReverse = consts.AS_Reverse_NoReset
  self._flourishAnimStyleForward = consts.AS_Forward
  self._flourishAnimStyleReverse = consts.AS_Reverse
  self._hookTokens = {}
  self._animEndCallbackTable = {}
  self._OnMeterChanged = args.OnMeterChanged
  self._OnFillAnimEnd = args.OnFillAnimEnd
  self._OnFlourishAnimEnd = args.OnFlourishAnimEnd
  self._OnGain = args.OnGain
  self._OnLose = args.OnLose
  self._SetColor = args.SetColor
  if self._OnFillAnimEnd ~= nil then
    local fillAnimNameHashString = tostring(engine.Hash(self._fillAnimName))
    self._animEndCallbackTable[fillAnimNameHashString] = self._OnFillAnimEnd
    self._fillAnimStyleForward = bit32.bor(self._fillAnimStyleForward, consts.AS_EndEvent)
    self._fillAnimStyleReverse = bit32.bor(self._fillAnimStyleReverse, consts.AS_EndEvent)
  end
  if self._OnFlourishAnimEnd ~= nil then
    local flourishAnimNameHashString = tostring(engine.Hash(self._flourishAnimName))
    self._animEndCallbackTable[flourishAnimNameHashString] = self._OnFlourishAnimEnd
    self._flourishAnimStyleForward = bit32.bor(self._flourishAnimStyleForward, consts.AS_EndEvent)
    self._flourishAnimStyleReverse = bit32.bor(self._flourishAnimStyleReverse, consts.AS_EndEvent)
  end
  self:SetGameObject(args.MeterObject)
  self:SetColor(self._color)
  if args.Percent ~= nil then
    self:SetPercent(args.Percent)
  else
    self:SetPercent(0)
  end
end
function Meter:Activate(show)
  if not self._active then
    if self._OnMeterChanged ~= nil then
      util.InstallEventHooks(hooks, self, {
        "EVT_METER_CHANGED"
      }, MeterChangedEventHandler)
    end
    if self:ShouldHandleAnimEndEvent() then
      util.InstallEventHooks(hooks, self, {
        "EVT_Anim_End"
      }, self.AnimEndEventHandler)
    end
    self._active = true
    if show == true then
      self:Show()
    end
  end
end
function Meter:Deactivate(hide)
  if self._active then
    self._active = false
    if self._OnMeterChanged ~= nil then
      util.UninstallEventHooks(hooks, self, {
        "EVT_METER_CHANGED"
      })
    end
    if self:ShouldHandleAnimEndEvent() then
      util.UninstallEventHooks(hooks, self, {
        "EVT_Anim_End"
      })
    end
    tablex.Clear(self._hookTokens)
  end
  if hide == true then
    self:Hide()
  end
end
function Meter:Show()
  self._goMeter:Show()
  if self._goMeterChild ~= nil then
    self._goMeterChild:Show()
  end
  if self._goFill ~= nil then
    self._goFill:Show()
    if self._goFillChild ~= nil then
      self._goFillChild:Show()
    end
  end
end
function Meter:Hide()
  self._goMeter:Hide()
end
function Meter:ShouldHandleMeterChangedEvent(markerID, idHash)
  local shouldHandle = false
  if self._markerID ~= nil and self._idHash ~= nil then
    shouldHandle = self._markerID == markerID and self._idHash == idHash
  end
  return shouldHandle
end
function Meter:PlayFullSound()
  if not util.IsStringNilOrEmpty(self._fullSound) and self:get_percent() >= 0.99 then
    Audio.PlaySound(self._fullSound)
  end
end
function Meter:SetIDHash(idHash)
  self._idHash = idHash
end
function Meter:SetColor(color)
  self._color = color
  if self._SetColor ~= nil then
    self._SetColor(self, color)
  end
end
function Meter:SetMinValue(newMinValue)
  assert(newMinValue ~= nil, "Meter " .. self._name .. " attemped to set nil min value")
  self._minValue = newMinValue
  self:SetPercentByValue(self:GetValue())
end
function Meter:SetMaxValue(newMaxValue)
  assert(newMaxValue ~= nil, "Meter " .. self._name .. " attemped to set nil max value")
  self._maxValue = newMaxValue
  self:SetPercentByValue(self:GetValue())
end
function Meter:SetRange(newMinValue, newMaxValue, newValue)
  assert(newMinValue < newMaxValue, "SetRange cannot set a negative range!")
  self._minValue = newMinValue
  self._maxValue = newMaxValue
  if newValue == nil then
    newValue = self:GetValue()
  end
  self:SetPercentByValue(newValue)
end
function Meter:SetPercent(newPercent, rate)
  local itemToAnimate = self:GetInstancedFillObject()
  if newPercent < 0 or newPercent ~= newPercent then
    newPercent = 0
  end
  if 1 < newPercent then
    newPercent = 1
  end
  if itemToAnimate ~= nil then
    if rate == nil then
      UI.Anim(itemToAnimate, consts.AS_Forward, self._fillAnimName, 0, newPercent)
    elseif newPercent > self._percent then
      UI.Anim(itemToAnimate, self._fillAnimStyleForward, self._fillAnimName, rate, self._percent, newPercent)
    elseif newPercent < self._percent then
      UI.Anim(itemToAnimate, self._fillAnimStyleReverse, self._fillAnimName, rate, self._percent, newPercent)
    end
  end
  if self._OnGain ~= nil and newPercent > self._percent then
    self._OnGain(self, self._percent, newPercent)
  elseif self._OnLose ~= nil and newPercent < self._percent then
    self._OnLose(self, self._percent, newPercent)
  end
  self._percent = newPercent
  self:UpdateText()
end
function Meter:SetPercentByValue(value, rate)
  local percent = self:GetPercentByValue(value)
  self:SetPercent(percent, rate)
end
function Meter:GetRange()
  local range = self._maxValue - self._minValue
  assert(range ~= 0, "Meter " .. tostring(self._name) .. "'s range is zero!")
  return range
end
function Meter:GetValue()
  return self._percent * self:GetRange() + self._minValue
end
function Meter:GetPercentByValue(value)
  local range = self:GetRange()
  local amount = value - self._minValue
  local percent = amount / range
  return util.Clamp(percent, 0, 1)
end
function Meter:ShouldHandleAnimEndEvent()
  return self._OnFillAnimEnd ~= nil or self._OnFlourishAnimEnd ~= nil
end
function Meter:get_active()
  return self._active
end
function Meter:get_fillObjectName()
  return self._fillObjectName
end
function Meter:get_color()
  return self._color
end
function Meter:get_percent()
  return self._percent
end
function Meter:get_maxValue()
  return self._maxValue
end
function Meter:get_gameObject()
  return self._goMeter
end
function Meter:get_flourishAnimName()
  return self._flourishAnimName
end
function Meter.AnimEndEventHandler(meter)
  if meter:ShouldHandleAnimEndEvent() then
    local senderObjectNameHash = UI.GetEventArgStringHash(1)
    local animNameHash = UI.GetEventArgStringHash(2)
    local percentComplete = UI.GetEventArgFloat(3) / 100
    local senderObject = UI.GetEventSenderGameObject()
    local fillObject = meter:GetInstancedFillObject()
    local onAnimEndCallback = meter._animEndCallbackTable[tostring(animNameHash)]
    if fillObject == senderObject and onAnimEndCallback ~= nil then
      onAnimEndCallback(meter, senderObject, percentComplete)
    end
  end
end
function Meter:ResetText()
  if self._textHandle ~= nil then
    SetText(self._textHandle, "")
  end
end
function Meter:UpdateText()
  self:ResetText()
  if self._textHandle ~= nil then
    SetText(self._textHandle, string.format(self._textFormat, self:GetValue()))
  end
end
function Meter:SetGameObject(goMeter)
  assert(goMeter ~= nil, "Meter:SetGameObject called with no game object!")
  self._goMeter = goMeter
  if not util.IsStringNilOrEmpty(self._meterInstanceChildName) then
    self._goMeterChild = self._goMeter:FindSingleGOByName(self._meterInstanceChildName)
  end
  if not util.IsStringNilOrEmpty(self._fillObjectName) then
    self._goFill = self._goMeter:FindSingleGOByName(self._fillObjectName)
    if not util.IsStringNilOrEmpty(self._fillInstanceChildName) then
      self._goFillChild = self._goFill:FindSingleGOByName(self._fillInstanceChildName)
    end
  end
end
function Meter:GetInstancedChildObject()
  return self._goMeterChild
end
function Meter:GetInstancedFillObject()
  local obj
  if self._goMeterChild ~= nil then
    obj = self._goMeterChild
    if self._goFill ~= nil then
      obj = self._goFill
      if self._goFillChild ~= nil then
        obj = self._goFillChild
      end
    end
  end
  return obj
end
return {Meter = Meter}
