% ConstructorArgsHelper - a collection of methods for helping during:
% - construction of Clusters
% - building of Jobs and Tasks

% Copyright 2011-2020 The MathWorks, Inc.

classdef ( Hidden ) ConstructorArgsHelper
    methods ( Static )
        function [names, values] = resolveTaskBuildArguments( objMetaClass, varargin )
            import parallel.internal.settings.ProfileExpander
            [names, values] = iResolveWorkUnitBuildArgs( objMetaClass, {}, ...
                @ProfileExpander.getTaskProperties, varargin{:} );
        end

        function [names, values] = resolveJobBuildArguments( objMetaClass, appendingArgs, varargin )
            import parallel.internal.settings.ProfileExpander
            [names, values] = iResolveWorkUnitBuildArgs( objMetaClass, appendingArgs, ...
                @ProfileExpander.getJobProperties, varargin{:} );
        end

        function [map, clusterProf] = interpretClusterCtorArgs( objMetaClass, varargin )
        % interpretCtorArgs - used during construction of a cluster.  The
        % constructor arguments are uniquified using a right-most-specified
        % value wins. Profiles are expanded in-line when encountered.  A "map"
        % is returned, the keys of which are the property names, the values are
        % a 2-cell containing the actual resolved value and a ValueType
        % specifying the origin of that value.

            import parallel.internal.settings.ProfileExpander
            import parallel.internal.settings.ValueType
            import parallel.internal.customattr.PropSet

            % Convert any string inputs to character vectors
            [varargin{:}] = convertStringsToChars(varargin{:});

            validateattributes( objMetaClass, {'meta.class'}, {'scalar'} );

            [names, values] = PropSet.amalgamate( {}, varargin{:} );
            map             = containers.Map();

            clusterProf = '';
            for ii = 1:numel( names )
                thisname = names{ii};
                thisval  = values{ii};

                if isequal( thisname, 'Profile' )
                    clusterProf = thisval;
                    [profileProps, profileValues] = ...
                        ProfileExpander.getClusterProperties( clusterProf );
                    [profileProps, profileValues] = iRetainApplicableProperties( ...
                        objMetaClass, profileProps, profileValues );
                        
                    for jj = 1:numel( profileProps )
                        profName = profileProps{jj};
                        profVal = profileValues{jj};
                        iCheckAndAddPropsToMap( map, objMetaClass, ...
                            profName, profVal, ValueType.Profile );
                    end
                else
                    iCheckAndAddPropsToMap( map, objMetaClass, ...
                        thisname, thisval, ValueType.User );
                end
            end
        end
    end
end

% --------------------------------------------------------------------------
% iResolveWorkUnitBuildArgs
% 1. amalgamate args using append/right-most-wins
% 2. expand winning Profile in-line if specified
% 3. amalgamate args using append/right-most-wins again with Profile in place.
function [names, values] = iResolveWorkUnitBuildArgs( ...
    objMetaClass, appendingArgs, profileExpanderFcn, varargin )

    import parallel.internal.customattr.PropSet
    import parallel.internal.customattr.Reflection

    [names, values] = PropSet.amalgamate( appendingArgs, varargin{:} );
    % Now check for Profile, we need to expand that and re-amalgamate
    isProfile = strcmp( 'Profile', names );
    if any( isProfile )
        profileIdx = find( isProfile );
        assert( length( profileIdx ) == 1 );

        % Expand the profile into PV pairs and insert them into the 
        % original PV.
        profileName = values{profileIdx};
        [profileProps, profileValues] = profileExpanderFcn(profileName);
        [profileProps, profileValues] = iRetainApplicableProperties( objMetaClass, ...
            profileProps, profileValues );

        names  = [names(1:profileIdx-1),  profileProps,  names(profileIdx+1:end)];
        values = [values(1:profileIdx-1), profileValues, values(profileIdx+1:end)];
        
        namesAndValues    = PropSet.namesValuesToPv( names, values );
        [names, values]   = PropSet.amalgamate( appendingArgs, namesAndValues{:} );
    end
    for ii=1:length( names )
        Reflection.deriveAndCheckSimpleConstraint( objMetaClass, names{ii}, values{ii} );
    end
end

% --------------------------------------------------------------------------
function map = iCheckAndAddPropsToMap( map, objMetaClass, name, value, valueType )
% Check the constraint and add it to the map
    import parallel.internal.customattr.Reflection
    value = Reflection.deriveAndCheckSimpleConstraint( objMetaClass, name, value );
    map( name ) = { value, valueType };
end

% --------------------------------------------------------------------------
function [props, values] = iRetainApplicableProperties( objMetaClass, props, values )
% Strip out the profile properties that don't apply to the supplied object
    import parallel.internal.customattr.Reflection
    validateattributes( props, {'cell'}, {'numel', numel( values )} );
    validateattributes( values, {'cell'}, {} );
    objProps = Reflection.getAllPublicProperties( objMetaClass );
    [~, applicablePropsIdx] = intersect( props, objProps );
    props  = props(applicablePropsIdx);
    values = values(applicablePropsIdx);
end
