module(..., package.seeall)

function newWindowedCounter(bucketSize, windowBuckets)
	local result = {}

	result.value = 0
	result.buckets = { 0 }
	result.actualBucket = 1
	result.lastBucketUpdate = os.time()
	result.bucketSize = bucketSize
	result.windowBuckets = windowBuckets

	result.update = function(self)
		while (os.time() - self.lastBucketUpdate) >= self.bucketSize do
			self.lastBucketUpdate = self.lastBucketUpdate + self.bucketSize
			self.actualBucket = self.actualBucket + 1
			if self.actualBucket > self.windowBuckets then self.actualBucket = 1 end
			self.value = self.value - (self.buckets[self.actualBucket] or 0)
			self.buckets[self.actualBucket] = 0;
		end
	end

	result.get = function(self)
		self:update()
		return self.value
	end

	result.increment = function(self, amount)
		amount = amount or 1
		self:update()
		self.buckets[self.actualBucket] = self.buckets[self.actualBucket] + amount
		self.value = self.value + amount
	end

	return result
end

function newNormal()
	local result = {}

	result.samples = 0
	result.oldMean = 0 
	result.newMean = 0 
	result.oldVarSum = 0 
	result.newVarSum = 0 
	result.var = 0 

	result.addSample = function(self, sample)
		self.samples = self.samples + 1
		if self.samples == 1 then
			self.oldMean = sample
			self.newMean = sample
		else 
			self.newMean = self.oldMean + (sample - self.oldMean)/self.samples
			self.newVarSum = self.newVarSum + (sample - self.oldMean)*(sample - self.newMean)
			self.oldMean = self.newMean
			self.oldVarSum = self.newVarSum
			self.var = self.newVarSum/(self.samples - 1)
		end
	end

	result.mean = function(self)
		return self.newMean
	end

	result.variance = function(self)
		return self.var
	end

	return result
end

function newWindowedMean(bucketSize, windowBuckets)
	local result = {}

	result.sum = 0
	result.samples = 0
	result.bucketsSum = { 0 }
	result.bucketsSamples = { 0 }
	result.actualBucket = 1
	result.lastBucketUpdate = os.time()
	result.bucketSize = bucketSize
	result.windowBuckets = windowBuckets

	result.update = function(self)
		local now = os.time()
		while (now - self.lastBucketUpdate) >= self.bucketSize do
			self.lastBucketUpdate = self.lastBucketUpdate + self.bucketSize
			self.actualBucket = self.actualBucket + 1
			if self.actualBucket > self.windowBuckets then self.actualBucket = 1 end
			self.sum = self.sum - (self.bucketsSum[self.actualBucket] or 0)
			self.bucketsSum[self.actualBucket] = 0;
			self.samples = self.samples - (self.bucketsSamples[self.actualBucket] or 0)
			self.bucketsSamples[self.actualBucket] = 0;
		end
	end

	result.addSample = function(self, sample)
		self:update()
		self.samples = self.samples + 1
		self.bucketsSamples[self.actualBucket] = self.bucketsSamples[self.actualBucket] + 1
		self.sum = self.sum + sample
		self.bucketsSum[self.actualBucket] = self.bucketsSum[self.actualBucket] + sample
	end

	result.get = function(self)
		self:update()
		return self.samples > 0 and (self.sum / self.samples) or 0
	end

	return result
end


function newWindowedMax (bucketSize, windowBuckets)
	local result = {}

	--
	-- sliding window
	-- given N buckets of size S, samples from oldest bucket will get overwritten once we wrap around
	--
	-- we use -math.huge as a 'no samples' sentinel, to allow negative samples to be added
	--

	result.currentBucket = 1
	result.lastBucketUpdate = os.time()
	result.bucketSize = bucketSize
	result.bucketsSamples = { }
	result.windowBuckets = windowBuckets

	result.update = function(self)
		local now = os.time()
		while (now - self.lastBucketUpdate) >= self.bucketSize do
			self.lastBucketUpdate = self.lastBucketUpdate + self.bucketSize
			self.currentBucket = self.currentBucket + 1
			if self.currentBucket > self.windowBuckets then
				self.currentBucket = 1
			end
			-- just reset the bucket value
			self.bucketsSamples[self.currentBucket] = -math.huge
		end
	end

	result.addSample = function(self, sample)
		self:update()
		self.bucketsSamples[self.currentBucket] = math.max(self.bucketsSamples[self.currentBucket] or -math.huge, sample)
	end

	result.get = function(self)
		self:update()
		local result = -math.huge
		for i = 1, self.windowBuckets do
			result = math.max(result, self.bucketsSamples[i] or -math.huge)
		end
		return (result ~= -math.huge) and result or 0
	end

	return result
end
