local registeredSpellCDReferences = { }

function ReadFullSpellInfo(info)
  local producer = info.producer
  return info.name, info.texture, info.objectId
  -- name, start time, end time, texture, stack size, stack limit
end

function GetSpellCDConfiguration(spellName)
  local results = { }
  for k, v in auraData do
	if v.spellCD and
	   common.CompareWStringEx(v.name, spellName) == 0 then
	  table.insert(results, v)
	end
  end
  return results
end

function RegisterSpellCDReferences(references)
  for reference, _ in references do
	local registeredReferencesForSpellCDId = registeredSpellCDReferences[reference.actionId]
	if not registeredReferencesForSpellCDId then
	  registeredReferencesForSpellCDId = { }
	  registeredSpellCDReferences[reference.actionId] = registeredReferencesForSpellCDId
	end
	registeredReferencesForSpellCDId[reference] = true
	reference.config.references[reference] = true
  end
end

function UnregisterSpellCDReference(reference)
  reference.config.references[reference] = nil
  local references = registeredSpellCDReferences[reference.actionId]  
  if references then
	references[reference] = nil
	for k, v in references do
	  return
	end
	registeredSpellCDReferences[reference.actionId] = nil
  end 
end

function CreateSpellCDReferences(spellId, cooldownInfo, spellInfo)
  spellId = spellId or cooldownInfo.id
  spellInfo = spellInfo or (spellId and avatar.GetSpellInfo(spellId))
  if not spellInfo then
	return nil
  end
  local spellCDStart, spellCDFinished = cachedTimestamp - cooldownInfo.duration + cooldownInfo.remaining, cooldownInfo.remaining + cachedTimestamp
  local spellName, texture, actionId = ReadFullSpellInfo(spellInfo)
  local results = { }
  local resultsAvailable = false
  for k, config in GetSpellCDConfiguration(spellName) do
	  if config == nil then
		return nil
	  end
	  config.references = config.references or {}
	  local result = { }  
	  result.spellName = spellName
	  result.spellId = spellId
	  result.actionId = actionId
	  result.config = config
	  result.spellCDStart = spellCDStart
	  result.spellCDFinished = spellCDFinished
	  result.duration = cooldownInfo.duration
	  result.texture = texture
	  result.showDuration = true
	  result.isEnabled = true
	  result.CanDelete = function(self)
	    local config = self.config
		return (config.timerremaining and config.timermin and self.timerremaining < config.timerminvalue) or
			   (config.timerelapsed and config.timermax and self.timerelapsed > config.timermaxvalue) or
			   (not config.timermin and not config.timermax and self.timerremaining < 0)
	  end  
	  result.Unregister = UnregisterSpellCDReference
	  results[result] = true
	  resultsAvailable = true
  end
  RegisterSpellCDReferences(results)
  for reference, _ in results do
	UpdateSpellCDReference(nil, nil, reference, spellInfo)
  end
  return resultsAvailable and results or nil
end

function RefreshSpellCDReference(reference, spellId, cooldownInfo, spellInfo)
  spellInfo = spellInfo or (spellId and avatar.GetSpellInfo(spellId))  
  if not reference then
    local actionId = spellInfo and spellInfo.objectId or nil
	local references = (actionId and registeredSpellCDReferences[actionId]) or nil
	if references then
	  for creference, _ in references do
		RefreshSpellCDReference(creference, creference.spellId, cooldownInfo, spellInfo)
	  end
	  return true
	end
	local result = CreateSpellCDReferences(spellId, cooldownInfo, spellInfo)
	if result then
	  for creference, _ in result do
		RefreshSpellCDReference(creference, creference.spellId, cooldownInfo, spellInfo)
	  end
	  return true
	end
	return false
  else
	reference.spellCDFinished = cooldownInfo.remaining + cachedTimestamp
	reference.duration = cooldownInfo.duration
	return true
  end
  return false
end

function UpdateSpellCDReference(spellId, cooldownInfo, reference, spellInfo)
  if not reference then
    spellInfo = spellInfo or (spellId and avatar.GetSpellInfo(spellId))
    local actionId = spellInfo and spellInfo.objectId or nil
	local references = (actionId and registeredSpellCDReferences[actionId]) or nil
	if references then
	  for creference, _ in references do
		UpdateSpellCDReference(creference.spellId, nil, creference)
	  end
	  return
	elseif cooldownInfo then
	  CreateSpellCDReferences(spellId, cooldownInfo, spellInfo)
	end
  else
	local config = reference.config
	reference.timerremaining = reference.spellCDFinished - cachedTimestamp
	reference.timerelapsed = cachedTimestamp - reference.spellCDStart	
	reference.isActive = ConfigTimeCheck(reference)
	reference.isVisible = ConfigExtendedConditionsCheck(config)
  end
end

function UpdateSpellCDReferences()
  for k, v in registeredSpellCDReferences do
	for reference, _ in v do
	  UpdateSpellCDReference(nil, nil, reference)
	end
  end
end

function RemoveSpellCDReference(spellId, cooldownInfo, reference, spellInfo)
  if not reference then
    spellInfo = spellInfo or (spellId and avatar.GetSpellInfo(spellId))
    local actionId = (spellInfo and spellInfo.objectId) or nil
	local references = (actionId and registeredSpellCDReferences[actionId]) or nil
	if references then
	  for creference, _ in references do
		RemoveSpellCDReference(creference.spellId, nil, creference)
	  end
	end
  else
    UnregisterSpellCDReference(reference)
  end
end

function CollectSpellCDReferences()
  for k, v in registeredSpellCDReferences do
	for reference, _ in v do
	  if reference:CanDelete() then
		UnregisterSpellCDReference(reference)
	  end
	end
  end
end
