function fOut = fullfile(varargin)
%MATLAB Code Generation Library Function

%   Limitations:
%
%   1. If one of the inputs is string, all other inputs must be scalar.

% Copyright 2023 The MathWorks, Inc.
%#codegen

narginchk(1, Inf);

containsCellOrStringInput = false;
containsStringInput = false;

numInputs = nargin;
theInputs = cell(1, 2*numInputs);

% perform check only on first input to obtain the correct filesep
[fs, containsIRI] = checkForIRIAndGetFilesep(varargin{1});

nonEmptyHit = false;
for i = coder.unroll(numInputs:-1:1)

    inputElement = varargin{i};
    containsCellOrStringInput = containsCellOrStringInput || iscell(inputElement);

    coder.internal.assert(ischar(inputElement) || iscell(inputElement) || ...
        isstring(inputElement) || (isnumeric(inputElement) && isreal(inputElement)), ...
        'MATLAB:fullfile:InvalidInputType');
    coder.internal.errorIf(ischar(inputElement) && ~isrow(inputElement) && ~isempty(inputElement), ...
        'MATLAB:fullfile:NumCharRowsExceeded');

    if i==1 && i~=numInputs
        % Add the trailing filesep to the first element only if there are
        % more than one inputs to the function
        if isstring(inputElement)
            containsStringInput = true;
            containsCellOrStringInput = true;
            charInput1 = convertStringsToChars(inputElement);
        elseif isnumeric(inputElement)
            charInput1 = char(inputElement);
        else
            charInput1 = inputElement;
        end
        if nonEmptyHit
            theInputs{1} = ensureTrailingFilesep(charInput1, fs);
        else
            theInputs{1} = charInput1;
        end
        theInputs{2} = '';
    else
        % cast all inputs to char
        if isstring(inputElement)
            containsStringInput = true;
            containsCellOrStringInput = true;
            theInputs{2*i-1} = convertStringsToChars(inputElement);
        elseif isnumeric(inputElement)
            theInputs{2*i-1} = char(inputElement);
        else
            theInputs{2*i-1} = inputElement;
        end
        if i~=numInputs && nonEmptyHit
            % Add trailing filesep to theInputs array
            theInputs{2*i} = fs;
        else
            theInputs{2*i} = '';
        end
        % update if this is first non empty element we hit from the end
        if ~isempty(inputElement)
            nonEmptyHit = true;
        end
    end

    % All inputs are converted to char vectors or cell array of char vectors
    coder.internal.assert(ischar(theInputs{2*i-1}) || iscell(theInputs{2*i-1}), ...
        'MATLAB:fullfile:InvalidInputType');

    % Recheck to ensure numeric inputs dont end up as 2D char matrices.
    % Add an exception if 2D numeric matrix is the only
    % input.(To match MATLAB behaviour)
    coder.internal.errorIf(nargin~=1 && ischar(theInputs{2*i-1}) && ~isrow(theInputs{2*i-1}) && ~isempty(theInputs{2*i-1}), ...
        'MATLAB:fullfile:InvalidInputType');
end

if coder.internal.isConstTrue(containsCellOrStringInput)
    % All cell arrays and string arrays must be of the same size.
    % Get the size of the cell that is fixed at compile time if any exist
    nonScalarCellInput = false;
    for argIdx = coder.unroll(1:2*numInputs)
        if iscell(theInputs{argIdx}) && coder.internal.isConstFalse(isscalar(theInputs{argIdx}))
            nonScalarCellInput = true;
            break;
        end
    end
    % conditional must be compile time const as it depends on iscell and
    % isscalar
    if coder.const(nonScalarCellInput)
        cellSz = size(theInputs{argIdx});
    else
        % no cells have fixed size at compile time, get the runtime size
        % for the first non scalar codertype
        cellSz = [1 1];
        for argIdx = coder.unroll(1:2*numInputs)
            if iscell(theInputs{argIdx}) && ~isscalar(theInputs{argIdx})
                cellSz = size(theInputs{argIdx});
                break;
            end
        end
    end
    sizeValidation(cellSz, theInputs);
else
    cellSz = 0;
end

if numInputs == 1
    if ~isnumeric(varargin{1}) % check class of actual input.
        if iscell(theInputs{1})
            f = cell(cellSz);
            for i = 1:numel(theInputs{1})
                f{i} = refinePath(theInputs{1}{i}, fs, ...
                    containsIRI);
            end
        else
            f = refinePath(theInputs{1}, fs, ...
                containsIRI);
        end
    else
        f = theInputs{1}; % already cast to char while preprocessing
    end
else
    if containsCellOrStringInput
        fTemp = strcat(theInputs{:});
    else
        fTemp = [theInputs{:}];
    end

    if iscell(fTemp)
        f = cell(cellSz);
        for i = 1:numel(f)
            f{i} = refinePath(fTemp{i}, fs, ...
                containsIRI);
        end
    else
        f = refinePath(fTemp, fs, ...
            containsIRI);
    end
end

if containsStringInput
    fOut = string(f);
else
    fOut = f;
end
end

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

function fOut = ensureTrailingFilesep(f, fs)
if iscell(f)
    fOut = cell(f);
    for i=1:numel(f)
        fOut{i} = addTrailingFileSep(f{i},fs);
    end
else
    fOut = addTrailingFileSep(f,fs);
end
end

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

function strOut = addTrailingFileSep(str, fs)
coder.inline('always');
if ~isempty(str) && (str(end) ~= fs && ~(ispc && str(end) == '/'))
    strOut = coder.internal.infer_or_error([str, fs], ...
        'MATLAB:fullfile:InvalidInputType'); % Throw same error as MATLAB
else
    strOut = str;
end
end

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

function fOut = refinePath(f, fs, isIRI)

if fs ~= '/'
    % explicit loop of strrep(f, '/', fs) for size inference
    for i = 1:numel(f)
        if f(i) == '/'
            f(i) = fs;
        end
    end
end

% Called even if pattern might not be present
% 'contains' needs to support multiple char vectors in cell arrays to be
% used here
ft = replaceSingleDots(f, fs);

% For non-IRIs, replace multiple file seperators
if ~isIRI
    fOut = replaceMultipleFileSeps(ft, fs);
else
    fOut = ft;
end

end

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

function fOut = replaceMultipleFileSeps(f,fs)
% Get rid of unnecessary multiple filesep.

% Note: This function is not meant to work with IRIs.
% codegen doesn't support IRI, this function will always be called
% on all inputs.

i = 1;
fTemp = blanks(numel(f));
k = 1;
if ispc
    if f(1) == '\'
        i = 2;
        k = 2;
        fTemp(1) = f(1);
        % elseif numel(f) >= 2 && isletter(f(1)) && f(2) == ':'
        %     i = 3;
    elseif numel(f) >= 4 && isequal(f(1:4), '\\?\')
        fOut = f;
        return;
    end
end

while(i <= numel(f))
    if f(i) == fs
        j = i + 1;
        while(j <= numel(f)) && (f(j) == fs)
            j = j + 1;
        end
        fTemp(k) = fs;
        i = j;
    else
        fTemp(k) = f(i);
        i = i + 1;
    end
    k = k + 1;
end

fOut = fTemp(1:k-1);
end

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

function fOut = replaceSingleDots(f,fs)
% Replace /./ with / greedily

fTemp = blanks(numel(f));
k = 1;
i = 1;
if ispc && (numel(f) >= 4)
    if isequal(f(1:4), '\\?\')
        fOut = f;
        return;
    end
    if isequal(f(1:4), '\\.\')
        fTemp(1:4) = f(1:4);
        i = 4;
        k = 4;
    end
end

while(i <= numel(f))
    if f(i) == fs
        j = i + 1;
        while(j < numel(f) && (f(j) == '.' && f(j+1) == fs) )
            j = j + 2;
        end
        fTemp(k) = f(i);
        i = j;
    else
        fTemp(k) = f(i);
        i = i + 1;
    end
    k = k + 1;
end

fOut = fTemp(1:k-1);
end

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

function sizeValidation(cellSz, theInputs)
% Ensure cell arrays(or string arrays) have equal size if more than one is present

coder.inline('always')
coder.internal.prefer_const(theInputs);

for argIdx = coder.unroll(1:numel(theInputs))
    currentArg = theInputs{argIdx};
    coder.internal.errorIf(iscell(currentArg) && ~(isequal(cellSz, size(currentArg)) || isscalar(currentArg)), ...
        'MATLAB:fullfile:CellstrSizeMismatch');
end

end

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

function [fs, containsIRI] = checkForIRIAndGetFilesep(inputElement)

coder.internal.assert(ischar(inputElement) || iscell(inputElement) || ...
    isstring(inputElement) || (isnumeric(inputElement) && isreal(inputElement)), ...
    'MATLAB:fullfile:InvalidInputType');
coder.internal.errorIf(ischar(inputElement) && ~isrow(inputElement) && ~isempty(inputElement), ...
    'MATLAB:fullfile:NumCharRowsExceeded');

if isstring(inputElement)
    charInput1 = convertStringsToChars(inputElement);
elseif isnumeric(inputElement)
    charInput1 = char(inputElement);
else
    charInput1 = inputElement;
end

% hasIRIprefix checks for IRI scheme using only the prefix
% should be sufficient for fullfile
isIRI = hasIRIPrefix(charInput1);

containsIRI = false;
if all(~isIRI)
    fs = filesep;
elseif all(isIRI)
    fs = '/';
    containsIRI = true;
else
    coder.internal.error('MATLAB:fullfile:MixedInputType');
end

end