function B = subsrefParens(A, aidx)
% subsrefParens performs paren indexing, i.e., (), for all distributed
% inputs.

% Copyright 2018-2022 The MathWorks, Inc.

assert(numel(aidx)==1, ...
    "subsrefParens can only handle a single level of subsref.");

% Tabular data like table and timetable enter this function from
% subsrefTabularParens with exactly two indices; assert otherwise.
isTabularData = istable(A) || istimetable(A);
assert(~isTabularData || (isTabularData && numel(aidx.subs)==2), ...
    "Tabular data must have exactly 2 subscripts for parens-indexing.");

nargoutchk(0, 1);
distA = getCodistributor(A);

[aidx, isColonLiteral, reshapeIndex, outDims] = iGatherCheckAndTransformSubscripts(aidx, distA);

if numel(aidx.subs)==1 && isColonLiteral
    % Shortcut for A(:), i.e., a single index that is ColonLiteral
    % -> just colonize
    B = colonize(A);
    return;
elseif isempty(aidx.subs) || ...
        (all(isColonLiteral) && numel(isColonLiteral)>=ndims(A))
    % Shortcut for A() or A(:,:,...,:), where we have at least ndims(A)
    % many ':' -> just return A 
    % Additonally, we need remove zero or empty imaginary parts for numeric
    % inputs if we are not in the A() case, i.e., aidx.subs is not empty.
    if isnumeric(A) && ~isempty(aidx.subs)
        localA = getLocalPart(A);
        % If localA has more dims then we have subscripts, we need to add
        % ':' to the subscripts for the additional dimensions. Notice that
        % this only happens if we use codistributor1d with a distribution
        % dimension higher than ndims(A).
        if ndims(localA)>numel(aidx.subs)
            aidx.subs = [aidx.subs, repmat({':'}, 1, ndims(localA)-numel(aidx.subs))];
        end
        localA = localA(aidx.subs{:});
        A = codistributed.pDoBuildFromLocalPart(localA, distA);
    end
    B = A;
    return;
end

% For tables, we need to create a tmpl on the codistributed level, where we
% can make use of meta data, to pass into the codistributor level
if isTabularData
    tableTmpl = getTableTemplate(A);
else
    tableTmpl = [];
end

% Call codistributor specific implementation. In case of problems we fall
% back to the 1D implementation as it should be complete.
try
    B = distA.hSubsrefParensImpl(A, aidx, tableTmpl);
catch err
    if isequal(err.identifier, "parallel:distributed:Subsref2dbcNotSupportedSyntax")
        % In this case we should revert to 1D codistributor rather than
        % forcing the customre to do it.
        A = redistribute(A, codistributor1d());
        distA = getCodistributor(A);
        B = distA.hSubsrefParensImpl(A, aidx, tableTmpl);
    else
        rethrow(err);
    end
end

% Reshape result for non-vector subscripts
if reshapeIndex
    B = reshape(B, outDims);
end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [aidx, isColonLiteral, reshapeIndex, outDims] = iGatherCheckAndTransformSubscripts(aidx, distA)
% Helper that pre-processes the subscripts:
% * gather all subscripts
% * reshape if single subscript is not a vector
% * transfer logical arrays intos subscripts
% * replace subscript by ':' if possible

lengthAidxSubs = numel(aidx.subs);
gsize = distA.Cached.GlobalSize;
ndimA = numel(gsize);
% Pad gsize with ones up to numer of subscripts
gsize = [gsize ones(1, lengthAidxSubs-length(gsize))];

% Check whether subscripts are linear indices, i.e., the last subscript if
% we have less subscripts than dimensions. Notice that this means a single
% index is always a linear index, since every array is at least
% 2-dimensional.
idxIsNotLinear = true(1, lengthAidxSubs);
if ndimA>lengthAidxSubs && lengthAidxSubs>0
    idxIsNotLinear(lengthAidxSubs) = false;
end

outDims = [];
reshapeIndex = false;

for k = 1:lengthAidxSubs
    if isa(aidx.subs{k}, 'codistributed')
        aidx.subs{k} = gather(aidx.subs{k});
    end
    
    if islogical(aidx.subs{k})
        aidx.subs{k} = find(aidx.subs{k});
    end
    
    if k==1 
        % For a single index which is not a vector, reshape the subscript and
        % afterwards reshape the result, i.e., B = reshape(A(idx(:)), size(idx))
        reshapeIndex = lengthAidxSubs==1 && ~isvector(aidx.subs{k});
        
        if reshapeIndex
            outDims = size(aidx.subs{k});
            aidx.subs{k} = aidx.subs{k}(:);
        end
    end
    
    % Check if non-empty subscript describes the entire array in dimension
    % k BUT it's not a linear index, unless A is scalar. For non scalar A,
    % the latter one could create weird results, e.g., 
    % A=rand(n,k); A(1:n) --> A(:)
    if ~isempty(aidx.subs{k}) && ... % non-empty index
            ... % index goes over entire array
            aidx.subs{k}(1)==1 && aidx.subs{k}(end)==gsize(k) && ...
            ... % no linear index unless input is scalar
            (idxIsNotLinear(k) || prod(gsize)==1)

        % First option: a provided ColonDescriptor is actually
        % 1:1:gsize(k). Notice that a:b:a always creates a independent of
        % the stride b. Hence, if gsize(k)==1 and a==1 (as checked above),
        % we can ignore the stride.
        if parallel.internal.indexing.isColonDescriptor(aidx.subs{k}) ...
                && (aidx.subs{k}.Stride==1 || gsize(k)==1)
            aidx.subs{k} = ':';
            
            % Second option: we have an expanded vector describing the
            % entire array in dimension k 
        elseif numel(aidx.subs{k})==gsize(k)
            steps = unique(diff(aidx.subs{k}));
            % If we have a unique step of 1 or if gsize(k)==1, we use ':'
            if (numel(steps)==1 && steps==1) || gsize(k)==1
                aidx.subs{k} = ':';
            end
        end
    end
end

isColonLiteral = cellfun(@parallel.internal.indexing.isColonLiteral, aidx.subs);

end