function obj = cast(obj, flag, prototype)
%CAST Cast a dlarray to a different data type or class.
%   B = CAST(A, NEWDATATYPE) copies the data in dlarray A into a dlarray B
%   of underlying datatype NEWDATATYPE. NEWDATATYPE must be one of
%   'double', 'single', 'logical'. B has the same dimension labels as A.
%
%   B = CAST(A, 'like', Y) returns an array B of the same class as Y. If Y
%   is a dlarray, B is a dlarray with the same underlying datatype as Y. If
%   Y is on the GPU, B is on the GPU. If A and Y are both dlarrays, B has
%   the same dimension labels as A; otherwise, B has no dimension labels.
%
%   See also CAST, DLARRAY.

%   Copyright 2019-2021 The MathWorks, Inc.

% Check that the flag is a char or string array (if this was a dlarray
% it could lead to an infinite-loop otherwise).
if ~ischar(flag) && ~isstring(flag)
    if nargin > 2
        % Case cast(obj, 'like', prototype)
        error(message('MATLAB:cast:invalidString'))
    else
        % Case cast(obj, classname)
        error(message('MATLAB:cast:invalidSyntax'))
    end
end

if nargin > 2 % Case cast(obj, 'like', prototype)
    
    if isa(obj, 'dlarray')
        if isa(prototype, 'dlarray')
            [fdobj, objdata] = extractData(obj.FormattedData);
            if internal_isRecordingArray(objdata)
                % The output is only, and always, recorded when the obj input is recording.
                % Make sure the prototype is also a recording array so that the attribute
                % is not lost.
                fdproto = record(prototype.FormattedData);
                prototypedata = fdproto.Data;
            else
                % We don't include the recording state from the prototype in the output
                % dlarray.
                prototypedata = gatherData(prototype.FormattedData);
            end
            
            objdata = cast(objdata, flag, prototypedata);
            obj.FormattedData = insertData(fdobj, objdata);
        else
            % Need to do an explicit permutation, as the new class can't
            % represent that. Also, no tracing in this case.
            if isa(gatherData(obj.FormattedData), 'deep.internal.blank')
                error(message('deep:dlarray:BlankCastLike'))
            end
            objdata = extractdata(obj);   
            obj = cast(objdata, flag, prototype);
        end
    else % ~isa(obj, 'dlarray')
        % In this case, prototype must be a dlarray.
        
        % We don't include the recording state in the output dlarray.
        prototypedata = gatherData(prototype.FormattedData);
        
        objdata = cast(obj, flag, prototypedata);
        
        % Construct a new dlarray to wrap value
        obj = dlarray(objdata);
    end
    
else % Case cast(obj, newdatatype)
    
    [fdobj, objdata] = extractData(obj.FormattedData);
    
    % Apply internal function to the data (this does not support 'gpuArray', but
    % that's consistent with the non-dlarray case).
    objdata = cast(objdata, flag);
    
    % Check that the new type is supported in dlarray.
    iCheckValidInternalType(objdata);
        
    obj.FormattedData = insertData(fdobj, objdata);
end


function iCheckValidInternalType(obj)
    if internal_isRecordingArray(obj)
        obj = getValue(obj);
    end
    if isa(obj, 'deep.internal.blank')
        obj = obj.EmptyData;
    end
    deep.internal.dlarray.validateNonDlarray(obj);