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

WFRingBuffer.lua

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

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

NOTICE: All information contained herein is, and remain 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 may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

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

--------------------------------------------------------------------------------
-- makes a ring buffer of size n. Ring buffers accumulate up to n items. When
-- additional items are added, the oldest item is overwritten each time.
--
-- ring buffers are implemented as opaque objects. use the addItem api to
-- add to them, and use the map api to iterate over them
--------------------------------------------------------------------------------

local function make( n )

	if type( n ) ~= "number" or n <= 0 or 
			math.ceil( n ) ~= math.floor( n ) then

		error( string.format( 
				"make ring buffer requires positive integer, got %s",
				tostring( n ) ) )

	end

	return { buf = {}, max = n, i = 0, didCycle = false }

end

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

local function cyclicIncrement( i, cycleN )

	return i < cycleN and i + 1 or 1

end

--------------------------------------------------------------------------------
-- adds an item to ring buffer if v is non-nil. Returns updated ring buffer
--------------------------------------------------------------------------------

local function addItem( rb, v )

	if v == nil then

		return rb

	end	

	local prevI = rb.i

	rb.i = cyclicIncrement( rb.i, rb.max )

	rb.didCycle = rb.didCycle or rb.i <= prevI

	rb.buf[ rb.i ] = v

	return rb

end

--------------------------------------------------------------------------------
-- maps a function function over all the values in the ring buffer in LIFO
-- the return value of each function call is captured in an array which is
-- returned to the caller
--------------------------------------------------------------------------------

local function map( f, rb, ... )

	if rb.i == 0 then

		return {}

	end

	if rb.max == 1 then

		return { f( rb.buf[ 1 ] ) }

	end

	local start = not rb.didCycle and 
				1 or
				( rb.i < rb.max and 
					rb.i + 1 or 1 )

	local i = start

	local ret = {}

	repeat

		local v = rb.buf[ i ]

		if v == nil then

			break

		end

		ret[ #ret + 1 ] = f( v, ... )

		i = cyclicIncrement( i, rb.max )

	until i == start

	return ret

end

--------------------------------------------------------------------------------
-- returns true if the ring buffer is full
--------------------------------------------------------------------------------

local function full( rb )

	return #rb.buf == rb.max

end

--------------------------------------------------------------------------------
-- Ring buffers are opaque, use the functions below to manipulate them.
--------------------------------------------------------------------------------

return {

	make = make,

	addItem = addItem,

	map = map,

	full = full,

}
