classdef(Hidden=true) RLSManager < rtw.pil.SubsystemManager
    % RLSMANAGER implements the Atomic Subsystem workflow
    
    % Copyright 2020-2023 The MathWorks, Inc.

    properties(Access = private)
        FunctionInterface char
        LibraryName char
        IsSSRefUnitTest (1,1) logical
    end
    
    methods
        function this = RLSManager(harnessName)
            this = this@rtw.pil.SubsystemManager(harnessName);
            this.LibraryName = this.HarnessInfo.model; % xxx?
            
            this.IsSSRefUnitTest = isempty(this.HarnessInfo.functionInterfaceName);
            if this.IsSSRefUnitTest
                % no function interface has been specified, this must be
                % RLS via subsystem reference
                interfaceName = this.HarnessInfo.name;
            else
        		% RLS via library based code generation
                interfaceName = this.HarnessInfo.functionInterfaceName;
            end
            this.FunctionInterface = interfaceName;
            this.HiddenModelName = ...
                rtw.pil.RLSUtils.getUniqueContextName(...
                    interfaceName);
                
            % cache the hidden model build dir for the teardown stage, this
            % workflow only supports TargetEnvironmentSubfolder folder
            % structure so we can reconstruct the folder as below
            buildDir = RTW.getBuildDir(harnessName);
            targetFolder = fileparts(buildDir.BuildDirectory);
            this.HiddenModelBuildDir = fullfile(targetFolder, this.HiddenModelName);
        end 
    end
    
    methods (Static, Access = public)    
        function [errMsgs, warnMsgs] = compatibilityCheck(parentModelName, susbystemFullPath)
            % early compatibility check, can be run before creating the
            % subsystem harness 
            
            errMsgs = cell(1,0);
            warnMsgs = cell(1,0);
            
            % error out for function call and triggered subsystems
            ssObj = Simulink.SubsystemType(susbystemFullPath);
            if ssObj.isFunctionCallSubsystem
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedFcnCallSS');
            elseif ssObj.isTriggeredSubsystem
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedTriggeredSS');
            end
            
            % error out if the simulation is running a different folder
            % than the one where the library file is saved
            rootFolder = fileparts(which(parentModelName));
            if ~strcmp(rootFolder, pwd)
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedSimFolder', rootFolder);
            end
            
            % error out if the CodeGenFolderStructure is not set to
            % TargetEnvironmentSubfolder
            folderStructure = Simulink.fileGenControl('get', 'CodeGenFolderStructure');
            if ~strcmp(folderStructure, 'TargetEnvironmentSubfolder')
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedFolderStructure');
            end
            
            % error out if there are Width blocks inside the subsystem
            % @todo update the usage of edit-time filter filterOutInactiveVariantSubsystemChoices()
            % instead use the post-compile filter activeVariants() - g2596051
            widthBlocks = find_system(susbystemFullPath, ...
                'MatchFilter',@Simulink.match.internal.filterOutInactiveVariantSubsystemChoices,... % look only inside active choice of VSS
                'FollowLinks', 'on', 'FirstResultOnly', 'on', 'BlockType', 'Width');
            if ~isempty(widthBlocks)
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedWidthBlock');
            end
        end
    end

    methods (Access=protected)
        
       function [errMsgs, warnMsgs] = validation(this, runCompatibilityCheck)

            if runCompatibilityCheck
                [errMsgs, warnMsgs] = this.compatibilityCheck(this.HarnessInfo.model, this.SubsystemFullPath);
            else
                errMsgs = cell(1,0);
                warnMsgs = cell(1,0);
            end

            % harness related validation

            % when the subsystem is substituted, check that profiling
            % is disabled
            % error out if profiling is active
            if strcmp(get_param(this.HarnessName, 'CodeExecutionProfiling'), 'on')
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedProfilingWithRLS');
            end
            
            if strcmp(get_param(this.HarnessName, 'CodeStackProfiling'), 'on')
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedStackProfiling', this.HarnessName);
            end
            
            % error out if there are local Data Store Memory blocks in
            % the test harness
            % Look inside library links as the user could have moved
            % DSMs into linked subsystems, this will be reviewed after
            % g2114905 is addressed.
            libPath = getfullname(this.SubsystemUTHandle);
            % @todo update the usage of edit-time filter filterOutInactiveVariantSubsystemChoices()
            % instead use the post-compile filter activeVariants() - g2596051
            dataStoreMemoryBlocks = find_system(this.HarnessName, ...
                'MatchFilter',@Simulink.match.internal.filterOutInactiveVariantSubsystemChoices,... % look only inside active choice of VSS
                'FollowLinks', 'on', 'BlockType',  'DataStoreMemory');
            for i = 1:length(dataStoreMemoryBlocks)
                dsm = dataStoreMemoryBlocks(i);
                dsmPath = getfullname(dsm);
                if ~startsWith(dsmPath, libPath)
                    errMsgs{end+1} = MSLException(...
                        'PIL:pil_subsystem:UnsupportedLocalDataStoreMemoryBlocks'); %#ok<AGROW>
                    break;
                end
            end
            
        end
        
        function [errMsgs, warnMsgs] = setup(this)
            
            % export code context.  If model already exists, clean up first
            % and then export
            if exist(this.HiddenModelName, 'file') == 4
                this.workflowTeardown();
            end

            this.extractFunctionInterface();

            [errMsgs, warnMsgs] = this.lateVerifySettings();
            if ~isempty(errMsgs)
                this.removeFile([this.HiddenModelName, '.slx']);
            end
        end
        
        function teardown(this)
            % remove hidden model file
            this.removeFile([this.HiddenModelName, '.slx']);
        end
        
        function params = getWorkflowSpecificParamValuePairs(~)
            params = {'XILSubsystemWorkflow', 'ReusableLibrary'};
        end
    end
    
    methods (Access = private)      

        function extractFunctionInterface(this)
            
            if this.IsSSRefUnitTest
                % TODO: replace with snapshot capability when available,
                % need g3051882
                this.copySSRefHarness();
            else
                Simulink.libcodegen.internal.exportCodeContext(...
                    this.SubsystemFullPath, this.FunctionInterface, ...
                    'name', this.HiddenModelName);
            end
        end

        function copySSRefHarness(this)
            % create temp model
            new_system(this.HiddenModelName);
            % copy all blocks
            srcBlocks = find_system(this.HarnessInfo.name,'SearchDepth',1);
            for idx=2:length(srcBlocks)
                copyBlockTo(srcBlocks{idx}, this.HiddenModelName);
            end
            % copy all lines
            lines = find_system(this.HarnessInfo.name,'SearchDepth',1,'FindAll','on' ,'Type','line');
            for idx=1:length(lines)
                add_line(this.HiddenModelName,get_param(lines(idx),'Points'));
            end
            % attach copied config set, will handle configset ref
            srcCs = getActiveConfigSet(this.HarnessInfo.name);
            newConfig = attachConfigSetCopy(this.HiddenModelName, srcCs, true);
            setActiveConfigSet(this.HiddenModelName, newConfig.name);
        end
                
        function removeFile(~, filename)
            warnID = 'MATLAB:DELETE:FileNotFound';
            preCfg = warning('off', warnID);
            restoreWarnState = onCleanup(@() warning(preCfg.state, warnID));
            % we should try to avoid try/catch
            try
                delete(filename);
            catch ME 
            end
        end
        
        function [errMsgs, wrnMsgs] = lateVerifySettings(this)
            % this method validates all the settings which are available
            % only when the code context is exported
            
            errMsgs = cell(1, 0);
            wrnMsgs = cell(1, 0);
            
            % open exported code context
            load_system(this.HiddenModelName);
            % check that harness and exported code context are in sync
            stf = get_param(this.HiddenModelName, 'SystemTargetFile');
            hw = get_param(this.HiddenModelName, 'TargetHWDeviceType');
            if ~strcmp(stf, get_param(this.HarnessName, 'SystemTargetFile')) || ...
                    ~strcmp(hw, get_param(this.HarnessName, 'TargetHWDeviceType'))
                harnessName = get_param(this.HarnessName, 'name');
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:LibraryAndHarnessNotInSync', ...
                     harnessName, stf, hw);
            end
            if isempty(errMsgs)
                % check that the generated code from the static library exists
                
                sbsName = get_param(this.SubsystemUTHandle, 'name');
                [~, contextCodeFolder] = rtw.pil.RLSUtils.getRLSFoldersFromContext(...
                    this.HiddenModelName, this.LibraryName, ...
                    this.FunctionInterface, sbsName);
                if ~isfile(fullfile(contextCodeFolder, 'codedescriptor.dmr'))
                    errMsgs{end+1} = MSLException('PIL:pil_subsystem:LibraryCodeNotPreGenerated', ...
                        this.LibraryName);
                end
            end
            
            % error out based on the caller
            if ~isempty(errMsgs)
                % close the system if an error occurred, leave it loaded
                % otherwise as it will be used in simulation
                needToSave = 0;
                close_system(this.HiddenModelName, needToSave);
            end
        end
    end
end


function copyBlockTo(srcBlk, dstModel)
    dst_pos = get_param(srcBlk, 'Position');
    
   
    % get the block priority of the destination block
    dst_priority = get_param(srcBlk, 'Priority');
    % use the original name for the savedDstBlk
    % Preserve the orientation of the block
    dst_orientation = get_param(srcBlk, 'Orientation');
       
    % add the src block
    dstName = get_param(srcBlk, 'Name');
    dstBlk = [dstModel '/' strrep(dstName, '/', '//')];
    add_block(srcBlk, dstBlk,...
        'Position', dst_pos,...
        'Orientation', dst_orientation, ...
        'Priority', dst_priority);
end
