local _, Addon = ...

----------------------------------------
Addon.SchedulerLib =
----------------------------------------
{
	Version = 1,
	
	EventFrame = CreateFrame("Frame", nil, UIParent),
	
	HasTasks = false,
	NeedSorted = false,
	Tasks = {},
	
	IteratorIndex = nil,
	IteratorCount = nil,
}

Addon.SchedulerLib.EventFrame.SchedulerLib = Addon.SchedulerLib
Addon.SchedulerLib.EventFrame:SetScript("OnUpdate", function (self, elapsed) self.SchedulerLib:OnUpdate(elapsed) end)

function Addon.SchedulerLib:ScheduleTask(pDelay, pFunction, pParam, pTaskName)
	if type(pFunction) ~= "function" then
		error("Attempt to schedule an invalid function (passed "..type(pFunction).." instead)")
	end
	
	local vTask =
	{
		Time = GetTime() + pDelay,
		Function = pFunction,
		Param = pParam,
		Name = pTaskName,
	}
	
	self:InsertTask(vTask)
end

function Addon.SchedulerLib:RescheduleTask(pDelay, pFunction, pParam, pTaskName)
	self:UnscheduleTask(pFunction, pParam)
	self:ScheduleTask(pDelay, pFunction, pParam, pTaskName)
end

function Addon.SchedulerLib:ScheduleUniqueTask(pDelay, pFunction, pParam, pTaskName)
	if self:FindTask(pFunction, pParam) then
		return
	end
	
	self:ScheduleTask(pDelay, pFunction, pParam, pTaskName)
end

function Addon.SchedulerLib:ScheduleRepeatingTask(pInterval, pFunction, pParam, pInitialDelay, pTaskName)
	local vTask =
	{
		Interval = pInterval,
		Function = pFunction,
		Param = pParam,
		Name = pTaskName,
	}
	
	if pInitialDelay then
		vTask.Time = GetTime() + pInitialDelay
	else
		vTask.Time = GetTime() + pInterval
	end
	
	self:InsertTask(vTask)
end

function Addon.SchedulerLib:ScheduleUniqueRepeatingTask(pInterval, pFunction, pParam, pInitialDelay, pTaskName)
	if self:FindTask(pFunction, pParam) then
		return
	end
	
	self:ScheduleRepeatingTask(pInterval, pFunction, pParam, pInitialDelay, pTaskName)
end

function Addon.SchedulerLib:SetTaskInterval(pInterval, pFunction, pParam)
	local vTask, vIndex = self:FindTask(pFunction, pParam)
	
	if not vTask then
		return
	end
	
	if vTask.Interval == pInterval then
		return
	end
	
	if vTask.Interval then
		vTask.Time = vTask.Time - vTask.Interval
	end
	
	vTask.Interval = pInterval

	if vTask.Interval then
		vTask.Time = vTask.Time + vTask.Interval
	end
	
	self.NeedSorted = true
end

function Addon.SchedulerLib:SetTaskDelay(pDelay, pFunction, pParam)
	local vTask, vIndex = self:FindTask(pFunction, pParam)
	
	if not vTask then
		return
	end
	
	vTask.Time = GetTime() + pDelay
	
	self.NeedSorted = true
end

function Addon.SchedulerLib:InsertTask(pTask)
	if not pTask then
		error("Inserting a nil task")
	end
	
	table.insert(self.Tasks, pTask)
	
	self.NeedSorted = true
	
	if not self.HasTasks then
		self.EventFrame:Show()
		self.HasTasks = true
	end
end

function Addon.SchedulerLib:UnscheduleAllTasks(pFunction, pParam)
	while self:UnscheduleTask(pFunction, pParam) do
	end
end

function Addon.SchedulerLib:UnscheduleTask(pFunction, pParam)
	local vTask, vIndex = self:FindTask(pFunction, pParam)
	
	if not vTask then
		return false
	end
	
	table.remove(self.Tasks, vIndex)
	
	if self.IteratorIndex
	and vIndex < self.IteratorIndex then
		self.IteratorIndex = self.IteratorIndex - 1
	end
	
	if #self.Tasks == 0 then
		self.HasTasks = false
		self.EventFrame:Hide()
	end
	
	return true
end

function Addon.SchedulerLib:FindTask(pFunction, pParam)
	for vIndex, vTask in ipairs(self.Tasks) do
		if (pFunction == nil or vTask.Function == pFunction)
		and (pParam == nil or vTask.Param == pParam) then
			return vTask, vIndex
		end
	end -- for
end

function Addon.SchedulerLib:OnUpdate(pElapsed)
	-- Prevent from re-entering
	
	if self.OnUpdateRunning then
		-- self:TestMessage("OnUpdate already running")
		return
	end
	
	self.OnUpdateRunning = true
	
	for vIndex, vTask in ipairs(self.Tasks) do
		vTask.DidRun = false
	end
	
	self:OnUpdate2(pElapsed)
	
	self.OnUpdateRunning = false
end

function Addon.SchedulerLib:OnUpdate2(pElapsed)
	local vTime = GetTime()
	
	if self.PerfMonitor then
		self.PerfMonitor:FunctionEnter(vTime)
	end
	
	--
	
	if self.NeedSorted then
		self.NeedSorted = false
		table.sort(self.Tasks, self.CompareTasks)
	end
	
	self.IteratorIndex = 1
	
	while self.IteratorIndex <= #self.Tasks do
		local vTask = self.Tasks[self.IteratorIndex]
		
		if not vTask then
			Addon:ErrorMessage("nil task at index "..self.IteratorIndex)
			table.remove(self.Tasks, self.IteratorIndex)
		elseif vTask.DidRun then
			-- Ignore it
			
			self.IteratorIndex = self.IteratorIndex + 1
		else
			if vTask.Time > vTime then
				break
			end
			
			-- Re-schedule or remove one-shot tasks before calling their function
			-- in case the function decides it wants to remove the task too
			
			if vTask.Interval == nil then
				table.remove(self.Tasks, self.IteratorIndex)
			
			-- Repeat tasks just need a new time calculated
			
			else
				vTask.Time = vTime + vTask.Interval
				
				if vTask.Interval > 0 then
					self.NeedSorted = true
				end
				
				self.IteratorIndex = self.IteratorIndex + 1
			end
			
			-- Call the function
			
			-- Addon:DebugMessage("Calling task %s", vTask.Name or "anonymous")
			
			if not vTask.Disabled then
				local vResult, vMessage
				
				vTask.DidRun = true
				
				if vTask.Param ~= nil then
					vTask.Function(vTask.Param, vTime)
				else
					vTask.Function(vTime)
				end
			end
		end
	end -- while
	
	self.IteratorIndex = nil
	
	if #self.Tasks == 0 then
		self.HasTasks = false
		self.EventFrame:Hide()
	end
	
	if self.PerfMonitor then
		local vEndTime = GetTime()
		
		self.PerfMonitor:FunctionExit(vEndTime)
		
		if not self.LastDumpTime
		or vEndTime - self.LastDumpTime > 2 then
			self.LastDumpTime = vEndTime
			
			self.PerfMonitor:DumpValue()
		end
	end
end

function Addon.SchedulerLib.CompareTasks(pTask1, pTask2)
	return pTask1.Time < pTask2.Time
end
