% gatherer - API for autoexpanding arrays for accumulating data
%
% gatherer ::
%    [N,M]:[[1,2]]  ~'size of elements to gather',
%    options {
%       init :: natural/512  ~'initial size of buffer';
%       grow :: natural/2    ~'factor by which to auto-expand buffer';
%       max  :: natural/1e9  ~'maximum size of buffer'
%    }
% -> struct {
%    remove  :: natural => void ~'action to remove last n elements';
%    length  :: void => natural ~'action to return current buffer length';
%    append  :: [[N,M]] => void ~'action to append data to buffer';
%    collect :: void =>[[N,L]]  ~'action to return buffer contents and reset';
% }.
function api=gatherer(size_in,varargin)
	opts=options('chunk',256,'init',512,'grow',2,'max',1e9,varargin{:});
	n=uint32(0); 
	cap=opts.init; % initial capacity of buffer
	buf=zeros(size_in(1),cap); % buffer

	api.remove=@remove;
	api.collect=@collect;
	api.length=@curlen;

	if isnan(size_in(2)),
		api.append=@append;
	elseif size_in(2)>1,
		m=uint32(size_in(2)); M=1:m;
		api.append=@append_m;
	else
		api.append=@append_1;
	end

	function l=curlen, l=n; end
	function remove(rem), n=n-rem;  end
	function x=collect, x=buf(:,1:n); buf=[]; end 
	function append_1(x)
		if n+1>cap, grow; end % need more room
		n=n+1; buf(:,n)=x;
	end

	function grow
		if n>opts.max, error('maximum capacity exceeded'); end
		cap=opts.grow*cap; 
		buf=repmat(buf,1,opts.grow); 
	end

	function append_var(x)
		chunk=size(x,2);
		if n+chunk>cap, grow; end % need more room
		buf(:,n+(1:chunk))=x;
		n=n+chunk;
	end

	function append_m(x)
		if n+m>cap, grow; end % need more room
		buf(:,n+M)=x;
		n=n+m;
	end
end
