local registeredBuffReferences = { }
Global("currentlyActiveBuffs", { })
Global("debug", false)

function ReadFullBuffInfo(info)
  local producer = info.producer
  return info.name, cachedTimestamp, info.remainingMs + cachedTimestamp, info.durationMs, info.texture, info.isPositive,
         producer and producer.casterId or nil, info.stackCount, info.isNeedVisualizeDuration and (info.durationMs ~= 0), info.stackLimit
  -- name, start time, end time, texture, stack size, stack limit
end

function ReadMinimalBuffInfo(info)
  return info.remainingMs + cachedTimestamp, info.durationMs, info.stackCount, info.stackLimit
  -- end time, stack size, stack limit
end

function GetBuffConfiguration(buffName, isPositive)
  local result = { }
  for k, v in auraData do
    if ((v.buff and isPositive) or (v.debuff and not isPositive)) and
       common.CompareWStringEx(v.name, buffName) == 0 then
      table.insert(result, v)
    end
  end
  return result
end

function RegisterBuffReferences(references)
  for reference, _ in references do
    local registeredReferencesForBuffId = registeredBuffReferences[reference.buffId]
    if not registeredReferencesForBuffId then
      registeredReferencesForBuffId = { }
      registeredBuffReferences[reference.buffId] = registeredReferencesForBuffId
    end
    registeredReferencesForBuffId[reference] = true
    reference.config.references[reference] = true
  end
end

function UnregisterBuffReference(reference)
  reference.config.references[reference] = nil
  local references = registeredBuffReferences[reference.buffId]
  if references then
    references[reference] = nil
    for k, v in references do
      return
    end
	registeredBuffReferences[reference.buffId] = nil
  end  
end

function CreateBuffReferences(unitId, buffId, buffInfo)
  if not object.IsExist(unitId) then
    return nil
  end
  buffId = buffId or (buffInfo and buffInfo.buffId)
  buffInfo = buffInfo or (buffId and object.GetBuffInfo(buffId))
  if not buffInfo then
    return nil
  end
  buffId = buffInfo.id
  local buffName, buffStart, buffFinished, buffDuration, buffTexture, isPositive, casterId, stackSize, showDuration = ReadFullBuffInfo(buffInfo)
  UpdateValueByWString(knownBuffs, buffName, buffTexture)  
  local results = { }
  local resultsAvailable = false
  for k, config in GetBuffConfiguration(buffName, isPositive) do
    if config == nil or not (not config.castedbyme or IsIdPlayerOrPlayerPet(casterId)) then   
      return nil
    end
    --config.defaultTexture = buffTexture
    config.references = config.references or {}
    local result = { }  
    result.buffName = buffName
    result.buffId = buffId
    result.unitId = unitId
    result.casterId = casterId
    result.config = config
    result.buffStart = buffStart
	result.duration = buffDuration
    result.buffFinished = buffFinished
    result.texture = buffTexture
    result.stackSize = stackSize
    result.showDuration = showDuration
    result.CanDelete = function(self)
        local config = self.config
		if config.show then
			if config.timerremaining and config.timermin then
				if self.timerremaining > config.timerminvalue and self.showDuration then	    
					return false
				end
			end
			if config.timerelapsed and config.timermax then
				if self.timerelapsed < config.timermaxvalue and self.showDuration then
					return false
				end
			end
			if not config.timermin and not config.timermax then
				if (self.showDuration and self.timerremaining > 0 or not self.showDuration) and IsBuffCurrentlyActive(self.unitId, buffId) then
					return false
				end 
			end
		else
			-- delete the reference with the buff, that let the timer of buffs visible when hide is activated 
			-- and the range is not matched
			--self:LogData("CanDelete")
			local result = IsBuffCurrentlyActive(self.unitId, buffId)			
			if result then 
			  return false
			end
			
		end
        return true   
    end		
	result.Unregister = UnregisterBuffReference
	result.LogData = function(self, ...)
	  local time = mission.GetLocalTimeHMS()
	  local ar = { }
	  local t1 = AsArray(time.h, ":", time.m,":",time.s,".",time.ms," - ")
	  local t2 = AsArray(" ", self.buffName, " on ", object.GetName(self.unitId), ": ", self.buffStart, " - ", self.buffFinished)
	  local t = ConcatTables(t1, arg, t2)
	  LogInfo(unpack(t))
	end
    results[result] = true
    resultsAvailable = true
  end
  RegisterBuffReferences(results)
  for reference, _ in results do
    UpdateBuffReferences(nil, nil, reference)
  end
  return resultsAvailable and results or nil
end

function RefreshBuffReference(reference, buffId, unitId)
  if not reference then
    local references = (buffId and registeredBuffReferences[buffId]) or nil
    if references then
      for creference, _ in references do
        RefreshBuffReference(creference, creference.buffId, creference.unitId)
      end
      return true
    end
    local result = CreateBuffReferences(unitId, buffId)
    if result then
      for creference, _ in result do
        RefreshBuffReference(creference, creference.buffId, creference.unitId)
      end
      return true
    end
    return false
  else
    local buffInfo = object.GetBuffInfo(buffId)
	if buffInfo then
      local buffFinished, buffDuration, stackSize = ReadMinimalBuffInfo(buffInfo)
      reference.buffFinished = buffFinished
	  reference.duration = buffDuration
      reference.stackSize = stackSize	
      return true
	end
  end
  return false
end

function UpdateBuffReference(unitId, buffId, reference)
  if not reference then
    local references = (buffId and registeredBuffReferences[buffId]) or nil
    if references then
      for creference, _ in references do
        UpdateBuffReference(creference.unitId, buffId, creference)
      end
      return
    end
    CreateBuffReferences(unitId, buffId)
  else
    local config = reference.config	
    reference.timerremaining = reference.buffFinished - cachedTimestamp
    reference.timerelapsed = cachedTimestamp - reference.buffStart	
	reference.isEnabled = ConfigUnitCheck(config, reference.unitId, reference.casterId)
    reference.isActive = ConfigStacksizeAndTimeCheck(reference)
    reference.isVisible = ConfigExtendedConditionsCheck(config, reference.unitId)
	--reference:LogData("IsEnabled: ", reference.isEnabled, ", IsActive: ", reference.isActive, ", IsVisible: ", reference.isVisible)
  end
end

function UpdateBuffReferences()
  for k, v in registeredBuffReferences do
    for reference, _ in v do
      UpdateBuffReference(reference.unitId, nil, reference)
    end
  end
end

function CollectBuffReferences()
  for k, v in registeredBuffReferences do
    for reference, _ in v do
      if reference:CanDelete() then
        UnregisterBuffReference(reference)
      end
    end
  end
end

function UpdateCurrentlyActive()
  local active = { }
  for k, v in registeredBuffReferences do
    for reference, _ in v do
        local unitId = reference.unitId
        if object.IsExist(unitId) and not active[unitId] then
          local buffs = { }
          for kk, vv in object.GetBuffs(unitId) do
            buffs[vv] = true
          end
          active[unitId] = buffs
        end	
    end
  end
  currentlyActiveBuffs = active
end

function IsBuffCurrentlyActive(unitId, buffId)
  if not object.IsExist(unitId) then
    currentlyActiveBuffs[unitId] = nil
    return false
  end
  local buffs = currentlyActiveBuffs[unitId]    
  return buffs and buffs[buffId]
end