%KeyValueBuffer
% A buffer of writes that have yet to be written. The intention of this
% class is to combine a number of small writes into one large vectorized write.

%   Copyright 2014-2016 The MathWorks, Inc.

classdef (Sealed, Hidden) KeyValueBuffer < handle
    
    properties (SetAccess = immutable)
        MaxNumAdds; % Maximum number of adds allowed before a reset.
        MaxContentSize; % Maximum content size before this reports as full.
    end
    
    properties (SetAccess = private)
        NumAdds = 0; % Current number of adds.
        ContentSize = 0; % Current content size.
    end
    
    properties (Access = private)
        KeyCache; % A cache of currently unwritten keys.
        ValueCache; % A cache of currently unwritten values.
    end
    
    properties (Access = private, Constant)
        DefaultMaxNumAdds = 1024; % Default value for MaxNumAdds.
        DefaultMaxContentSize = 1024 * 1024; % Default value for MaxContentSize.
    end
    
    methods
        % Create a buffer with the given limits.
        function this = KeyValueBuffer(maxNumAdds, maxContentSize)
            import parallel.internal.mapreduce.KeyValueBuffer;
            if ~nargin
                maxNumAdds = KeyValueBuffer.DefaultMaxNumAdds;
                maxContentSize = KeyValueBuffer.DefaultMaxContentSize;
            end
            validateattributes(maxNumAdds, {'double'}, {'scalar', 'integer', 'positive'})
            validateattributes(maxContentSize, {'double'}, {'scalar', 'integer', 'positive'})
            this.MaxNumAdds = maxNumAdds;
            this.MaxContentSize = maxContentSize;
            this.KeyCache = cell(maxNumAdds, 1);
            this.ValueCache = cell(maxNumAdds, 1);
        end
        
        % Add the given <key, value> to the underlying data storage.
        % The keys and values input must be column vectors of the same length.
        % Both inputs must be vertcat-able with the input of other calls to
        % add. This will return true when either of the set limits are hit.
        function isFull = add(this, keys, values)
            assert(iscolumn(keys) && iscolumn(values) && numel(keys) == numel(values));
            
            whosData = whos('values');
            this.NumAdds = this.NumAdds + 1;
            this.ContentSize = this.ContentSize + whosData.bytes;
            this.KeyCache{this.NumAdds} = keys;
            this.ValueCache{this.NumAdds} = values;
            
            
            isFull = (this.ContentSize >= this.MaxContentSize) || (this.NumAdds >= this.MaxNumAdds);
        end
        
        % Commit any output to the final location. This is only called if
        % the task is successful. If a task is not successful, the sink
        % is expected to cleanup all side-effects when destroyed.
        function [keys, values] = get(this)
            % Keys can only be either a column vector of numeric scalars or
            % a column cell array of strings. Vertcat is ok here.
            keys = vertcat(this.KeyCache{:});
            
            % Values are a column array of "scalars" of the same type. The
            % only time vertcat will fail is if the user is using a custom
            % array type that does not follow the vertcat contract. As this
            % is an advanced use-case, we will not guard against it here.
            values = vertcat(this.ValueCache{:});
        end
        
        % Reset the buffer to empty.
        function reset(this)
            this.KeyCache = cell(this.MaxNumAdds, 1);
            this.ValueCache = cell(this.MaxNumAdds, 1);
            this.NumAdds = 0;
            this.ContentSize = 0;
        end
    end
end
