local h=require("\105\110\118\111\107\097\116\105\111\110\046\099\111\109\098\111\115\046\066\097\115\101\067\111\109\098\111")local Y=require("\105\110\118\111\107\097\116\105\111\110\046\099\111\109\098\111\115\046\067\111\109\098\111\083\101\113\117\101\110\099\101")local q=require("\112\108\046\099\108\097\115\115")local t=require("\109\111\115\101\115")local I=require("\112\108\046\116\097\098\108\101\120")local x=q(h)local m=Y.STATES local b={invoker_alacrity={"\100\117\114\097\116\105\111\110"},invoker_chaos_meteor={"\108\097\110\100\095\116\105\109\101","\098\117\114\110\095\100\117\114\097\116\105\111\110"},invoker_cold_snap={"\100\117\114\097\116\105\111\110"};invoker_deafening_blast={"\100\105\115\097\114\109\095\100\117\114\097\116\105\111\110"},invoker_emp={"\100\101\108\097\121"};invoker_ice_wall={"\100\117\114\097\116\105\111\110"};invoker_sun_strike={"\100\101\108\097\121"},invoker_tornado={"\108\105\102\116\095\100\117\114\097\116\105\111\110"};item_black_king_bar={"\100\117\114\097\116\105\111\110"},item_cyclone={"\099\121\099\108\111\110\101\095\100\117\114\097\116\105\111\110"};item_sheepstick={"\115\104\101\101\112\095\100\117\114\097\116\105\111\110"},item_shivas_guard={"\098\108\097\115\116\095\100\101\098\117\102\102\095\100\117\114\097\116\105\111\110"}}local function M(h)if b[h.name]==nil then return h:GetDuration()or 0 end local function Y(Y)return h:GetSpecialValueFor(Y)or 0 end local q=t.map(b[h.name],Y)return I.reduce("\043",q)end function x._init(t,h,q)t:super(h,q)q=q or{}t.clock=q.clock or Time t.sequence=Y(t.id,t.sequence,{clock=t.clock})t.waitQueue={}end function x.todot(h)return h.sequence:todot()end function x.CurrentStepId(h)return h.sequence.currentId end function x.CurrentStep(h)return h.sequence.current end function x.NextStepsIds(h)return h.sequence.nextIds end function x.NextSteps(h)return h.sequence.next end function x.Progress(Y,h)local q=Y.sequence:Progress(h.name)if q then Y.started=true Y.count=Y.count+1 table.insert(Y.waitQueue,Y.clock()+M(h))end return q end function x.PreFinish(h)h.preFinished=h.sequence:PreFinish()if h.preFinished then h.waitTime=math.max(unpack(h.waitQueue))h.waitDuration=h.waitTime-h.sequence:EnterTime(m.PRE_FINISH)end return h.preFinished end function x.Finish(h)h.finished=h.sequence:Finish()if h.finished then h.duration=h.sequence.duration end return h.finished end return x
--- Combo class.
-- @classmod invokation.combos.Combo
local BaseCombo = require("invokation.combos.BaseCombo")
local ComboSequence = require("invokation.combos.ComboSequence")
local class = require("pl.class")
local moses = require("moses")
local tablex = require("pl.tablex")

local M = class(BaseCombo)

local STATES = ComboSequence.STATES

local WAIT_ABILITY_SPECIALS = {
  invoker_alacrity = { "duration" },
  invoker_chaos_meteor = { "land_time", "burn_duration" },
  invoker_cold_snap = { "duration" },
  invoker_deafening_blast = { "disarm_duration" },
  invoker_emp = { "delay" },
  invoker_ice_wall = { "duration" },
  invoker_sun_strike = { "delay" },
  invoker_tornado = { "lift_duration" },
  item_black_king_bar = { "duration" },
  item_cyclone = { "cyclone_duration" },
  item_sheepstick = { "sheep_duration" },
  item_shivas_guard = { "blast_debuff_duration" },
}

local function abilityWait(ability)
  if WAIT_ABILITY_SPECIALS[ability.name] == nil then
    return ability:GetDuration() or 0
  end

  local function getSpecialValue(specialKey)
    return ability:GetSpecialValueFor(specialKey) or 0
  end

  local values = moses.map(WAIT_ABILITY_SPECIALS[ability.name], getSpecialValue)

  return tablex.reduce("+", values)
end

--- Constructor.
-- @tparam BaseCombo.Spec spec Combo data
-- @tparam[opt] table options Options
-- @tparam Logger options.logger Logger instance
-- @tparam function options.clock Clock function that returns the current time
function M:_init(spec, options)
  self:super(spec, options)

  options = options or {}

  self.clock = options.clock or Time
  self.sequence = ComboSequence(self.id, self.sequence, { clock = self.clock })
  self.waitQueue = {}
end

--- Generates a DOT formatted string from the combo's FSM.
-- @treturn string DOT string
function M:todot()
  return self.sequence:todot()
end

--- Returns the current step id.
-- @treturn ?int The current step id or `nil`
function M:CurrentStepId()
  return self.sequence.currentId
end

--- Returns the current step.
-- @treturn ?combos.ComboStep The current step or `nil`
function M:CurrentStep()
  return self.sequence.current
end

--- Returns the current next steps ids.
-- @treturn {int,...} Array of next steps ids
function M:NextStepsIds()
  return self.sequence.nextIds
end

--- Returns the current next steps.
-- @treturn {ComboStep,...} Array of next steps
function M:NextSteps()
  return self.sequence.next
end

--- Progresses the combo with the given ability if possible.
-- @tparam dota2.Ability ability Ability instance
-- @treturn bool `true` if succeeded, `false` otherwise
function M:Progress(ability)
  local progressed = self.sequence:Progress(ability.name)

  if progressed then
    self.started = true
    self.count = self.count + 1

    table.insert(self.waitQueue, self.clock() + abilityWait(ability))
  end

  return progressed
end

--- Progresses combo to pre finish if possible.
-- @treturn bool `true` if succeeded, `false` otherwise
function M:PreFinish()
  self.preFinished = self.sequence:PreFinish()

  if self.preFinished then
    self.waitTime = math.max(unpack(self.waitQueue))
    self.waitDuration = self.waitTime - self.sequence:EnterTime(STATES.PRE_FINISH)
  end

  return self.preFinished
end

--- Finishes combo if possible.
-- @treturn bool `true` if succeeded, `false` otherwise
function M:Finish()
  self.finished = self.sequence:Finish()

  if self.finished then
    self.duration = self.sequence.duration
  end

  return self.finished
end

return M
