classdef (Abstract) HostDebuggerManager < handle
    %HOSTDEBUGGERMANAGER utility interface to create an instance of
    % a HostSILDebugger
    %
    %   This is an undocumented class. Its methods and properties are
    %   likely to change without warning from one release to the next.
    
    %   Copyright 2020-2022 The MathWorks, Inc.
        
    properties(GetAccess = protected, SetAccess = immutable)
        % Cell array of supported compiler short names, e.g. MSVC, GCC
        CompilerID(1,:) cell;
        % Cell array of compiler full names and version pairs.
        CompilerVersionMap containers.Map;
        % Target Framework Support Package manager.
        TFSPKGManager (1, 1) targetframework.internal.utilities.supportpackage.ISupportPackageManager = targetframework.internal.utilities.supportpackage.SupportPackageManager()
    end
    
    properties(Access = protected)
        ToolchainInfo(1,1) rtw.connectivity.XILDebuggerToolchainInfo;
    end
    
    methods(Abstract, Access = protected)     
        populateToolchainInfo(this, defaultComp);
        checkDebuggerIsAvailable(this);
        service = getDebugTool(this);
    end
    
    methods(Static)                       
        function service = getDebugExecutionService(spkgManager)

            arguments
                spkgManager (1, 1) targetframework.internal.utilities.supportpackage.ISupportPackageManager = targetframework.internal.utilities.supportpackage.SupportPackageManager()
            end

            manager = rtw.connectivity.HostDebuggerManager.getManager(spkgManager);
            service = manager.getDebugTool();
        end
    end
    
    methods(Access = protected)
        
        function this = HostDebuggerManager(compilerID, compilerNameVersionPairs, spkgManager)
            assert(length(compilerID) == length(compilerNameVersionPairs), ...
                'Arguments should have the same size');
            this.CompilerID = compilerID;
            this.CompilerVersionMap = containers.Map(compilerID, compilerNameVersionPairs);
            this.TFSPKGManager = spkgManager;
        end
        
        function getToolchainInfo(this)
            % Gather compiler configuration info
            this.ToolchainInfo = rtw.connectivity.XILDebuggerToolchainInfo;
            
            % Get the default compiler that was used to compile the
            % model
            defaultComp = coder.make.internal.getMexCompilerInfo();
            if ~isempty(defaultComp)
                this.populateToolchainInfo(defaultComp);
            end
            % Error out if the code was not built with a SIL Debugging
            % compatible compiler, or if a SIL Debugging compatible IDE is
            % not present.
            this.checkCompilerIsCompatible;
            this.checkDebuggerIsAvailable;
        end
        
        function populateBasicToolchainInfo(this, aCompInfoSrc)
            this.ToolchainInfo.IsSupported = true;
            this.ToolchainInfo.ID = aCompInfoSrc.comp.ShortName;
            this.ToolchainInfo.Manufacturer = aCompInfoSrc.comp.Manufacturer;
        end
        
        function isSupported = isSupportedVersion(this, tool, currVersion)
            % check that current version is greater or equal than the
            % registered version
            toolInfo = this.CompilerVersionMap(tool);
            isSupported = str2double(regexp(currVersion, '^(\d+\.\d+)', 'match', 'once')) >= str2double(toolInfo{2});
        end

        function throwOnMissingUnixDebugger(this, debuggerName, errorIdentifier)

            % Check if debugger is available. If it is not, and the VS Code
            % Debug Tool is not enabled or not installed, throw and error.
            if ~rtw.connectivity.HostSILDebugger.commandExists(debuggerName) && ~this.TFSPKGManager.isInstalledAndEnabled('VSCodeDebugTool')

                msg = string.empty();
                msg(end+1) = message(errorIdentifier).getString();

                [status, details] = this.TFSPKGManager.getInstallationStatus('VSCodeDebugTool');
                if status
                    msg(end+1) = message('PIL:pil:SILDebuggingVSCodeNotEnabled', details.PublishedName).getString();
                else

                    % Here we use a "tripwire", to keep track of the number
                    % of users clicking on the installation link.
                    callback = sprintf('matlab.internal.addons.launchers.showExplorer(''tripwire_sildebugging_vscodedebug'', ''identifier'', ''%s'')', details.BaseCode);
                    msg(end+1) = message('PIL:pil:SILDebuggingVSCodeNotFound', details.PublishedName, callback).getString();
                end

                exception = MSLException(message('PIL:pil:SILDebuggingUNIXDebuggerNotFound', join(compose("\n    * %s", msg))));
                exception.throw();
            end
        end
    end
    
    methods(Static, Access = private)
        function manager = getManager(spkgManager)

            arguments
                spkgManager (1, 1) targetframework.internal.utilities.supportpackage.ISupportPackageManager = targetframework.internal.utilities.supportpackage.SupportPackageManager()
            end

            % Depending on the host platform, create a HostDebuggerManager
            % to create an instance of the expected HostSILDebugger.
            if(ismac)
                manager = rtw.connectivity.MacHostDebuggerManager(spkgManager);
            elseif(isunix)
                manager = rtw.connectivity.LinuxHostDebuggerManager(spkgManager);
            elseif(ispc)
                manager = rtw.connectivity.WindowsHostDebuggerManager(spkgManager);
            else
                assert(false, 'Unexpected host platform.');
            end
            manager.getToolchainInfo();
        end
    end
    
    methods(Access = private)    
        function comp = getSupportedCompilers(this)
            % return a cell array of supported compiler names in the form 
            % of "compiler >= version" or "compiler" if no version is 
            % provided
            numComp = this.CompilerVersionMap.length;
            comp = cell(1, numComp);
            for idx = 1:numComp
                key = this.CompilerID{idx};
                compNameVersionPair = this.CompilerVersionMap(key);
                compName = compNameVersionPair{1};
                comp{idx} = sprintf('%s', compNameVersionPair{1});
                compVersion = compNameVersionPair{2};
                if ~isempty(compVersion)
                    comp{idx} = sprintf('%s >= %s', compName, compVersion);
                end
            end
        end
        
        function checkCompilerIsCompatible(this)
            if ~this.ToolchainInfo.IsSupported
                % If supported compiler not found
                supportedCompilersStr = strjoin(this.getSupportedCompilers, ', ');
                DAStudio.error('PIL:pil:SILDebuggingCompilerNotFound', ...
                    supportedCompilersStr);
            end
        end
    end
end
