% A collection of helper methods to do with setting properties, and property
% argument lists.

% Copyright 2011-2020 The MathWorks, Inc.

classdef ( Hidden, Sealed ) PropSet
    methods ( Static )

        function [names, values] = pvToNamesValues( varargin )
        % Convert multiple arguments into names and values.
        % Errors if there aren't an even number of arguments, or
        % the 'names' aren't all strings.
            if mod( length(varargin), 2 ) ~= 0
                error(message('parallel:cluster:PropertyValueInPairs'));
            end
            names  = varargin(1:2:end);
            values = varargin(2:2:end);
            if ~iscellstr( names ) %#ok<ISCLSTR>
                error(message('parallel:cluster:PropertyNameMustBeChar'));
            end
        end

        function pvPairs = namesValuesToPv( names, values )
        % Convert 2 matched-length cell arrays of names and values back into
        % a P-V pairs list.
            if ~iscellstr( names ) %#ok<ISCLSTR>
                error(message('parallel:cluster:PropertyNameMustBeChar'));
            end
            if ~iscell( values )
                error(message('parallel:cluster:PropertyValuesCell'));
            end
            if numel( names ) ~= numel( values )
                error(message('parallel:cluster:NamesValuesSameLength'));
            end
            pvPairs = [ names(:), values(:) ].';
            pvPairs = reshape( pvPairs, 1, numel( pvPairs ) );
        end

        function [names, values] = amalgamate( appendingProps, varargin )
        % amalgamate - combine multiple P-V-pairs, selecting the "winning"
        % specification; appending values if specified.
        %
        % Note that appending properties will be row-ified. 
        %
        % The order of names/values in the P-V-pairs is preserved.
        % appendingProps (cellstr) - a list of names of properties whose values
        % append if specified multiple times. varargin: P-V-pairs.
        %
        % All property names (and appending names) are treated as case
        % *sensitive*.
        %
        % Example: PropSet.amalgamate( {'Appends'}, 'Appends', 1, 'Appends', 2, ...
        %     'Foo', 1, 'Foo', 2, 'Bar', 3 )
        % returns: [{'Appends', 'Foo', 'Bar'}, {[1 2], 2, 3}]
            import parallel.internal.customattr.PropSet

            [inputNames, inputValues] = PropSet.pvToNamesValues( varargin{:} );
            numPairs                  = length( inputNames );

            % Empty names/values for return
            names  = repmat( {''}, 1, numPairs );
            values = cell( 1, numPairs );

            % Logical array indicating which of names/values we should return.
            valid = false( 1, numPairs );

            for ii = 1:numPairs
                thisName  = inputNames{ii};
                thisValue = inputValues{ii};
                
                if  any(strcmp(thisName, appendingProps)) && ischar(thisValue)
                    % In order to sensibly append strings we need the
                    % property to be a cellstr.
                    thisValue = {thisValue};
                end

                % Have we already seen this? Ignore names that we've seen and
                % already rejected by masking using 'valid'.
                alreadySeenThisName = strcmp( thisName, names ) & valid;
                if any( alreadySeenThisName )
                    if sum( alreadySeenThisName ) ~= 1
                        error(message('parallel:cluster:PropSetUnexpectedMatches'));
                    end
                    % Seen this name before - overwrite or append as
                    % appropriate.
                    if ismember( thisName, appendingProps )
                        % Row-ify the value to ensure horzcat succeeds.
                        thisRowValue = reshape( thisValue, 1, numel( thisValue ) );
                        alreadySeenValue = values{alreadySeenThisName};
                        alreadySeenRow = reshape( alreadySeenValue, 1, numel( alreadySeenValue ) );
                        values{alreadySeenThisName} = [ alreadySeenRow, thisRowValue ];
                    else
                        valid(alreadySeenThisName) = false;
                        names{ii}  = thisName;
                        values{ii} = thisValue;
                        valid(ii)  = true;
                    end
                else
                    % New name.
                    names{ii}  = thisName;
                    values{ii} = thisValue;
                    valid(ii)  = true;
                end
            end
            names  = names( valid );
            values = values( valid );
        end

        function [isSpecified, propValue, remainders] = extractOneProperty( propName, varargin )
        % extractOneProperty - find the "winning" (right-most) setting of a
        % given named property. Case-sensitive. Returns:
        % - boolean flag indicating whether or not the property was specified
        % - the value itself, or [] if not specified
        % - a cell array of the remaining arguments, with all instances of 
        %   propName removed.

            import parallel.internal.customattr.PropSet

            [names, values] = PropSet.pvToNamesValues( varargin{:} );
            propMatch       = strcmp( propName, names );
            isSpecified     = any( propMatch );

            if isSpecified
                winner     = find( propMatch, 1, 'last' );
                propValue  = values{winner};
                remainders = PropSet.namesValuesToPv( names( ~propMatch ), ...
                                                      values( ~propMatch ) );
            else
                propValue  = [];
                remainders = varargin;
            end
        end

    end
end
