function [localX, xDist] = hDiffImpl(xDist, localX, dim)
% hDiffImpl Implementation of DIFF for codistributor1d along specified dimension

% Copyright 2014-2022 The MathWorks, Inc.

outDims = xDist.Cached.GlobalSize;
outDims(dim) = outDims(dim) - 1;

if xDist.Dimension == dim
    [localX, xDist] = iDiffAlongDistributionDimension(localX, xDist, dim, outDims);
else
    localX = diff(localX, 1, dim);
    % Explicitly construct the codistributor1d with exactly the right information -
    % we do not wish to allow the codistributor1d to recalculate anything.
    xDist  = codistributor1d(xDist.Dimension, xDist.Partition, outDims);
end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [localX, xDist] = iDiffAlongDistributionDimension(localX, xDist, dim, outDims)
% diff along the distribution dimension. Workers must send a slice to their left
% neighbour who will concatenate it with their local part before calling
% DIFF.

% Interpret the partition to work where the data is.
workersWithData     = find(xDist.Partition > 0);
firstWorkerWithData = workersWithData(1);
lastWorkerWithData  = workersWithData(end);
iHaveData           = xDist.Partition(spmdIndex) > 0;

% Work out who needs to send, and who needs to receive.
% Senders:   have data, and are not the last with data
% Receivers: have data, and are not the first with data
sendTo   = [];
recvFrom = [];
sendData = [];
if iHaveData
    myIndexInWorkersWithData = find(workersWithData == spmdIndex);
    if spmdIndex ~= firstWorkerWithData
        % Not the first worker with data - send to the worker to the left with data
        sendTo            = workersWithData(myIndexInWorkersWithData - 1);
        sliceRequest      = repmat({':'}, 1, max(dim, ndims(localX)));
        sliceRequest{dim} = 1;
        sendData          = localX(sliceRequest{:});
    end
    if spmdIndex ~= lastWorkerWithData
        % Not the last worker with data - receive from worker to the right with data
        recvFrom = workersWithData(myIndexInWorkersWithData + 1);
    end
end

mwTag         = 31885;
receivedSlice = spmdSendReceive(sendTo, recvFrom, sendData, mwTag);

% If we received, cat it onto the existing local part.
if ~isempty(recvFrom)
    localX = cat(dim, localX, receivedSlice);
end

% Call built-in DIFF on the probably-augmented local part.
localX = diff(localX, 1, dim);

% Build a new codistributor1d with updated partition and global size. Use the
% codistributor1d constructor directly because we do not wish to allow the
% codistributor1d to recalculate anything.
newPartition = xDist.Partition;
newPartition(lastWorkerWithData) = newPartition(lastWorkerWithData) - 1;
xDist = codistributor1d(xDist.Dimension, newPartition, outDims);
end
