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

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
--

-- The dedup option ensures that no duplicates can be added into the ring buffer.
--------------------------------------------------------------------------------

local function make( n, dedup )

	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, 
		values = {}, 
		dedup = not not dedup
	}

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 or rb.values[ v ] then

		return rb

	end	

	local prevI = rb.i

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

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

	if rb.dedup then
	
		local currentValue = rb.buf[ rb.i ]
		
		if currentValue then
			rb.values[ currentValue ] = nil
		end
		
		rb.values[ v ] = true
		
	end

	rb.buf[ rb.i ] = v

	return rb

end

--------------------------------------------------------------------------------
-- Return true if the item already exists in the ring buffer. Returns false
-- otherwise. The following function only works if the ring buffer was created
-- with the dedup option.
--------------------------------------------------------------------------------

local function hasItem( rb, v )

	return not not rb.values[ v ]

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,

	hasItem = hasItem,

	map = map,

	full = full,

}
