function [ values, handled ] = doVectorisedGet( obj, propsToAccess, handled, existingValues )
%

%   Copyright 2011-2022 The MathWorks, Inc.

    import parallel.internal.customattr.Reflection
    
    publicProps = Reflection.getPublicProperties(obj);
    
    if nargin < 3
        handled = num2cell( false(1, numel(propsToAccess)) );
        existingValues = cell(1, numel(propsToAccess));
    elseif nargin < 4
        existingValues = cell(1, numel(propsToAccess));        
    end
    
    if ~obj.isConnected() || ~obj.ClusterState.isStateClusterAvailable()
        [values, handled] = iGetDefaultProps(obj, publicProps, propsToAccess, handled, existingValues);
    else
        [values, handled] = iGetProps(obj, publicProps, propsToAccess, handled, existingValues);
    end
    [values, handled] = doVectorisedGet@parallel.Cluster(obj, propsToAccess, handled, values);
end
% -------------------------------------------------------------------------
%
% iGetDefaultProps: Get properties when there is no support to defer to OR
% the cluster is not in the state 'ready', i.e. the MJS is being started up
% or built and we don't want to send any calls to it yet, but we can still
% talk to the cloud console
%
function [newValues, handledHere] = iGetDefaultProps(obj, myProps, propsToAccess, handled, existingValues)

    superProps = {'Type', 'Profile', 'Modified'};
    specialHandlingProps = {...
                            'Name',...
                            'Host',...
                            'MatlabVersion',...
                            'NumWorkersRequested',...
                            'Jobs',...
                            'State'};
    clusterDetails = obj.getClusterDetails();
    specialHandlingFuncs = { @() clusterDetails.ClusterName, ...
                             @() clusterDetails.ClusterHeadNodeDNSName, ...
                             @() clusterDetails.MatlabReleaseVersion, ...
                             @() clusterDetails.NumberOfWorkers, ...
                             @() parallel.job.MJSIndependentJob.empty(), ...
                             @() obj.ClusterState.Name };
    lookupMap = containers.Map(specialHandlingProps, specialHandlingFuncs);
    
    validPropsToLookup = setdiff(myProps, superProps, 'legacy');
    
    helperFunc = @(a, b, c) iVectorisedGetHelper(lookupMap, validPropsToLookup, a, b, c);
    
    [newValues, handledHere] = cellfun(helperFunc, propsToAccess, handled, existingValues, 'UniformOutput', false);
    
end
%
function [val, handled] = iVectorisedGetHelper(map, validNameList, name, alreadyHandled, existingValue)
    if alreadyHandled
        val = existingValue;
        handled = alreadyHandled;
    else
        if ismember(name, validNameList, 'legacy')
            if map.isKey(name)
                func = map(name);
                val = {func()};
                handled = true;
            else
                val = {''};
                handled = true;
            end
        else
            val = existingValue;
            handled = alreadyHandled;
        end
    end
end
%
% iGetProps: Get properties using the support
%
function [vals, handled] = iGetProps(obj, myProps, propsToAccess, handled, vals)

    propsToHandleLocally = {...
        'Certificate', ...
        'PreferredPoolNumWorkers', ...
        'Identifier', ...
        'IsClusterLifetimeManagementEnabled', ...
        'Modified', ...
        'Profile', ...
        'Type', ...
        'UserData', ...
        'LicenseNumber', ...
        'RequiresOnlineLicensing', ...
        'NumThreads', ...
        'OfflineMaxNumMachines', ...
        'MaxNumMachines', ...
        'NumWorkersPerMachine'};
    
    % Some properties need to be obtained using a different mechanism, and cannot
    % be retrieved in a vectorized call.
    specialHandlingProps = {...
        'MatlabVersion', ... 
        'JobStorageLocation', ...
        'Jobs', ...
        'State', ...
        'MaxNumWorkers', ...
        'NumWorkersRequested', ...
        'ShutdownAt', ... % This value is stored in Cloud Center not the cluster.
        'OperatingSystem', ... % We have to check the workers for this value.
        'SharedState'
    };
    clusterDetails = obj.getClusterDetails();
    specialHandlingFuncs = { ...
        @() clusterDetails.MatlabReleaseVersion, ...
        @() parallel.cluster.MJSCloudCluster.hFormatJobStorageLocation( ...
                clusterDetails.ClusterName, ...
                clusterDetails.ClusterHeadNodeDNSName ), ...
        @() obj.Jobs, ...
        @() obj.ClusterState.Name, ...
        @() obj.MaxNumWorkers, ...
        @() clusterDetails.NumberOfWorkers, ...
        @() parallel.cluster.MJSCloudCluster.hFormatTerminationPolicy( clusterDetails ), ...
        @() iGetOperatingSystem(), ...
        @() getString(message(parallel.internal.types.ClusterSharedStates.fromName(clusterDetails.ClusterSharedState).MessageCatalogueUIString))};
    specialHandlingMap = containers.Map(specialHandlingProps, specialHandlingFuncs);
    
    propsNotLookedUpOnSupport = union(propsToHandleLocally, specialHandlingProps, 'legacy');
    
    propsToHandleIndex  = ~cell2mat(handled);
    propsUnhandledSoFar = propsToAccess(propsToHandleIndex);
    [~, iPropsToAccess] = ismember(propsUnhandledSoFar, propsToAccess, 'legacy');
    [validPropsToLookup, ~, iPropsUnhandled] = intersect(myProps, propsUnhandledSoFar, 'legacy');   
    
    [nonLookupProps, iValidProps1]  = intersect(validPropsToLookup, propsNotLookedUpOnSupport, 'legacy');     
    if ~isempty(nonLookupProps)
        localLookupFunc = @(x) iLocalLookupHelper(obj, specialHandlingMap, x);
        nonLookupVals   = cellfun(localLookupFunc, nonLookupProps, 'UniformOutput', false);
    else
        nonLookupVals = {};
    end
    
    [clusterLookupProps, iValidProps2] = setdiff(validPropsToLookup, propsNotLookedUpOnSupport, 'legacy');
    if ~isempty(clusterLookupProps)
        lookedUpVals = obj.getClusterProps(clusterLookupProps);
     else
        lookedUpVals = {};
    end
    
    propIds  = [ iValidProps1, iValidProps2 ];
    propVals = [ nonLookupVals, lookedUpVals ];
    
    propsUnhandledIds = iPropsUnhandled(propIds);
    accessIds = iPropsToAccess(propsUnhandledIds);
    handled(accessIds) = {true};
    vals(accessIds) = propVals;
end   
%
function val = iLocalLookupHelper(obj, map, name)
    if map.isKey(name)
        getFunc = map(name);
        val = getFunc();
    else
        getFunc = @(x) obj.(x);
        val = getFunc(name);
    end    
end

% As an optimization we are not interrogating the cluster for the operating system.
% We know that the cluster is running unix. Until we support other operating
% systems we are hard-coding the value here.
function val = iGetOperatingSystem()
    val = parallel.internal.types.OperatingSystem.Unix.Name;
end
