function obj = parenReference(obj,varargin)
% Implement obj(indices)

%   Copyright 2019-2022 The MathWorks, Inc.

dimsobj = dims(obj);
if ~isempty(dimsobj) && 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

[fd, objdata] = extractData(obj.FormattedData);
viewToData = obj.ViewToDataMap;

% Remove dlarray subscripts
[objdata, varargin] = iRemoveDlarray(objdata,varargin);

if isempty(viewToData)
    % Apply indexing to underlying data directly
    objdata = objdata(varargin{:});

elseif numel(varargin) >= numel(viewToData)
    % Possible to permute varargin to match the internal permutation
    [objdata, viewToData] = iPermuteSubscriptsRef(objdata, viewToData, varargin);
    
elseif isscalar(varargin) && isnumeric(varargin{1}) && ~iIsRecordingArray(varargin{1}) && ...
        numel(varargin{1}) < numel(objdata)/10 && ...
        all(varargin{1} <= numel(objdata), 'all')
    % Linear indexing case, translate the index if it is short enough
    % Heuristic: Transforming the index is only faster if the index
    % array is smaller than obj by a significant number.
    [objdata, viewToData] = iLinearNumericIndexRef(objdata, viewToData, varargin{1});

else
    % No special case applied. Fall back on explicitly permuting the input
    % dlarray.
    [objdata, viewToData] = iExplicitlyPermuteRef(objdata, viewToData, varargin);
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, viewToData] = addOrDropDimensionLabels(dimsobj, objdata, viewToData);
fd.DimensionMetadata = dimsobj;

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

% Set implicit permutations
obj.ViewToDataMap = viewToData;

end


function [data, viewToData] = iExplicitlyPermuteRef(data, viewToData, args)
% Permute obj explicitly, then just flow through to indexing data. This
% assumes viewToData is not empty.

data = permute(data, viewToData);
    
% Remove all information related to permutation
viewToData = [];

% Apply indexing to underlying data
data = data(args{:});
end


function [data, viewToData] = iPermuteSubscriptsRef(data, viewToData, args)
% Permute the subcripts to match the underlying data.

% Adjust length of viewToData to be at least as long as args cell array
lenDtoV = length(viewToData);
lenArgs = length(args);
viewToData(lenDtoV+1:lenArgs) = lenDtoV+1:lenArgs;

% Permute arguments to internal order
args(viewToData) = args;

% Index into data
data = data(args{:});
end


function [data, viewToData] = iLinearNumericIndexRef(data, viewToData, ind)
% Apply a linear index in obj's view to underlying data by translating the
% index (only efficient if numel(x) is significantly smaller than numel(obj)).

% Size of object seen from the outside (may have additional trailing
% singleton dimensions)
szobj = size(data, viewToData);

% Convert ind to a 1-based double array
if isobject(ind)
    ind = subsindex(ind) + 1;
end
ind = double(ind);

% Translate outside linear index to linear index into underlying data
dataToView(viewToData) = 1:length(viewToData);
ind = deep.internal.dlarray.translateLinearIndicesToNewView(ind, szobj, dataToView);
% Apply subsref to underlying data
data = data(ind);

% If both obj and ind are "ND-vectors", their direction depends on the size
% of the array being indexed into. Need to adjust for internal permutation
% here.
if nnz(szobj ~= 1) == 1 && nnz(size(ind) ~= 1) == 1
    szobj(szobj ~= 1) = numel(data);
    
    data = reshape(data, szobj);
end

viewToData = [];
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
