Quantcast
local INF = 2147483647;

function MaxDps:InitTTD(maxSamples, interval)
	self.ttd = {};
	self.ttd.Windows = maxSamples or 8; -- Max number of samples

	-- Code variables
	self.ttd.GUID = nil; -- Remember GUID of mob you are tracking
	self.ttd.MaxValue = 0; -- Remember max HP for relative shift
	self.ttd.Last = GetTime(); -- Remember last update
	self.ttd.Start = nil; -- First data collection time for relative shift
	self.ttd.Index = 0; -- Current ring buffer index
	self.ttd.Times = {}; -- Ring buffer data - data_x
	self.ttd.Values = {}; -- Ring buffer data - data_y
	self.ttd.Samples = 0; -- Number of collected (active) samples
	self.ttd.Estimate = nil; -- Estimated end time (not relative)
	self.ttd.TimeToDie = INF; -- Estimated end time relative

	self.ttd.Timer = self:ScheduleRepeatingTimer('TimeToDie', interval or 1.5);
end

function MaxDps:DisableTTD()
	if self.ttd.Timer then
		self:CancelTimer(self.ttd.Timer);
	end
end

function MaxDps:TimeToDie(target)
	target = target or 'target';

	-- Query current time (throttle updating over time)
	local now = GetTime();

	-- Current data
	local data = UnitHealth(target);

	-- Reset data?
	if data == UnitHealthMax(target) or not self.ttd.GUID or self.ttd.GUID ~= UnitGUID(target) then
		self.ttd.GUID = nil
		self.ttd.Start = nil
		self.ttd.Estimate = nil
	end

	-- No start time?
	if not self.ttd.Start or not self.ttd.GUID then
		self.ttd.Start = now;
		self.ttd.Index = 0;
		self.ttd.Samples = 0;
		self.ttd.MaxValue = UnitHealthMax(target) / 2;
		self.ttd.GUID = UnitGUID(target);
	end

	-- Remember current time
	self.ttd.Last = now;

	-- Save new data (Use relative values to prevent 'overflow')
	self.ttd.Values[self.ttd.Index] = data - self.ttd.MaxValue;
	self.ttd.Times[self.ttd.Index] = now - self.ttd.Start;

	-- Next index
	self.ttd.Index = self.ttd.Index + 1;

	-- Update number of active samples
	if self.ttd.Index > self.ttd.Samples then
		self.ttd.Samples = self.ttd.Index;
	end

	-- Using table as ring buffer
	if self.ttd.Index >= self.ttd.Windows then
		self.ttd.Index = 0;
	end

	-- Min number of samples
	if self.ttd.Samples >= 2 then
		-- Estimation variables
		local SS_xy, SS_xx, x_M, y_M = 0, 0, 0, 0;

		-- Calc pre-solution values
		for index = 0, self.ttd.Samples - 1 do
			-- Calc mean value
			x_M = x_M + self.ttd.Times[index] / self.ttd.Samples;
			y_M = y_M + self.ttd.Values[index] / self.ttd.Samples;

			-- Calc sum of squares
			SS_xx = SS_xx + self.ttd.Times[index] * self.ttd.Times[index];
			SS_xy = SS_xy + self.ttd.Times[index] * self.ttd.Values[index];
		end

		-- Few last additions to mean value / sum of squares
		SS_xx = SS_xx - self.ttd.Samples * x_M * x_M;
		SS_xy = SS_xy - self.ttd.Samples * x_M * y_M;

		-- Calc a_0, a_1 of linear interpolation (data_y = a_1 * data_x + a_0)
		local a_1 = SS_xy / SS_xx;
		local a_0 = (y_M - a_1 * x_M) + self.ttd.MaxValue;

		-- Find zero-point (Switch back to absolute values)
		local x = -(a_0 / a_1);

		-- Valid/Usable solution
		if a_1 and a_1 < 1 and a_0 and a_0 > 0 and x and x > 0 then
			self.ttd.Estimate = x + self.ttd.Start;
			-- Fallback
		else
			self.ttd.Estimate = nil;
		end

		-- Not enough data
	else
		self.ttd.Estimate = nil;
	end

	-- No/False information
	if not self.ttd.Estimate then
		self.ttd.TimeToDie = INF;
		-- Already over
	elseif now > self.ttd.Estimate then
		self.ttd.TimeToDie = 0;
	else
		self.ttd.TimeToDie = self.ttd.Estimate - now;
	end

	return self.ttd.TimeToDie;
end