function varargout = mdbfileonpath(inFilename, ~)
    %MDBFILEONPATH Helper function for the Editor/Debugger
    %   MDBFILEONPATH is passed a string containing an absolute filename of an
    %   file.
    %   It returns:
    %      a filename:
    %         the filename that will be run (may be a shadower)
    %         if file not found on the path and isn't shadowed, returns the
    %         filename passed in
    %      an integer defined in FilePathState
    %      describing the status:
    %         FILE_NOT_ON_PATH - file not on the path or error occurred
    %         FILE_WILL_RUN - file is the one MATLAB will run (or is shadowed by a newer
    %         p-file)
    %         FILE_SHADOWED_BY_PWD - file is shadowed by another file in the current directory
    %         FILE_SHADOWED_BY_TBX - file is shadowed by another file somewhere in the MATLAB path
    %         FILE_SHADOWED_BY_PFILE - file is shadowed by a p-file in the same directory
    %         FILE_SHADOWED_BY_MEXFILE - file is shadowed by a mex, mdl, or slx file in the same directory
    %         FILE_SHADOWED_BY_MLX - file is shadowed by a mlx file file somewhere in the MATLAB path
    %         FILE_SHADOWED_BY_MLAPP - file is shadowed by a mlapp file somewhere in the MATLAB path
    %
    %   inFilename should be an absolute filename with extension ".m" (no
    %   checking is done).
    %
    %   This file is for internal use only and is subject to change without
    %   notice.
    
    %   Copyright 1984-2022 The MathWorks, Inc.
    
    if nargin > 0
        try
            % Purposefully not canonicalizing the filename to ensure that
            % case sensitivity of MATLAB is handled correctly.
            [~, fn] = fileparts(inFilename);
            [canonicalPath, isValid] = canonicalizePath(inFilename);
            if ~isValid
                varargout{1} = inFilename;
                varargout{2} = double(FilePathState.FILE_NOT_ON_PATH);
                return
            end
            [path, ~, ext] = fileparts(canonicalPath);
            processedFilename = fullfile(path, [fn ext]);
            
            fileTypes = {
                {mexext, ... Mex file type
                @()isNativeMexFile(processedFilename), ...
                @(path)getMexShadowStatus(path)}, ...
                {'slx', ... Simulink model .slx file type
                @()isMdlFile(processedFilename), ...
                @(path)getMexShadowStatus(path)}, ...
                {'mdl', ... Simulink model .mdl file type
                @()isMdlFile(processedFilename), ...
                @(path)getMexShadowStatus(path)}, ...
                {'mlx', ... MLX file type
                @()isMlxFile(processedFilename), ...
                @(path)getMlxFileShadowStatus(path)}, ...
                {'mlapp', ... MLAPP file type
                @()isMlappFile(processedFilename), ...
                @(path)getMlappFileShadowStatus(path)}, ...
                {'p', ... P file type
                @()isPFile(processedFilename), ...
                @(path)getPFileShadowStatus(path, processedFilename)}};
            
            isShadowedByFileinSameFolder = false;
            % For each possible shadowing file type (in precedence order)
            for i=1:numel(fileTypes)
                fileTypeEntry = fileTypes{i};
                pathWithExt = fullfile(path, [fn '.' fileTypeEntry{1}]);
                % If this matches the current file, stop searching.
                % Any other file extensions could not shadow this file.
                if (fileTypeEntry{2}())
                    break;
                elseif doesFileExist(pathWithExt)
                    % If a file exists with the current extension, then it potentially shadows it
                    % P-coded files are the one exception
                    isShadowedByFileinSameFolder = true;
                    fh = fileTypeEntry{3};
                    [shadowStatus, fullpath] = fh(pathWithExt);
                    break;
                end
            end
            
            %If there are no files in the same directory search elsewhere on the path.
            if ~isShadowedByFileinSameFolder
                [shadowStatus, fullpath] = checkIfShadowed(processedFilename);
            end

            if shadowStatus == FilePathState.FILE_NOT_ON_PATH           
                %If the file is not on the path, then the original file must be returned.
                varargout{1} = inFilename;
            else
                varargout{1} = fullpath;
            end
            varargout{2} = double(shadowStatus);
        catch
            varargout{1} = inFilename;
            varargout{2} = double(FilePathState.FILE_NOT_ON_PATH);
        end
    else
        varargout{1} = '';
        varargout{2} = double(FilePathState.FILE_NOT_ON_PATH);
    end
end

function [shadowStatus, fullpath] = getMexShadowStatus(fullpath)
    % Mex shadow status returned for mex, mdl and slx.
    shadowStatus = FilePathState.FILE_SHADOWED_BY_MEXFILE;
end

function [shadowStatus, fullpath] = getPFileShadowStatus(fullpath, inFilename)
    % Check if there is a p-file shadowing this file in the same
    % directory.  If there is (and if the ".m" file is newer), then
    % report that the p-file is shadowing it.
    mdirInfo = dir(inFilename);
    pdirInfo = dir(fullpath);
    % If the p-file is newer than the ".m" file, assume that it's OK
    if (pdirInfo.datenum >= mdirInfo.datenum)
        [shadowStatus, fullpath] = checkIfShadowed(internal.matlab.desktop.editor.unmapFilePath(fullpath));
    else
        shadowStatus = FilePathState.FILE_SHADOWED_BY_PFILE;
    end
end

function [shadowStatus, fullpath] = getMlxFileShadowStatus(fullpath)
    % Returning 'shadowed by MLX file' since this function is only
    % called if an MLX file exists.
    shadowStatus = FilePathState.FILE_SHADOWED_BY_MLXFILE;
end

function [shadowStatus, fullpath] = getMlappFileShadowStatus(fullpath)
    % Returning 'shadowed by MLAPP file' since this function is only
    % called if an MLAPP file exists.
    shadowStatus = FilePathState.FILE_SHADOWED_BY_MLAPPFILE;
end

function [shadowStatus, fullpath] = checkIfShadowed(inFilename)
    [path, fn, ext] = filepartsWithoutPackages(inFilename);
    xfiletorun = getFileToRun(inFilename);
    if ~isempty(xfiletorun)
        [xpath, xfn, xext] = filepartsWithoutPackages(xfiletorun);
        
        arePathsEqual = areDirectoriesEqual(xpath, path);
        areFileNamesEqual = areFilenamesEqualForRun(xfn, fn);
        areExtensionsEqual = areFilenamesEqualForRun(xext, ext);
        isExactFileNameMatch = areFileNamesEqual && areExtensionsEqual;
        areExtensionsCompatible = isStandardExtension(ext) && isStandardExtension(xext);
        
        areCompatibleFiles = areFileNamesEqual && areExtensionsCompatible;
        isPossibleShadow = ~arePathsEqual;
        
        %File will only run if the paths, the filename, and the extension match.
        if ~isPossibleShadow && isExactFileNameMatch
            % The executable fileparts are identical to the file passed in
            fullpath = xfiletorun;
            % MATLAB will run the file
            shadowStatus = FilePathState.FILE_WILL_RUN;
            
            %A file is shadowed only if the path are not equal, but the filename and extensions are compatible.
        elseif isPossibleShadow && areCompatibleFiles
            fullpath = xfiletorun;
            if areDirectoriesEqual(xpath, pwd)
                shadowStatus = FilePathState.FILE_SHADOWED_BY_PWD;
            else
                % shadower on path
                shadowStatus = FilePathState.FILE_SHADOWED_BY_TBX;
            end
            
            %All other cases denote file that will not run.
            %The exception are files that will not run ever: g1564802.
        else
            fullpath = inFilename;
            shadowStatus = FilePathState.FILE_NOT_ON_PATH;
        end
    else
        fullpath = inFilename;
        shadowStatus = FilePathState.FILE_NOT_ON_PATH;
    end
end

%---------------------------------------------------------------------
% Like fileparts, but removes package ("\+foo") directories
function [outpath, outfile, outext] = filepartsWithoutPackages(inpath)
    [outpath, outfile, outext] = fileparts(inpath);
    outpath = removePackageDirs(outpath);
end

%---------------------------------------------------------------------
% Strip away the package directories from the given directory.
function result = removePackageDirs(inDir)
    result = inDir;
    % Make sure that result ends in a file separator so that the last item
    % is treated as a directory rather than a file. However, we don't wish
    % to return a value ending in a file separator from the method.
    while (~isempty(result) && isPackageDirectory(strcat(result, filesep)))
        result = fileparts(result);
    end
end

%---------------------------------------------------------------------
% Test if two strings containing directories are equal.  This takes
% platform considerations into account.
function pathsAreEqual = areDirectoriesEqual(path1, path2)
    pathsAreEqual = strcmp(canonicalizePath(path1), canonicalizePath(path2));
end

%---------------------------------------------------------------------
% Test if two strings containing filenames are equal.  This is a case sensitive match because even though windows file
% system is case insensitive, MATLAB still uses case to determine what to run.
function namesAreEqual = areFilenamesEqualForRun(name1, name2)
    try
        namesAreEqual = strcmp(name1, name2);
    catch
        namesAreEqual = false;
    end
end

%---------------------------------------------------------------------
% Test if two strings containing filenames are equal.  This is a case sensitive match because even though windows file
% system is case insensitive, MATLAB still uses case to determine what to run.
function extAreStandard = isStandardExtension(ext)
    extAreStandard = any(areFilenamesEqualForRun(ext, {'.m', '.mlx', '.mlapp', '.p'}));
end

%---------------------------------------------------------------------
function fn = getFileToRun(inFilename)
    % Return a string containing the absolute path of the file that
    % MATLAB will run based on the input filename (e.g., foo)
    
    if isFileInPackage(inFilename)
        % 1) For MCOS files, we need to determine whether the parent
        %    directory for the package is on the path. If the parent
        %    directory is not on the path, then return the empty string.
        parentPath = getValidPathEntryParent(inFilename);
        if ~isDirectoryOnPath(parentPath)
            fn = '';
            return;
        end
        
        % 2) Next, determine what which thinks is the full path to the
        %    class or method that we're trying to set a breakpoint in.
        %    Then, look to see if the result of which is on the path (it
        %    might not be if we're inside the class or package directory).
        whichResult = which(trimToMcosPath(inFilename));
        whichParentPath = getValidPathEntryParent(whichResult);
        if isDirectoryOnPath(whichParentPath)
            fn = whichResult;
            return;
        end
        
        % 3) Finally, if the given file is on the path, and not found by
        %    which, simply return the given file.
        fn = inFilename;
    elseif isPrivate(inFilename)
        % For files in a private use absolute path
        fn = which(inFilename);
    else  % make the variable names somewhat obscure -- geck 281208
        [~, fileparts_Filename_Var] = fileparts(inFilename);
        fn = which(fileparts_Filename_Var);
        % correct returned built-ins to point to their matching file for the purposes of command-line help -- geck 376452
        if ( 1 ==strfind( fn, 'built-in (' ) )
            fn = fn(length('built-in (')+1:length(fn)-1); % e.g. matlabroot '/toolbox/matlab/ops/@single/plus'
            fn = [fn '.m'];
        end
    end
end


%---------------------------------------------------------------------
function result = trimToMcosPath(filepath)
    % Trims away the part of the path that is not related to the MCOS name.

    if isempty(filepath)
        result = '';
        return;
    end

    [directoryName, filename] = fileparts(filepath);
    directoryName = [directoryName filesep];
    
    if ~isPackageDirectory(directoryName) && ~isObject(directoryName)
        result = filename;
        return;
    end

    firstSeparator = regexp(directoryName, regexptranslate('escape', [filesep '+']), 'once');
    if isempty(firstSeparator)
        firstSeparator = regexp(directoryName, regexptranslate('escape', [filesep '@']), 'once');
    end

    assert(~isempty(firstSeparator), 'Didn''t find package directory or object despite checking earlier');
    
    result = filepath(firstSeparator + 1:end);
end


%---------------------------------------------------------------------
% Does the given file live in a private directory? Argument must represent
% a file and therefore not end in a file separator.
function result = isPrivate(inFile)
    assert(~isdir(inFile), ['argument must be a file, not a directory: ' inFile]);
    
    inDir = fileparts(inFile);
    
    if isempty(inDir)
        result = false;
        return;
    end
    
    %Remove final slash if present
    if isequal(inDir(end), filesep)
        inDir = inDir(1:end - 1);
    end
    
    result = endsWith(inDir, [filesep 'private']);
end

%---------------------------------------------------------------------
% Does the given file live in an MCOS package? Argument must not end in a
% file separator.
function result = isFileInPackage(inFile)
    assert(~isdir(inFile), ['argument must be a file, not a directory: ' inFile]);
    result = isPackageDirectory(fileparts(inFile));
end

%---------------------------------------------------------------------
% Does the given file live in an MCOS class directory? Argument must
% represent a file and therefore not end in a file separator.
function result = isObject(inDirectory)
    result = contains(inDirectory, [filesep '@']);
end

%---------------------------------------------------------------------
% Does the given directory represent an MCOS package? See comments in
% MatlabPath.isPackage. Argument must end in a file separator.
function result = isPackageDirectory(inDir)
    %Directory must be a folder that is on the path
    if isempty(inDir) || isdir(inDir) == 0
        result = false;
    else        
        %Remove final slash if present
        if isequal(inDir(end), filesep)
            inDir = inDir(1:end - 1);
        end
        
        parts = strsplit(inDir, filesep);
        
        %Determine final folder name and parent's name, if it exists.
        baseName = parts{end};
        if numel(parts) ~= 1
            parent = parts{end - 1};
        else
            parent = '';
        end
        
        %A folder is a package if it begins with a + or it is a class folder whose parent begins with a +.
        % Any drives or unc paths will not parse correctly, but will also not start with a + so it is okay.
        result = startsWith(baseName, '+') || (startsWith(parent, '+') && startsWith(baseName, '@'));
    end
end

%---------------------------------------------------------------------
% This returns true only if inFilename's case matches what is on disk.
% This function is used instead of the 'exist' function because we want to
% differentiate files 'FOO.m' and 'foo.m' when on UNIX.  The 'exist'
% function will return 2 regardless of the case of the string that is
% passed in.
function existsOnDisk = doesFileExist(inFilename)
    % Canonicalize path throws an error if the file in question does not exist.
    % It correctly handles case sensitivity.
    [~, existsOnDisk] = canonicalizePath(inFilename);
end

function isOnPath = isDirectoryOnPath(directory)
    % returns true if the given directory is on the path (including pwd).
    
    assert(exist(directory, 'dir') == 7);
    
    % determine if the directory is on the path, either implicitly
    % by being on pwd, or explicitly by being on the actual path.
    cellOfPathEntries = regexp(path, pathsep, 'split');
    cellOfPathEntries{end+1} = pwd;
    isOnPath = ~isempty(find(doPlatformBasedPathComparison(cellOfPathEntries, directory), 1));
    
end

function areEqual = doPlatformBasedPathComparison(cellOfPathEntries, directory)
    % returns true if the given directory is contained within the given cell
    % array of path entries. this function takes into account the fact that the
    % path is case-sensitive on Mac and Linux, while case-insensitive on
    % Windows.
    if ispc
        areEqual = strcmpi(cellOfPathEntries, directory);
    else
        areEqual = strcmp(cellOfPathEntries, directory);
    end
end

function flag = isNativeMexFile(filename)
    flag = endsWith(filename, ['.' mexext]);
end

function flag = isMdlFile(filename)
    flag = endsWith(filename, {'.mdl', '.slx'}, 'IgnoreCase', true);
end

function flag = isMlxFile(filename)
    flag = endsWith(filename, '.mlx', 'IgnoreCase', true);
end

function flag = isMlappFile(filename)
    flag = endsWith(filename, '.mlapp', 'IgnoreCase', true);
end

function flag = isPFile(filename)
    flag = endsWith(filename, '.p', 'IgnoreCase', true);
end

function validPathEntry = getValidPathEntryParent(inFilename)
    validPathEntry = fileparts(inFilename);
    % Make sure that result ends in a file separator so that the last item
    % is treated as a directory rather than a file. However, we don't wish
    % to return a value ending in a file separator from the method. We are usin
    % file parts to strip off one directory at a time.
    while ~isempty(validPathEntry) && ~isValidPathEntryParent([validPathEntry filesep])
        validPathEntry = fileparts(validPathEntry);
    end
end

function isValidPathEntry = isValidPathEntryParent(directoryName)
    isValidPathEntry = ~endsWith(directoryName, [filesep 'private']) && ~isPackageDirectory(directoryName) && ~isObject(directoryName);
end