function out = validatestring(str,validstrings,funcname,varname,argindex)
%MATLAB Code Generation Library Function

%   Limitations:
%   1. The 3rd argument must be supplied and must be FUNCNAME.
%   2. Does not throw as caller.
%   3. VALIDSTRINGS, FUNCNAME, VARNAME, and ARGINDEX must be constants.
%      Use coder.unroll if processing multiple inputs in a loop.
%   4. Does not support partial matching when STR is a subset of multiple
%      valid strings, regardless of whether those strings form a sequence
%      where each is a subset of the next.
%   Copyright 1993-2021 The MathWorks, Inc.
%#codegen
%#ok<*EMCA>

eml_invariant(nargin >= 2,'MATLAB:narginchk:notEnoughInputs');
coder.internal.prefer_const(str,validstrings);
% NOTE: the text of the following errors will need to be updated if
% validatestring begins accepting string arrays.
% Alternatively, we would be able to begin using MATLAB
% error messages from MATLAB:validatestring.
coder.internal.assert(isstring(validstrings) || iscellstr(validstrings), ...
    'Coder:toolbox:ValidatestringInvalidStringList');
coder.internal.assert((coder.internal.isConst(validstrings) && isstring(validstrings)) || ...
    isconstcell(validstrings), ...
    'Coder:toolbox:ValidatestringNeedConstStrings');
coder.internal.assert(~isempty(validstrings), ...
    'Coder:toolbox:ValidatestringNeedConstStrings');
coder.internal.assert(nargin >= 3 && coder.internal.isCharOrScalarString(funcname), ...
    'Coder:toolbox:ValidatestringNeedFunctionName');
coder.internal.prefer_const(funcname);
coder.internal.assert(eml_is_const(funcname), ...
    'Coder:toolbox:ValidatestringNeedConstFunctionName');
coder.internal.assert(~ischar(funcname) || ...
    (isempty(funcname) || isrow(funcname)), ...
    'Coder:toolbox:ValidatestringBadFunctionName');
if nargin < 4 || ...
        coder.internal.isConstTrue(isempty(varname))
    varname = '';
else
    coder.internal.prefer_const(varname);
    coder.internal.assert(eml_is_const(varname), ...
        'Coder:toolbox:ValidatestringNeedConstVariableName');
    coder.internal.assert(isstring(varname) || (ischar(varname) && isrow(varname)), ...
        'Coder:toolbox:ValidatestringBadVariableName');
end
if nargin < 5
    argindex = 0;
else
    coder.internal.prefer_const(argindex);
    coder.internal.assert(eml_is_const(argindex), ...
        'Coder:toolbox:ValidatestringNeedConstArgumentPosition');
    coder.internal.assert(coder.internal.isPosIntScalar(argindex), ...
        'Coder:toolbox:ValidatestringBadArgumentPosition');
end
if isstring(validstrings)
    validstringsC = coder.const(cellstr(validstrings));
else
    validstringsC = validstrings;
end
if isstring(str)
    strChar = char(str);
else
    strChar = str;
end
if isstring(funcname) || isstring(varname)
    outC = fullValidatestring(strChar,validstringsC,coder.const(char(funcname)),coder.const(char(varname)),argindex);
    if isstring(validstrings)
        out = string(outC);
    else
        out = outC;
    end
else
    out = fullValidatestring(strChar,validstringsC,funcname,varname,argindex);
end


%--------------------------------------------------------------------------

function out = fullValidatestring(str,validstrings,funcname,varname,argindex)
if isempty(funcname)
    repIDprefix = 'MATLAB:';
else
    repIDprefix = ['MATLAB:', coder.const(feval('replace',funcname,'.',':')), ':'];
end
coder.internal.assert(ischar(str),...
    'CatalogID', 'Coder:toolbox:ValidatestringUnrecogonizedStringChoiceExpectedChar',...
    'ReportedID', [repIDprefix, 'unrecognizedStringChoice'], ...
    make_var_string(varname, argindex),...
    coder.internal.makeValidStringsString(validstrings),...
    make_input_string([]),...
    class(str));

coder.internal.assert(isrow(str) || strcmp(str,''),...
    'CatalogID', 'Coder:toolbox:ValidatestringUnrecognizedStringChoiceExpectedRow',...
    'ReportedID', [repIDprefix, 'unrecognizedStringChoice'], ...
    make_var_string(varname, argindex),...
    coder.internal.makeValidStringsString(validstrings),...
    make_input_string(str));

% Define a result for stand-alone targets when there is no match and no
% error checking. We make it the same length as validstrings{1} so that if
% all validstrings are the same length, the output of validatestrings can
% be fixed-size.
noMatch = blanks(strlength(validstrings{1}));

% Ensure that a constant string returns a constant
if coder.internal.isConst(str)
    [partial_match,nmatched] = coder.const(@get_match,str,validstrings,noMatch);
else
    [partial_match,nmatched] = get_match(str,validstrings,noMatch);
end

if nmatched == 0 || isempty(str) ~= isempty(partial_match)
    % There were no true matches. The case where nmatched == 0 is clear
    % enough, but the empty cases merit some comments.
    % * If str is empty and there is an empty string in validstrings, then
    %   nmatched > 0 && isempty(str) == isempty(partial_match), so
    %   execution does not flow here.
    % * If nmatched > 0, then str can be empty while partial_match is not
    %   since strncmpi(str,partial_match,0) returns true. We consider this
    %   a spurious partial match.
    % * If str is non-empty and partial_match is empty, then nmatched = 0.
    %   There is no value in changing the condition to avoid the redundancy
    %   in this case.
    out = noMatch;
    coder.internal.errorIf(nmatched == 0 || isempty(str) ~= isempty(partial_match),...
        'CatalogID', 'Coder:toolbox:ValidatestringUnrecognizedStringChoice',...
        'ReportedID', [repIDprefix, 'unrecognizedStringChoice'], ...
        make_var_string(varname, argindex),...
        coder.internal.makeValidStringsString(validstrings),...
        make_input_string(str));
elseif nmatched > 1
    out = partial_match;
    coder.internal.errorIf(nmatched > 1,...
        'CatalogID', 'Coder:toolbox:ValidatestringAmbiguousStringChoice',...
        'ReportedID', [repIDprefix, 'ambiguousStringChoice'], ...
        make_var_string(varname, argindex),...
        coder.internal.makeValidStringsString(validstrings),...
        make_input_string(str));
else
    out = partial_match;
end

%--------------------------------------------------------------------------

function [match, nmatched] = get_match(str,validstrings,noMatch)
coder.internal.prefer_const(str,validstrings,noMatch);
if ~eml_is_const(str)
    % When str is not a constant, the compiler may complain that
    % partial_match is not defined on all execution paths.  However, adding
    % these declarations when str is constant may prevent constant-folding
    % of the output.
    partial_match = noMatch;
end

n = coder.internal.indexInt(numel(validstrings));
lenstr = strlength(str);
nmatched = coder.internal.indexInt(0);
matched = false;
for k = coder.unroll(1:n)
    vstr = validstrings{k};
    lenvstr = strlength(vstr);
    if lenstr <= lenvstr && strncmpi(str,vstr,lenstr)
        if lenstr == lenvstr
            % Exact match.
            nmatched(1) = 1;
            match = vstr;
            return
        end
        if ~matched
            partial_match = vstr;
        end
        matched = true;
        nmatched = nmatched + 1;
    end
end
if nmatched == 0
    match = noMatch;
else
    match = partial_match;
end

%--------------------------------------------------------------------------

function inputstr = make_input_string(str)
% If str is an ordinary char row vector, surround it with quotes and
% commas. Otherwise, return an empty row string.
coder.internal.prefer_const(str);
if ischar(str) && eml_const(eml_is_const(isrow(str)) && isrow(str))
    inputstr = [', ''',str,''','];
else
    inputstr = char(zeros(1,0));
end

%--------------------------------------------------------------------------

function p = iscellstr(x)
p = isa(x,'cell');
if p
    for k = coder.unroll(1:numel(x))
        xk = x{k};
        if ~(ischar(xk) && eml_is_const(isrow(xk)) && isrow(xk))
            p = false;
            return
        end
    end
end

%--------------------------------------------------------------------------

function p = isconstcell(x)
coder.internal.prefer_const(x);
p = isa(x,'cell');
if p
    for k = coder.unroll(1:numel(x))
        xk = x{k};
        if ~eml_is_const(xk)
            p = false;
            return
        end
    end
end

%--------------------------------------------------------------------------

function s = make_var_string(varname,argindex)
coder.extrinsic('num2str');
coder.internal.prefer_const(varname,argindex);
if coder.const(isempty(varname))
    if coder.const(argindex < 1)
        s = 'input';
    else
        s = coder.const(['input number ',coder.const(num2str(argindex))]);
    end
elseif coder.const(argindex < 1)
    s = coder.const(varname);
else
    s = coder.const(['input number ',coder.const(num2str(argindex)),', ',varname,',']);
end

%--------------------------------------------------------------------------
