function obj = parenAssign(obj,rhs,varargin)
% Implement obj(indices)=rhs

% Same as preparation for binary, but permute data to match the
% view format for both obj and rhs.

%   Copyright 2019-2020 The MathWorks, Inc.

% Note: obj is always a dlarray, other cases error without reaching this
% method. Only exception is if variable obj does not exist, and rhs is a
% dlarray.
if ~isobject(obj) && isempty(obj)
    % Indexing into an non-existent variable calls parenAssign with obj set
    % to [].
    obj = zeros(0, 0, 'like', rhs);
end

isAssignEmptyBrackets = ~isobject(rhs) && isempty(rhs) && builtin('_isEmptySqrBrktLiteral', rhs);

dimsobj = dims(obj);

if isAssignEmptyBrackets && numel(varargin) < ndims(obj)
    % Drop the format if the indexing does not address all of the dimensions
    % (result will be reduced in dimensionality compared to the input).
    dimsobj = '';
end

szobj = size(obj);
objViewToData = obj.ViewToDataMap;

[fdobj, objdata] = extractData(obj.FormattedData);
obj.FormattedData = [];

[rhsdata, rhsViewToData] = iGetRHS(rhs);

if isa(rhsdata, 'deep.internal.recording.RecordingArray') && ~isa(objdata, 'deep.internal.recording.RecordingArray')
    objdata = deep.internal.recording.RecordingArray(objdata);
end

argsNonDlarray = [];

success = false;
if isempty(objViewToData)
    % No permutation vector, explicit permute is a no-op.
    if ~isempty(rhsViewToData)
        rhsdata = permute(rhsdata, rhsViewToData);
    end
    
    % Remove dlarray subscripts
    [objdata, argsNonDlarray] = iRemoveDlarray(objdata, varargin);
    
    % Apply indexing to the data
    objdata(argsNonDlarray{:}) = rhsdata;
    
    success = true;
    
elseif isscalar(varargin) && islogical(varargin{1}) && isscalar(rhs) && isequal(szobj, size(varargin{1}))
    % x(logical) = scalar
    [objdata, success] = iAssignScalarUsingLogicalIndex(objdata, objViewToData, rhsdata, varargin{1});

    
elseif length(varargin) >= length(szobj)
    
    % Remove dlarray subscripts
    [objdata, argsNonDlarray] = iRemoveDlarray(objdata,varargin);
    
    if isscalar(rhs)
        % x(ind1, ind2, ..., indD) = scalar, with D >= ndims(obj).
        [objdata, objViewToData, success] = ...
            iAssignScalarNdSubscripts(objdata, objViewToData, rhsdata, argsNonDlarray);
        
    elseif isAssignEmptyBrackets && ~all(cellfun(@(x) ischar(x) && isequal(x, ':'), varargin))
        % x(ind1, ind2, ..., indD) = [], with D >= ndims(obj).
        [objdata, objViewToData, success] = ...
            iAssignScalarNdSubscripts(objdata, objViewToData, rhs, argsNonDlarray);
    else
        % x(ind1, ind2, ..., indD) = y, with D >= ndims(obj).
        [objdata, objViewToData, success] = ...
            iAssignNonScalarNdSubscripts(objdata, objViewToData, rhsdata, rhsViewToData, argsNonDlarray);
    end
end

if ~success
    if isequal(argsNonDlarray,[])
        % Remove dlarray subscripts
        [objdata, argsNonDlarray] = iRemoveDlarray(objdata,varargin);
    end
    
    % Extract data as is
    objdata = iAssignWithExplicitPermute(objdata, objViewToData, rhsdata, rhsViewToData, argsNonDlarray);
    
    objViewToData = [];
end

% Adjust dimension labels (add 'U' for trailing dimensions that used to be
% singletons without a dimension label, drop 'U' for trailing dimensions
% that have become singleton).
% May call squeezedata if dimension labels are dropped.
[dimsobj, objdata, objViewToData] = addOrDropDimensionLabels(dimsobj, objdata, objViewToData);
fdobj.DimensionMetadata = dimsobj;

% Set data
obj.FormattedData = insertData(fdobj, objdata);

% Set implicit permutations
if isempty(objViewToData)
    obj.ViewToDataMap = [];
    obj.DataToViewMap = [];
else
    objDataToView(objViewToData) = 1:length(objViewToData);
    obj.ViewToDataMap = objViewToData;
    obj.DataToViewMap = objDataToView;
end

end


function objdata = iAssignWithExplicitPermute(objdata, objViewToData, rhsdata, rhsViewToData, args)

% Apply view permutation
objdata = permute(objdata, objViewToData);

if ~isempty(rhsViewToData)
    rhsdata = permute(rhsdata, rhsViewToData);
end

% Apply indexing to the data
objdata(args{:}) = rhsdata;
end


function [objdata, success] = iAssignScalarUsingLogicalIndex(objdata, objViewToData, rhsdata, logInd)

if ~isa(logInd, 'dlarray') || ~isequal(objViewToData, logInd.ViewToDataMap)
    % Could support this case by doing one permutation instead of two in
    % the explicit case, but it's not clear this is worthwhile.
    success = false;
    return
end

[~, logIndData] = extractData(logInd.FormattedData);

% Apply indexing to the data (this never fails, since we have already
% checked that the size of objdata and of logIndData are the same).
objdata(logIndData) = rhsdata;
success = true;
end


function [objdata, objViewToData, success] = iAssignScalarNdSubscripts(objdata, objViewToData, rhsdata, args)

% Extend args and objViewToData to have matching lengths:
maxlen = max(length(args), length(objViewToData));
args(end+1:maxlen) = {1};
objViewToData(end+1:maxlen) = length(objViewToData)+1:maxlen;

args(objViewToData) = args;

% Apply indexing to the data
try
    objdata(args{:}) = rhsdata;
    success = true;
catch
    success = false;
    return
end
end


function [objdata, objViewToData, success] = iAssignNonScalarNdSubscripts(objdata, objViewToData, rhsdata, rhsViewToData, args)    

    % Extend objViewToData, rhsViewToData and args to have matching
    % lengths.
    maxlen = max(max(length(args), length(objViewToData)), length(rhsViewToData));
    objViewToData(end+1:maxlen) = length(objViewToData)+1:maxlen;
    rhsViewToData(end+1:maxlen) = length(rhsViewToData)+1:maxlen;
    args(end+1:maxlen) = {1};

    % Find out if each dimension of the indexed espression on the left-hand
    % side is a singleton
    isScalarIndex = false(1, length(args));
    for ii=1:length(args)
        argi = args{ii};
        if iIsRecordingArray(argi)
            argi = getValue(argi);
        end
        if isnumeric(argi)
            isScalarIndex(ii) = isscalar(argi);
        elseif islogical(argi)
            isScalarIndex(ii) = nnz(argi) == 1;
        elseif ischar(argi) && isequal(argi, ':')
            isScalarIndex(ii) = size(objdata, objViewToData(ii)) == 1;
        else
            % Potential other index falls back to explicit permute
            success = false;
            return
        end
    end
    
    % Size of the right-hand side in outside view
    if isempty(rhsViewToData)
        szRHS = size(rhsdata, 1:maxlen);
    else
        szRHS = size(rhsdata, rhsViewToData);
    end
    
    % Assignment matches nonsingleton dimensions - the placement of the
    % singular dimensions in the left-hand side and right-hand side must match.
    % Otherwise, fall back to the explicitly permuted case.
    if ~isequal(isScalarIndex, szRHS == 1)
        success = false;
        return
    end
    
    % Adjust args to data permutation.
    args(objViewToData) = args;
    
    % Construct combination of the 4 permutation vectors above. Applying
    % totalPermutation to rhsdata is equivalent to the following two
    % operations:
    % 1) Apply explicit permutation to rhs, if there was one. Permute with
    % rhsViewToData.
    % 2) Permute rhs to match underlying permutation of obj. Inverse
    % permutation with objViewToData.
    totalPermutation(objViewToData) = rhsViewToData;

    rhsdata = permute(rhsdata, totalPermutation);
    
    % Apply indexing to the data
    try
        objdata(args{:}) = rhsdata;
        success = true;
    catch
        success = false;
        return
    end
end


function [rhsdata, viewToData] = iGetRHS(rhs)
if isa(rhs, 'dlarray')
    [~, rhsdata] = extractData(rhs.FormattedData);
    viewToData = rhs.ViewToDataMap;
else
    rhsdata = rhs;
    if ~isobject(rhs) || ~isa(rhs, 'missing')
        deep.internal.dlarray.validateNonDlarray(rhsdata);
    end
    viewToData = [];
end
end

function [objdata, subs] = iRemoveDlarray(objdata, subs)
% Convert dlarray subscripts into RecordingArray or CPU/GPU array
% If any subscripts are recording then make objdata recording
isObjRecording = iIsRecordingArray(objdata);
for i=1:length(subs)
    sub = subs{i};
    if iIsDlarray(sub)
        recordingSub = getRecorded(sub);
        if iIsRecordingArray(recordingSub)
            subs{i} = iGetPermutedRecordingArray(sub,recordingSub);
            if ~isObjRecording
                objdata = deep.internal.recording.RecordingArray(objdata);
                isObjRecording = true;
            end
        else
            subs{i} = extractdata(sub);
        end
    end
end
end

function recordingSub = iGetPermutedRecordingArray(sub,recordingSub)
[~,externalPermutation] = getRecordingPermutations(sub);
if ~isempty(externalPermutation)
    recordingSub = permute(recordingSub,externalPermutation);
end
end

function tf = iIsRecordingArray(x)
tf = isa(x,'deep.internal.recording.RecordingArray');
end

function tf = iIsDlarray(x)
tf = isa(x,'dlarray');
end
