% This class provides static methods for filtering a lists of objects.

% Copyright 2011-2018 The MathWorks, Inc.

classdef ( Hidden, Sealed ) WorkUnitFinder

    methods ( Static, Access = private )
        function [funits, isMatched] = filterByPVPairs( units, names, values )
        % Implementation of find:
        %   filter "units" and by names/values.
        %   If a given "unit" doesn't have all properties in "names", it can never
        %   match.
        %   Matching is performed using "isequal".

            import parallel.internal.customattr.Reflection;

            % Seed the match by picking those jobs which have all the named properties
            isMatched = Reflection.elementwiseHasAllProperties( units(:), names );

            % Next, refine the filtered list
            for ii = 1:length( names )
                if ~any( isMatched )
                    break;
                end
                oldMatch = isMatched;
                % Only examine those units currently in the running, note we pass the property
                % name in as a cell to "get" to force gotValues to be a cell.
                gotValues = get( units( oldMatch ), names(ii) );
                % Update "match" based on more matching.
                isMatched( oldMatch ) = cellfun( @(x) isequal( x, values{ii} ), ...
                                             gotValues );
            end
            funits = units( isMatched );
        end
        
        function [funits, isMatched] = filterByFcn( units, filterFcn )
        % Implementation of find:
        %   filter "units" by filter function.
        %   If a given "unit" is not indexed by the output of filterFcn
        %   then it does not match.
        
            try
                isMatched = filterFcn( units );
            catch ex
                error( message( 'parallel:job:FilterFunctionException', func2str(filterFcn), ex.message ) );
            end
            
            if isa( isMatched, 'logical' ) && numel( isMatched ) == numel( units )
                funits = units( isMatched );
            else
                error( message( 'parallel:job:InvalidFilterFunctionOutput' ) );
            end
        end
    end

    methods ( Static )
        function [funits, isMatched] = find( units, args )
        % FIND - find objects matching a set of criteria
        %    [funits, isMatched] = find( objs, PVPairsCell )
        %    filters the list "objs" of type CustomPropTypes by the P-V pairs specified
        %    as a cell array.
        %    funits - the subset of units with the specified P-V pair
        %    properties
        %    isMatched - a logical array indicating which members of units
        %    have the specified P-V pair properties
        %
        %    [funits, isMatched] = find( objs, filterFcn )
        %    filters the list "objs" by applying filterFcn to them.
        %    filterFcn returns a logical array used to index objs.
        %    funits - the subset of units for which filterFcn returns true
        %    isMatched - a logical array indicating for which memebrs of
        %    units the filterFcn returns true 
        %
        %    This is intended to support findJob()/findTask() and similar
        %    methods.

            import parallel.internal.customattr.PropSet
            import parallel.internal.customattr.WorkUnitFinder

            validateattributes( units, {'parallel.internal.customattr.CustomPropTypes'}, {} );
            if ~iscell(args)
                args = {args};
            end

            if numel(args) == 1 && isa(args{1}, 'function_handle')
                [funits, isMatched] = WorkUnitFinder.filterByFcn( units, args{1} );
            else
                [filterNames, filterValues] = PropSet.pvToNamesValues( args{:} );
                [funits, isMatched] = WorkUnitFinder.filterByPVPairs( units, filterNames, filterValues );
            end
        end
    end
end
