function [success,warnStr,errorStr,reportStr] = privateValidateToolchain(h,setupToolchain,cleanupToolchain)

%   Copyright 2014-2020 The MathWorks, Inc.

narginchk( 3, 3 );
success = 1;
warnStr = '';
errorStr = '';
reportStr = '';
tb = sprintf( '\t' );

% Create TemporaryDirectory for validation
tempDirName = getTempDirNameAndPath();
mkdir(tempDirName);
initialPWD = pwd;
cd(tempDirName);
cleanupObj.a = onCleanup(@()cd(initialPWD));
cleanupObj.b = onCleanup(@()rmdir(tempDirName, 's'));
% -------------------------
% Validate platform
% -------------------------
msg = message( 'coder_compile:toolchain:ValidateToolchain_CheckingPlatform' );
platformMsg = msg.getString();
if isempty( h.Platform )
    msg = message( 'coder_compile:toolchain:UnspecifiedPlatform' );
    processOutput( true, '', [ platformMsg, tb, msg.getString(), newline ] );
elseif ~strcmpi( h.Platform, computer( 'arch' ) )
    msg = message( 'coder_compile:toolchain:RunningOnUnsupportedPlatform', h.Name, h.Platform, computer( 'arch' ) );
    processOutput( false, msg.getString(), [ platformMsg, tb, msg.getString(), newline ] );
end
% ------------------------------------
% Validate the writer & executor
% ------------------------------------
if isempty( which( h.BuildArtifactWriter ) )
    msg = message( 'coder_compile:toolchain:NonexistentWriter', h.BuildArtifactWriter );
    processOutput( false, msg.getString(), '' );
end
if isempty( which( h.BuildArtifactExecutor ) )
    msg = message( 'coder_compile:toolchain:NonexistentExecutor', h.BuildArtifactExecutor );
    processOutput( false, msg.getString(), '' );
end
% ------------------------------------
% Validate if toolchain is installed
% ------------------------------------
nonEmptyAlias = (ischar( h.Alias ) && ~isempty( h.Alias )) || (iscell( h.Alias ) && ~all( cellfun( @isempty, h.Alias ) ));  % non-empty cell array
% non-empty string
if nonEmptyAlias
    installed = coder.make.internal.isToolchainInstalled(h.Alias);
    if ~installed
        success = 0;
        msg = message( 'coder_compile:toolchain:ValidateToolchain_NotInstalled' );
        locErrorStr = [ msg.getString(), newline ];
        errorStr = [ errorStr, newline, locErrorStr ];
        reportStr = [ reportStr, newline, locErrorStr ];
        return
    end
end
% ------------------------------------
% Perform setup before tool validation
% ------------------------------------
if setupToolchain
    verbose = false;
    lBuildLogger = coder.make.BuildLogger(verbose, []);
    [toolSuccess,toolMsg,toolReport] = h.toolchainSetup(lBuildLogger, 'test', true );
    processOutput( toolSuccess, toolMsg, toolReport );
end
% ------------------------------------
%
% ------------------------------------
[toolSuccess,toolMsg,thisMsg] = h.validateSpecialLinkerProperties();
processOutput( toolSuccess, toolMsg, thisMsg );
% ------------------------------------
% Validate each tool
% ------------------------------------
excludedList = {  };
excludedToolList = validateEachTool( h.BuildTools );
excludedList = horzcat( excludedList, excludedToolList );
excludedToolList = validateEachTool( h.PrebuildTools );
excludedList = horzcat( excludedList, excludedToolList );
excludedToolList = validateEachTool( h.PostbuildTools );
excludedList = horzcat( excludedList, excludedToolList );
excludedToolList = validateEachTool( h.BuilderApplication );
excludedList = horzcat( excludedList, excludedToolList );
if ~isempty( excludedList )
    validateToolByBatch( excludedList );
end
% -------------------------
% Validate macros
% -------------------------
msg = message( 'coder_compile:toolchain:ValidateToolchain_CheckingMacros' );
macrosReport = msg.getString();
[~,thisMsg] = h.checkMacros();
if isempty( thisMsg )
    macrosReport = [ macrosReport, tb, 'Passed.', newline ];
else
    macrosReport = [ macrosReport, tb, thisMsg, newline ];
end
processOutput( true, thisMsg, macrosReport );
% ------------------------------------
% Perform setup before tool validation
% ------------------------------------
if cleanupToolchain
    verbose = false;
    lBuildLogger = coder.make.BuildLogger(verbose, []);
    [toolSuccess,toolMsg,toolReport] = h.toolchainCleanup(lBuildLogger, 'test', true);
    processOutput( toolSuccess, toolMsg, toolReport );
end
% ------------------------------------
% Final result
% ------------------------------------
if ~isempty( strtrim( errorStr ) )
    success = 0;
end
% -------------------------------------------------------
% Helper: Validate each tool in the list
% -------------------------------------------------------
    function excludedList = validateEachTool(tools)
        excludedList = {  };
        if h.isBuildTool( tools )
            tool = tools;
            [toolSuccess,toolMsg,toolReport,excluded] = tool.validate( h, 'batch' );
            if excluded
                excludedList{ end + 1 } = tool;
                return
            else
                processOutput( toolSuccess, toolMsg, toolReport );
            end
        else
            keys = tools.keys();
            for i = 1:numel( keys )
                tool = tools.getValue( keys{ i } );
                if ~h.isBuildTool( tool )
                    error( message( 'coder_compile:toolchain:CannotValidateInvalidBuildTool', h.Name, tool.Name ) );
                end
                [toolSuccess,toolMsg,toolReport,excluded] = tool.validate( h, 'batch' );
                if excluded
                    excludedList{ end + 1 } = tool;  %#ok<AGROW>
                    continue
                else
                    processOutput( toolSuccess, toolMsg, toolReport );
                end
            end
        end
    end
% -------------------------------------------------------
% Helper: Validate all tools in the list all at once
% -------------------------------------------------------
    function validateToolByBatch(tools)
        msg = message( 'coder_compile:toolchain:ValidateToolchain_CheckingOtherBuildTools' );
        thisReportStr = msg.getString();
        base_positive = 'Located build tool ';  % do not put this string in the message catalog
        base_negative = 'Unable to locate build tool ';  % do not put this string in the message catalog
        tempScriptFile = fullfile( tempdir, 'tempToolchainValidationScript' );
        % -----------------------------------------------
        % Form the initial command: the shell setup code
        % -----------------------------------------------
        initWhichCommand = '';
        setupCommand = '';
        for i = 1:numel( h.ShellSetup )
            setupCommand = [ setupCommand, h.ShellSetup{ i }, newline ];  %#ok<AGROW>
        end
        if ~isempty( h.ShellSetup )
            initWhichCommand = [ setupCommand, newline ];
        end
        % -----------------------------------------------
        % Invoke the batch validation
        % -----------------------------------------------
        try
            if ispc
                [thisSuccess,thisWarnErrorStr,thisReportStr] = batchValidationPC( tools, initWhichCommand, tempScriptFile, thisReportStr );
            else  % Linux, Mac
                [thisSuccess,thisWarnErrorStr,thisReportStr] = batchValidationUnix( tools, initWhichCommand, tempScriptFile, thisReportStr );
            end
            % Process the results
            processOutput( thisSuccess, thisWarnErrorStr, thisReportStr );
        catch exc
            thisSuccess = 1;
            buildToolNames = getBuildToolNames( tools );
            tempMsg = message( 'coder_compile:toolchain:BuildToolPresenceBatchCheckError', buildToolNames, exc.message() );
            thisWarnErrorStr = tempMsg.getString();
            thisReportStr = { [ thisReportStr, thisWarnErrorStr ] };
            processOutput( thisSuccess, thisWarnErrorStr, thisReportStr );
            return
        end
        % ---------------------------------------------------------
        % Utility to get a comma-separated tool names
        % ---------------------------------------------------------
        function names = getBuildToolNames(tools)
            numTools = numel( tools );
            names = cell( 1, numTools );
            for k = 1:numTools
                names{ k } = tools{ k }.Name;
            end
            names = coder.make.internal.cell2str( names, ',' );
        end
        % ---------------------------------------------------------
        % Validate tools by batch
        % ---------------------------------------------------------
        function [thisSuccess,thisWarnErrorStr,thisReportStr] = batchValidationPC(tools,fullWhichCommand,tempScriptFile,thisReportStr)
            whichTool = 'where';
            thisSuccess = true;
            thisWarnErrorStr = '';
            tempScriptFileExt = '.bat';
            code.condition = '@if %%errorlevel%%==0 (echo %s) else (echo %s)';  % do not put this string in the message catalog
            code.exit = 'exit /B 0';  % do not put this string in the message catalog
            tempScriptFile = [ tempScriptFile, tempScriptFileExt ];
            % ------------------------------------
            % Form the contents of the batch file
            % ------------------------------------
            for j = 1:numel( tools )
                toolCommand = tools{ j }.getCommand();
                toolName = tools{ j }.Name;
                toolCmds = regexp( strtrim( toolCommand ), '\s', 'split' );
                isMultiWordCmd = numel( toolCmds )>1;
                if isMultiWordCmd
                    toolCommand = toolCmds;
                else
                    toolCommand = cellstr( toolCommand );
                end
                for k = 1:numel( toolCommand )
                    shortCommand = toolCommand{ k };
                    %the toolCommand could also contain a path
                    [locToolCommand,allMacrosReplaced,undeclaredMacrosMsg,otherMsg] = tools{ j }.replaceMacrosInPath( toolCommand{k}, h );
                    toolCommand{ k } = locToolCommand;
                    if ~allMacrosReplaced
                        if ~isempty( undeclaredMacrosMsg )  % warn
                            thisSuccess = true;
                            thisWarnErrorStr = [thisWarnErrorStr, newline, undeclaredMacrosMsg.getString()]; %#ok<AGROW>
                            thisReportStr = [ thisReportStr, tb, undeclaredMacrosMsg.getString(), newline ]; %#ok<AGROW>
                        end
                        if ~isempty( otherMsg )
                            thisWarnErrorStr = [thisWarnErrorStr, newline, otherMsg.getString()]; %#ok<AGROW>
                            thisReportStr = [ thisReportStr, tb, otherMsg.getString(), newline ]; %#ok<AGROW>
                        end
                        % Since there are still unresolved macros, we
                        % skip
                        continue;
                    end
                    if ~isempty( regexp( toolCommand{ k }, '[/\\]', 'once' ) )
                        toks = regexp( toolCommand{k}, '^(.*?)[/\\]([^<>:"/\\|?*]+?(?(?=\.)\w*))$','tokens');
                        shortCommand = toks{1}{2};
                        whichCommand = ['cd ', toks{1}{1}, newline,...
                                        whichTool, ' ', toks{1}{2}];
                        whichCommand = regexprep( whichCommand, '/', '\\' );
                    else
                        whichCommand = [ whichTool, ' ', toolCommand{ k } ];
                    end
                    positive = [ base_positive, '"', toolName, '": ', shortCommand ];
                    negative = [ base_negative, '"', toolName, '": ', shortCommand ];
                    fullWhichCommand = [ fullWhichCommand, whichCommand, newline, sprintf( code.condition, positive, negative ), newline, newline];  %#ok<AGROW>
                end
            end
            % Finish out content of the script file
            fullWhichCommand = [ fullWhichCommand, code.exit ];
            % ------------------------------------
            % Run the script
            % ------------------------------------
            % Create a temporary script file
            if exist( tempScriptFile, 'file' )==2
                delete( tempScriptFile );
            end
            fid = fopen( tempScriptFile, 'w' );
            if fid==-1
                fullWhichCommand = strrep( fullWhichCommand, newline, '' );
                fullWhichCommand = strrep( fullWhichCommand, tb, '' );
                [~,output] = rtw.system( fullWhichCommand );
            else
                try
                    % Write the contents of the script file
                    fprintf( fid, '%s', fullWhichCommand );
                    fclose( fid );
                    % Run the batch file
                    [~,output] = rtw.system( tempScriptFile );
                catch exc
                    % do nothing
                end
                % Delete the temporary script file
                delete( tempScriptFile );
            end
            % ------------------------------------
            % Process the script's output
            % ------------------------------------
            % Parse the output. Locate known string patterns.
            % Parse out the negative & positive results from the
            % output string.
            negativeMatches = regexp( output, [ base_negative, '"([^\"]*)": .*?[\n\r\s]' ], 'match' );
            positiveMatches = regexp( output, [ base_positive, '"([^\"]*)": .*?[\n\r\s]' ], 'match' );
            allMatches = [ coder.make.internal.cell2str( negativeMatches, newline ), newline, coder.make.internal.cell2str( positiveMatches, newline ) ];
            % Determine when to warn or error out
            noMatches = isempty( negativeMatches ) && isempty(positiveMatches);
            if noMatches
                % do not switch thisSuccess to false just because the batch script failed
                [thisReportStr,thisWarnErrorStr] = processBatchValidationOutput( noMatches, allMatches, thisSuccess, thisReportStr, thisWarnErrorStr);
            else
                thisSuccess = thisSuccess && isempty( negativeMatches ) && ~isempty( positiveMatches );
                [thisReportStr,thisWarnErrorStr] = processBatchValidationOutput( noMatches, allMatches, thisSuccess, thisReportStr, thisWarnErrorStr);
            end
        end  % batch validation in PC
        % ---------------------------------------------------------
        % Validate tools by batch
        % ---------------------------------------------------------
        function [thisSuccess,thisWarnErrorStr,thisReportStr] = batchValidationUnix(tools,fullWhichCommand,tempScriptFile,thisReportStr)
            persistent unixShell
            whichTool = 'which';
            thisSuccess = true;
            thisWarnErrorStr = '';
            tempScriptFileExt = '';
            tempScriptFile = [ tempScriptFile, tempScriptFileExt ];
            % ------------------------------------
            % Determine the available Unix shell
            % ------------------------------------
            if isempty( unixShell )
                shells = { 'sh', 'csh', 'bash' };  % 'ksh','dash', 'rbash', 'tcsh'
                for j = 1:numel( shells )
                    shell = [ '/bin/', shells{ j } ];
                    if exist( shell, 'file' )==2
                        unixShell = shells{ j };
                        fullWhichCommand = [ [ '#! ', shell ], newline, newline, fullWhichCommand ];  %#ok<AGROW>
                        break
                    end
                end
                % Still empty - no Unix shell found
                % Resort to checking build tools individually
                if isempty( unixShell )
                    for j = 1:numel( tools )
                        [toolSuccess,toolWarnErrorMsg,toolReportMsg] = tools{ j }.checkForPresence( true, h, 'single' );
                        processOutput( toolSuccess, toolWarnErrorMsg, toolReportMsg );
                    end
                    return
                end
            end
            % ------------------------------------
            % Form the contents of the script file
            % ------------------------------------
            divider = '======================';  % do not put this string in the message catalog
            for j = 1:numel( tools )
                toolCommand = tools{ j }.getCommand();
                toolName = tools{ j }.Name;
                toolCmds = regexp( strtrim( toolCommand ), '\s', 'split' );
                isMultiWordCmd = numel( toolCmds )>1;
                if isMultiWordCmd
                    toolCommand = toolCmds;
                else
                    toolCommand = cellstr( toolCommand );
                end
                for k = 1:numel( toolCommand )
                    %the toolCommand could also contain a path
                    [locToolCommand,allMacrosReplaced,undeclaredMacrosMsg,otherMsg] = tools{ j }.replaceMacrosInPath( toolCommand{k}, h );
                    toolCommand{ k } = locToolCommand;
                    if ~allMacrosReplaced
                        if ~isempty( undeclaredMacrosMsg )  % warn
                            thisSuccess = true;
                            thisWarnErrorStr = [thisWarnErrorStr, newline, undeclaredMacrosMsg.getString()]; %#ok<AGROW>
                            thisReportStr = [ thisReportStr, tb, undeclaredMacrosMsg.getString(), newline ]; %#ok<AGROW>
                        end
                        if ~isempty( otherMsg )
                            thisWarnErrorStr = [thisWarnErrorStr, newline, otherMsg.getString()]; %#ok<AGROW>
                            thisReportStr = [ thisReportStr, tb, otherMsg.getString(), newline ]; %#ok<AGROW>
                        end
                        % Since there are still unresolved macros, we skip
                        break;
                    end
                    whichCommand = [ whichTool, ' ', toolCommand{ k } ];
                    fullWhichCommand = [ fullWhichCommand, 'echo Build Tool: ---', toolName, '---', newline, whichCommand, newline, 'echo ', divider, newline ];  %#ok<AGROW>
                end
            end
            % Finish out content of the script file
            fullWhichCommand = [ fullWhichCommand, 'exit 0', newline ];
            % ------------------------------------
            % Run the script
            % ------------------------------------
            % Create a temporary script file
            if exist( tempScriptFile, 'file' )==2
                delete( tempScriptFile );
            end
            fid = fopen( tempScriptFile, 'w' );
            fileattrib( tempScriptFile, '+w +x' );
            % Write the script and run it
            if fid==-1
                output = '';
            else
                try
                    % Write the contents of the script file
                    fprintf( fid, '%s', fullWhichCommand );
                    fclose( fid );
                    % Escape spaces if tempScriptFile contains any space
                    tempScriptFileStr = coder.make.internal.transformPaths(tempScriptFile, 'pathType', 'alternate');
                    % Run the script file
                    [~,output] = rtw.system( [ unixShell, ' ', tempScriptFileStr ] );
                catch exc
                    % do nothing
                end
                % Delete the temporary script file
                delete( tempScriptFile );
            end
            % ------------------------------------
            % Process the script's output
            % ------------------------------------
            % Parse the output. Locate known string patterns.
            toolsIdx = regexp( output, 'Build Tool: ---([^---]*)---' );
            checkOutput.Desc = regexp( output, 'Build Tool: ---([^---]*)---', 'match' );
            checkOutput.Found = ones( 1, numel( toolsIdx ) );
            newLineIdx = regexp( output, newline );
            for k = 1:numel( toolsIdx )
                idx = find( newLineIdx>toolsIdx( k ) );
                checkOutput.Loc{ k } = output( newLineIdx( idx( 1 ) ) + 1:newLineIdx( idx( 2 ) ) - 1 );
                if isempty( checkOutput.Loc{ k } ) || strcmpi( checkOutput.Loc{ k }, divider ) || exist( checkOutput.Loc{ k }, 'file' )~=2
                    checkOutput.Found( k ) = 0;
                    checkOutput.Loc{ k } = 'Not Found.';
                end
            end
            % Determine when to warn or error out
            noMatches = isempty( toolsIdx );
            if noMatches
                % do not fail validation just because the script failed
                [thisReportStr, thisWarnErrorStr] = processBatchValidationOutput( noMatches, toolsIdx, thisSuccess, thisReportStr, thisWarnErrorStr);
            else
                thisSuccess = thisSuccess && all( checkOutput.Found );
                allMatches = [  ];
                for k = 1:numel( checkOutput.Desc )
                    allMatches = [ allMatches, sprintf( '%s \n\t%s', checkOutput.Desc{ k }, checkOutput.Loc{ k } ), newline ];  %#ok<AGROW>
                end
                [thisReportStr, thisWarnErrorStr] = processBatchValidationOutput( noMatches, allMatches, thisSuccess, thisReportStr, thisWarnErrorStr);
            end
        end  % batch validation in Unix
        % ---------------------------------------------------------
        % Process batch validation's output
        % ---------------------------------------------------------
        function [aReportStr,aWarnErrorStr] = processBatchValidationOutput(noMatches,allMatches,aSuccess,aReportStr,aWarnErrorStr)
            if noMatches
                msg = message( 'coder_compile:toolchain:UnsuccessfulBatchValidation', getBuildToolNames( tools ) );
                aReportStr = { [ aReportStr, msg.getString() ] };
                aWarnErrorStr = [aWarnErrorStr, aReportStr];  % warning
            else
                aReportStr = { [ aReportStr, allMatches, newline ] };
                if aSuccess
                    msg = message( 'coder_compile:toolchain:Passed' );
                    warnErrorStr = {  };  % no error/warning
                else
                    msg = message( 'coder_compile:toolchain:Failed' );
                    warnErrorStr = aReportStr;  % error
                end
                aReportStr{ 1 } = [ aReportStr{ 1 }, msg.getString(), newline ];
                aWarnErrorStr = [aWarnErrorStr, warnErrorStr];
            end
        end
    end  % batch validation
% --------------------------------------------------------
% Helper: Append the validation result to the final report
% --------------------------------------------------------
    function processOutput(success,warnErrorMsg,reportMsg)
        if ischar( warnErrorMsg )
            warnErrorMsg = { warnErrorMsg };
            reportMsg = { reportMsg };
        end
        for x = 1:numel( warnErrorMsg )
            if success && ~isempty( warnErrorMsg{ x } )
                warnStr = [ warnStr, newline, warnErrorMsg{ x } ];  %#ok<AGROW>
            elseif ~success
                errorStr = [ errorStr, newline, warnErrorMsg{ x } ];  %#ok<AGROW>
            end
        end
        for x = 1:numel( reportMsg )
            reportStr = [ reportStr, newline, reportMsg{ x } ];  %#ok<AGROW>
        end
    end
% --------------------------------------------------------
% Helper: Get the name of a temp directory
% --------------------------------------------------------
    function fullPath = getTempDirNameAndPath()
        fullPath = tempname;
        while exist(fullPath, 'dir')
            fullPath = getTempDirNameAndPath();
        end
    end
end
