function A = subsasgnTabularBraces(A, S, B, ~)
% Brace assignment for distributed table/timetable.

% Copyright 2018-2022 The MathWorks, Inc.

% If multiple levels of indexing, first get the variable and update it.
if numel(S)>1
    Avar = subsref(A, S(1));
    B = subsasgn(Avar, S(2:end), B);
    % We are now left with just the outer level of indexing.
    S = S(1);
end

if numel(S.subs) == 1
    error(message('MATLAB:table:LinearSubscript'));
elseif numel(S.subs) ~= 2
    error(message("MATLAB:table:NDSubscript"));
end

S.subs = tabularInterpretVartype(A, S.subs);

Atemplate = getTableTemplate(A);
[firstSub, secondSub] = deal(S.subs{:});
varIdx = iResolveVarSubscript(Atemplate, secondSub);

% For now we can only support assigning multiple variables at once if
% distributed in the first dimension.
if numel(varIdx)>1
    A = redistribute(A, codistributor1d(1));
end

% Table throws slightly different errors for bad indices.
try
    parallel.internal.indexing.validateTableIndexBounds(A, S);
catch ME
    % We don't support growing whereas in-memory tables do. Throw a
    % specific error for that case.
    if strcmp(ME.identifier, "MATLAB:table:RowIndexOutOfRange")
        error(message("parallel:distributed:SubsasgnBraceGrowRows"));
    elseif strcmp(ME.identifier, "MATLAB:table:VarIndexOutOfRange")
        error(message("parallel:distributed:SubsasgnBraceGrowVars"));
    else
        % Just throw the original error
        throw(ME);
    end
end

Acodistr = getCodistributor(A);
if Acodistr.Dimension==1
    % All variables are distributed. Extract the variable, assign it using
    % standard array assignment then put it back.
    Avarlp = A.Local{:,varIdx};
    globalSz = size(Avarlp);
    globalSz(1) = sum(Acodistr.Partition);
    Avar = codistributed.pDoBuildFromLocalPart(Avarlp, codistributor1d(1,Acodistr.Partition,globalSz));
    Avar = subsasgn(Avar, substruct('()', {firstSub,':'}), B);
    % Put the new var back into the table
    A.Local{:,varIdx} = Avar.Local;
    
else
    % Workers have whole variables. Find out which worker has the one we
    % want.
    assert(numel(varIdx)==1, ...
        "No support for brace assignment of multiple variables when distributed in dimension 2");
    
    numToLeft = cumsum(Acodistr.Partition);
    workerWithVar = find(numToLeft>=varIdx, 1, "first");
    if iscodistributed(B)
        % Work out which worker needs B and gather it there
        B = gather(B, workerWithVar);
    end
    % Catch any error so that all workers can throw.
    try
        if spmdIndex == workerWithVar
            if spmdIndex==1
                localIdx = varIdx;
            else
                localIdx = varIdx - numToLeft(spmdIndex-1);
            end
            A.Local = subsasgn(A.Local, substruct('{}', {firstSub, localIdx}), B);
        end
        err = [];
    catch err
    end
    % Check if the worker doing the assignment errored
    err = spmdCat(err);
    if ~isempty(err)
        throw(err(1));
    end
end
% Data has changed. Clear metadata.
A.Metadata.TableTemplate.reset();
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function varIdx = iResolveVarSubscript(template, subscript)
% Resolve a variable subscript allowing for growing (i.e. indices beyond
% the width of the table).

% Assign into the template to get all input checking done.
template = subsasgn(template, substruct('{}',{':',subscript}), 0);

% Now resolve the index into the new template (we already know the
% subscript is valid).
varNames = template.Properties.VariableNames;
[~,varIdx] = distributedutil.lookupTableVars(subscript, varNames);
end
