function [pathValue,replacedAll,undeclaredMacrosMsg,otherMsg] = replaceMacrosInPath(h,pathValue,tc)
% Replace all declared macros

%   Copyright 2014-2020 The MathWorks, Inc.
pathValue = convertStringsToChars(pathValue);

[macros,values,intrinsicMacros] = getAllKnownMacros( tc );
[pathValue,replacedAll,undeclaredMacrosMsg,otherMsg] = replaceKnownMacros( pathValue, macros, values, intrinsicMacros );
if ~replacedAll
    return
end

% Special macro: %MATLAB%
% If matlabroot will be mapped to a virtual drive, then don't use
% transformPaths as this will throw warnings on machines with no 8dot3
% support.
mapMlr = coder.make.internal.checkMLRDriveMap(tc);
if mapMlr
    pathValue = strrep(pathValue, '%MATLAB%', matlabroot);
else
    pathValue = strrep(pathValue, '%MATLAB%', coder.make.internal.transformPaths(matlabroot, 'pathType', 'alternate'));
end

% -------------------------------------------------------------
% Replace with declared macros
    function [pathValue,replacedAll,undeclaredMacrosMsg,otherMsg] = replaceKnownMacros(pathValue,macros,values,intrinsicMacros)
        undeclaredMacrosMsg = [  ];
        otherMsg = [  ];
        prev = getMacroRefsInPath( pathValue );
        now = {  };
        while ~isequal( prev, now )
            now = prev;
            for i = 1:numel( prev )
                idx = find( strcmp( macros, prev{ i }( 3:end - 1 ) ), 1 );
                if ~isempty( idx )
                    pathValue = strrep( pathValue, prev{ i }, values{ idx } );
                end
            end
            prev = getMacroRefsInPath( pathValue );
        end
        macrosInPath = getMacrosInPath( pathValue );
        replacedAll = isempty( macrosInPath );
        if ~replacedAll
            intrinsicMacrosFound = ismember( macrosInPath, intrinsicMacros );
            if all( intrinsicMacrosFound )
                % If path still contains unresolved macros but they
                % are intrinsic macros, DO NOT WARN.
                % If macro is declared as intrinsic, the tool or
                % user will declare this later, therefore, we have
                % a contract with the user not to throw an
                % unnecessary warning.
                otherMsg = message( 'coder_compile:toolchain:BuildToolPathFileContainsIntrinsicMacros', h.Name, pathValue );
            else
                % If path still contains macros and they are
                % undeclared macros, WARN.
                undeclaredMacrosMsg = message( 'coder_compile:toolchain:BuildToolPathFileContainsUndeclaredMacros', h.Name, pathValue );
            end
        end
    end
% -------------------------------------------------------------
% Helper: Get macros in path or filename
    function macros = getMacroRefsInPath(pathValue)
        macros = regexp( pathValue, '\$\(\w+\)', 'match' );
    end
% -------------------------------------------------------------
% Helper: Get macros in path or filename
    function macros = getMacrosInPath(pathValue)
        macros = regexp( pathValue, '\$\((\w+)\)', 'tokens' );
        if ~isempty( macros )
            macros = macros{ 1 };
        end
    end
% -------------------------------------------------------------
% Helper: Get all declared macros
    function [macros,values,intrinsicMacros] = getAllKnownMacros(tc)
        macros = tc.Macros.keys;
        values = tc.Macros.values;
        intrinsicMacros = getIntrinsicMacros( macros, values );
        % Remove the intrinsic macros from the list
        macroIdxs = ~cellfun( @isempty, values );
        macros = macros( macroIdxs );
        values = values( macroIdxs );
        % Add other known macros
        macros = horzcat( macros, { 'MATLAB_ROOT', 'MATLAB_BIN', 'MATLAB_ARCH_BIN' } );
        values = horzcat( values, { matlabroot, fullfile( matlabroot, 'bin' ), fullfile( matlabroot, 'bin', computer('arch') ) } );
    end
% -------------------------------------------------------------
% Helper: Get intrinsic macros
    function [intrinsicMacros,intrinsicMacroIdxs] = getIntrinsicMacros(macros,values)
        intrinsicMacroIdxs = cellfun( @isempty, values );
        intrinsicMacros = macros( intrinsicMacroIdxs );
    end
end
