--[[----------------------------------------------------------------------------

WFDiagnosticLogRingBuffer.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2013 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remains the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and are protected by all applicable
intellectual property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

----------------------------------------------------------------------------]]--

	-- RxLua
local Rx = require 'Rx'

	-- WF.core.utils
local WFArguments = require 'WFArguments'


--============================================================================--

-- @mark utilities

--------------------------------------------------------------------------------

local function historyNodes( ringBuffer, fieldIndex )

	local t = {}
	local idx = 1

	for _, idxs in ipairs { { ringBuffer.head + 1, #ringBuffer }, { 1, ringBuffer.head } } do

		for i = idxs[ 1 ], idxs[ 2 ] do

			local val = ringBuffer[ i ]

			if type( val ) == 'function' then
				ringBuffer[ i ] = { val() }
				val = ringBuffer[ i ]
			end

			val = val[ fieldIndex ]

			if val then
				t[ idx ] = val
				idx = idx + 1
			end

		end

	end

	return t

end


--============================================================================--

-- @mark WFDiagnosticLogRingBuffer class

--[[!
	\class WFDiagnosticLogRingBuffer

	\brief A diagnostic log tool for recording data values that change over time.
	It provides two visualization options: A box-and-whisker analysis to
	show how the values are distributed across different percentiles.  And a spark-line
	visualization to show the value's trend over time.

	Each instance of the log can record multiple channels, and are displayed
	together with shared X-axis visualization (same min and max value for
	the box-and-whisker and the same time duration for spark line).

	\param (table, optional) options a table
	<ul>
		<li>'size' table key (number, optional) : How many samples to include
		<li>'accumulate' table key (function, optional) : A function to add values together
		<li>'mapFn' table key (function , optional) : A function to alter added data prior to inclusion
		<li>... (string tuple) : array values are used as titles for the channels.
	</ul>
--]]
	-- GRUNGE [arahn 2013-06-14 via scouten]: Pretty sure Doxygen doesn't support
	-- \param argument on \class declarations.

local WFDiagnosticLogRingBuffer = {

	_size = false,	-- Number of samples to retain

	_titles = false, -- labels for each channel

	_accumulate = false, -- optional user-provided function to do accumulation

	_mapFn = false, -- optional user-provided function to map values before storing

	_ringBuffer = false, -- a circular buffer of data, containing at most _size elements

}

--------------------------------------------------------------------------------

function WFDiagnosticLogRingBuffer:__init( options )

	WFArguments.check( 'WFDiagnosticLogRingBuffer', 'table' )

	options = options or {}
	self._size = options.size or 1000
	self._accumulate = options.accumulate or false
	self._mapFn = options.mapFn

	-- Strip down to just array elements.

	self._titles = { unpack( options, 1, #options ) }

	self._ringBuffer = {
		head = 0,
	}

end

--------------------------------------------------------------------------------
--[[!
	Add another set of data points to the ring buffer.

	\param (... < numbers > OR a one-element only, a function) This
	provides the data values for each channel. You may provide a function that
	will be evaluated to return the result if the values are not known
	fully yet.
--]]

function WFDiagnosticLogRingBuffer:add( ... )

	local ringBuffer = assert( self._ringBuffer )
	local mapFn = self._mapFn
	local accumulate = self._accumulate
	local head = ringBuffer.head
	local size = self._size
	local titles = self._titles

	local lastEntry = head > 0 and ringBuffer[ head ]
	local values

	if mapFn then
		values = { mapFn( ... ) }
	else
		values = { ... }
	end

	if accumulate then
		for i = 1, #titles do
			if lastEntry and lastEntry[ i ] then
				values[ i ] = ( values[ i ] or 0 ) + lastEntry[ i ]
			end
		end
	end

	-- Support for simple "future" values that will evaluate when
	-- we run their function.

	if type( values[ 1 ] ) == 'function' and #values == 1 then
		values = values[ 1 ]
	end

	head = head + 1

	if head > size then
		head = 1
	end

	ringBuffer.head = head
	ringBuffer[ head ] = values

end

--------------------------------------------------------------------------------
--[[!
	\brief visualize the data in this ring buffer.

	\param kind (string) eitehr "sparkline" or "boxAndWhisker" to produce
	the corresponding kind of visualization.

	\return a table suitable for inclusion in a WFDiagnosticLog that will
	generate nice-looking HTML output.
--]]

function WFDiagnosticLogRingBuffer:visualize( kind )

	WFArguments.check( 'WFDiagnosticLogRingBuffer', 'string' )

	-- Late require to avoid circular dependency.

	local WFDiagnosticLog = require 'WFDiagnosticLog'

	local titles = self._titles
	local ringBuffer = self._ringBuffer

	local result = {}
	local fn = kind == 'sparkline' and WFDiagnosticLog.sparkline
		or kind == 'boxAndWhisker' and WFDiagnosticLog.boxAndWhisker

	for i = 1, #titles do
		table.insert( result, fn( titles[ i ], historyNodes( ringBuffer, i ) ) )
	end

	return unpack( result )

end

--------------------------------------------------------------------------------

WFDiagnosticLogRingBuffer = Rx.Class( 'WFDiagnosticLogRingBuffer', WFDiagnosticLogRingBuffer )

--------------------------------------------------------------------------------

return WFDiagnosticLogRingBuffer
