function varargout = subsref(obj, S)
%SUBSREF Subscripted reference for distributed array
%   B = A(I)
%   B = A(I,J)
%   B = A(I,J,K,...)
%   
%   A{...} indexing is not supported for distributed cell arrays.
%   A.field indexing is not supported for distributed arrays of structs.
%   
%   Example:
%       N = 1000;
%       D = distributed.eye(N);
%       one = D(N,N)
%   
%   See also SUBSREF, DISTRIBUTED, DISTRIBUTED/EYE.


%   Copyright 2006-2019 The MathWorks, Inc.

% Make sure S is a valid substruct
matlab.bigdata.internal.util.checkValidSubstruct(S);

% Because of the way MATLAB translates index requests into subsref calls, S
% may contain distributed objects, so we need to disentangle things.

% We must pass the subs in explicitly, as some of them might be
% distributed. distributedutil.distributedSpmdWrapper will check for validity of any distributed
% arguments. For multi-level indexing, only the final level can have more
% than one output.
for ii=1:numel(S)-1
    obj = iDispatchOne(obj, S(ii));
end
[varargout{1:nargout}] = iDispatchOne(obj, S(end));

end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Perform one level of indexing
function varargout = iDispatchOne(obj, S)

% This is needed to suggest a correct variable name and syntax when erroring. 
import matlab.lang.correction.ReplaceIdentifierCorrection

% In some cases, the result of the first subsref might be local. In that
% case just use normal subsref.
if ~isdistributed(obj)
    if isa(obj, 'matlab.tabular.TimetableProperties')
        % Some properties are not supported for distributed timetable and
        % we throw a specific error.
        propName = S.subs;
        unsupportedProps = distributedutil.getDistributedTimetableUnsupportedProperties();
        if ismember(propName, unsupportedProps)
            error(message('parallel:distributed:UnsupportedTimetableProperty', propName));
        end
        % Handle separately supported properties with a wrong case, and non-existing properties.
        try
            [varargout{1:nargout}] = subsref(obj, S);
            return;
        catch ME
            if ME.identifier == "MATLAB:noSuchMethodOrField"
                timetableProps = string(properties(matlab.tabular.TimetableProperties));
                supportedProps = setdiff(timetableProps, unsupportedProps);
                match = matches(supportedProps, propName, 'IgnoreCase', true);
                if any(match)
                    % Error and suggest the correct variable name and syntax.
                    match = supportedProps(match);
                    throw(MException(message('MATLAB:table:UnknownPropertyCase', propName, match)) ...
                        .addCorrection(ReplaceIdentifierCorrection(propName, match)));
                end
                error(message('MATLAB:table:UnknownProperty', propName));
            else
                throw(ME);
            end
        end
    elseif isa(obj, 'matlab.tabular.TableProperties')
        % Distributed tables have all the properties as in-memory tables. We need to
        % handle separately non-existing properties and supported properties
        % with a wrong case.
        try
            [varargout{1:nargout}] = subsref(obj, S);
            return;
        catch ME
            if ME.identifier == "MATLAB:noSuchMethodOrField"
                propName = S.subs;
                % All properties are supported for distributed tables.
                supportedProps = string(properties(obj));
                match = matches(supportedProps, propName, 'IgnoreCase', true);
                if any(match)
                    % Error and suggest the correct variable name and syntax.
                    match = supportedProps(match);
                    throw(MException(message('MATLAB:table:UnknownPropertyCase', propName, match)) ...
                        .addCorrection(ReplaceIdentifierCorrection(propName, match)));
                end
                error(message('MATLAB:table:UnknownProperty', propName));
            else
                throw(ME);
            end
        end
    else
        varargout{1} = subsref(obj, S);
        return;
    end
end

% For distributed arrays, we must unpack the substruct in case the index
% vector/array was itself distributed.
if iscell(S.subs)
    [varargout{1:nargout}] = distributedutil.distributedSpmdWrapper(@iSubsRefHelper, obj, S.type, S.subs{:});
else
    [varargout{1:nargout}] = distributedutil.distributedSpmdWrapper(@iSubsRefHelper, obj, S.type, S.subs);
end

% Special case for timetable.Properties since the RowTimes field of the
% returned struct is itself a distributed array and must be fetched
% separately.
if istimetable(obj) && isequal(S, substruct( '.', 'Properties' ))
    timeVar = varargout{1}.DimensionNames{1};
    varargout{1}.RowTimes = distributedutil.distributedSpmdWrapper(@iSubsRefHelper, obj, '.', timeVar);
end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Receives the disentangled index information which
function varargout = iSubsRefHelper(coDd, s_type, varargin)
varargout = cell(1, nargout);
[varargout{:}] = subsref(coDd, substruct(s_type, varargin));
end
