function D = pBuildFromLikeFcn(fcn, varargin)
    ; %#ok<NOSEM> % Undocumented
%pBuildFromLikeFcn Hidden static method to build codistributed arrays using a 
%   'like' build function.  All errors are thrown as caller.
%   
%   See also codistributed/onesLike, codistributed/zerosLike.


%   Copyright 2013-2022 The MathWorks, Inc.

    % This error should never be triggered since this is a hidden function.
    narginchk(1, Inf);

    if ischar(fcn)
        buildFcnName = fcn;
        fcn = str2func(buildFcnName);
    else
        if ~isa(fcn,'function_handle')
            throwAsCaller(MException(message(...
                'parallel:distributed:CodistributedFunctionFunctionHandleInput')))
        end
        buildFcnName = func2str(fcn);
    end
    
    % Redirect to special case code for EYE
    if strcmp(buildFcnName,"eye")
        D = eyeLike(varargin{:});
        return
    end
    
    y = varargin{1};
    extractCodistrFcn = @distributedutil.CodistParser.extractCodistributorNoDefault;
    allowNegativeSizes = true;

    try
        [sizeVec, className, codistr, ~, limits] = codistributed.pParseBuildArgs( ...
            buildFcnName, ...
            allowNegativeSizes, ...
            varargin(2:end), ... % Ignore prototype (1st arg)
            extractCodistrFcn);

        % if output is to be codistributed, make sure we have a codistributor
        if isempty(codistr)
            codistr = getCodistributor(y);
        end
        % Get size of codistributor right
        codistr = codistr.hGetNewForSize(sizeVec);

        % Determine class of output
        if isempty(className)
            className = underlyingType(y);
            classFromPrototype = true;
        else
            classFromPrototype = false;
        end

        % Construct prototype
        % This call doesn't make sense if the prototype is invalid
        % for the type of data we are trying to construct.
        iCheckClassName(buildFcnName, className, classFromPrototype);
        proto = cast(1, className); % Prototype has non-zero value to prevent imag part being lost

        % It only makes sense to "complexify" a numeric, real array.
        if ~isreal(y) && isnumeric(proto)
            proto = complex(proto, proto);
        end

        % Sparsity only makes sense for numeric arrays.
        if issparse(y)
            try
                proto = sparse(proto);
            catch ME
                if strcmp(ME.identifier, 'MATLAB:UndefinedFunction')
                    ME = MException(message('MATLAB:unimplementedSparseType'));
                end
                throw(ME);
            end
            doUseSizeAsVector = false;
            if numel(sizeVec) > 2
                % No support for N-D sparse arrays
                error(message('parallel:array:UnsupportedNdSparse'));
            end
            if isempty(limits)
                buildFcn = @(sz1,sz2)fcn(sz1, sz2, 'like', proto);
            else
                buildFcn = @(sz1,sz2)fcn(limits, sz1, sz2, 'like', proto);
            end
        else
            doUseSizeAsVector = true;
            if isempty(limits)
                buildFcn = @(szVector)fcn(szVector, 'like', proto);
            else
                buildFcn = @(szVector)fcn(limits, szVector, 'like', proto);
            end
        end
        [LP, codistr] = codistr.hBuildFromFcnImpl(buildFcn, sizeVec, [], ...
                                                  doUseSizeAsVector);
    catch E
        throwAsCaller(E);
    end
    D = codistributed.pDoBuildFromLocalPart(LP, codistr);
end

function iCheckClassName(buildFcnName, className, classFromPrototype)
% Check that build function and 'like' class are consistent
    switch buildFcnName
        case {'inf', 'Inf', 'nan', 'NaN', 'rand', 'randn'}
            if ~strcmpi(className, {'single', 'double'})
                error(iGetTypeError(buildFcnName, classFromPrototype));
            end
        case {'true', 'false'}
            if ~strcmpi(className, 'logical')
                error(iGetTypeError(buildFcnName, classFromPrototype));
            end
        case {'ones', 'zeros', 'eye', 'randi'}
            if ~distributedutil.CodistParser.isValidNumericOrLogicalType(className)
                error(iGetTypeError(buildFcnName, classFromPrototype));
            end
        otherwise
            assert(false, 'Unsupported build function encountered.')
    end

end

function msg = iGetTypeError(buildFcnName, classFromPrototype)
% Get the right message ID to use for an incorrect 'like' type.
% Unfortunately this depends in the function name in a non-trivial way.

% First work out which catalog to use
switch buildFcnName
    case {'inf', 'Inf', 'true', 'false'}
        % These ones capitalize just the first letter
        catalogID = [upper(buildFcnName(1)), buildFcnName(2:end)];
    case {'nan','NaN'}
        % nan is special
        catalogID = 'NaN';
    otherwise
        % For everything else, assume the catalog ID is the name
        catalogID = buildFcnName;
end

% Now try to work out the message within the catalog
if classFromPrototype
    msgID = 'invalidInputClass';
else
    msgID = 'invalidStringInput';
end

switch buildFcnName
    case {'rand', 'randi', 'randn'}
        % The rand functions have their own conventions.
        if classFromPrototype
            msgID = 'invalidPrototype';
        else
            msgID = 'invalidOutputType';
        end
    case {'eye'}
        % Eye is the same as the rest for typename, but not for prototype
        if classFromPrototype
            msgID = 'invalidNonStringInput';
        end
    otherwise
        % Others use the default
end

% Build the final ID
msg = message(['MATLAB:',catalogID,':',msgID]);
end
