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


%   Copyright 2006-2021 The MathWorks, Inc.

% This method needs to have varargout so that users see the correct error
% message when trying to use struct indexing on a distributed array.

% We only support a single level of indexing for now, except for tables.
if ~istable(obj) && ~istimetable(obj) && length(S) ~= 1
    error(message('parallel:distributed:DistributedSubsrefSimple'))
end

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

try
    for ii=1:numel(S)-1
        obj = iDispatchOne(obj, S(ii));
    end
    varargout{1} = iDispatchOne(obj, S(end));
catch err
    % Hide the call stack
    throw(err);
end

% Finally, if we requested more than one output and the method didn't
% throw, error now.
nargoutchk(0,1);

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

% If previous indexing steps have given us an in-memory object, just use
% normal indexing.
if ~iscodistributed(obj)
    if isa(obj, 'matlab.tabular.TimetableProperties')
        % Some properties are not supported for codistributed 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

% Get function handles for the different indexing methods depending on the
% underlying class
subsrefMethods = iGetSubsrefMethods(obj);

switch S(1).type
    case '()'
        method = subsrefMethods.Parens;
    case '{}'
        method = subsrefMethods.Braces;
    case '.'
        method = subsrefMethods.Dot;
end

varargout{1} = method(obj, S);

end

function subsrefMethods = iGetSubsrefMethods(obj)
% TODO: these should probably be in some kind of adaptor class, as per tall arrays,
% so that we can use OO principles instead of switchyards.
    
% Get all subsref method as function handles depending on the underlying
% class
switch underlyingType(obj)
    case {'table', 'timetable'}
        % To avoid stalls, make sure errors are thrown collectively
        %TODO(g1831002) This does not obey the rule of not calling any
        % communicating method inside syncOnError but has been used like
        % this since 17a. We need to investigate this further, but for the
        % refactoring we keep this safe guard in place.
        subsrefMethods.Parens = @(x, y)distributedutil.syncOnError(...
            @subsrefTabularParens, x, y);
        subsrefMethods.Braces = @(x, y)distributedutil.syncOnError(...
            @subsrefTabularBraces, x, y);
        subsrefMethods.Dot    = @(x, y)distributedutil.syncOnError(...
            @subsrefTabularDot, x, y);
        
    case {'datetime', 'duration', 'calendarDuration'}
        % Datetime family has Format and Timezeone properties accessible
        % using dot notation, but oterwise obeys generic indexing rules.
        subsrefMethods.Parens = @subsrefParens;
        subsrefMethods.Braces = @subsrefBraces;
        subsrefMethods.Dot    = @datetimeSubsrefDot;
        
    otherwise 
        subsrefMethods.Parens = @subsrefParens;
        subsrefMethods.Braces = @subsrefBraces;
        subsrefMethods.Dot    = @subsrefDot;
end
end
