classdef(Abstract, Hidden=true) SubsystemManager < handle
    % SUBSYSTEMMANAGER implements the Atomic Subsystem workflow
    
    % Copyright 2020-2023 The MathWorks, Inc.
    
    properties (Access = protected)
        HarnessInfo
        HarnessName
        SubsystemFullPath      
        HiddenModelName
        SubsystemUTHandle
        SubsystemUTFullName
        HiddenModelBuildDir char = ''
    end
    
    methods (Access=protected, Abstract)
        [errMsgs, warnMsgs] = validation(this, runCompatibilityCheck);
        [errMsgs, warnMsgs] = setup(this);
        teardown(this);
        params = getWorkflowSpecificParamValuePairs(this)
    end
    
    methods (Abstract, Static, Access = public)
        [errMsgs, warnMsgs] = compatibilityCheck(parentModelName, susbystemName);
    end
        
    methods
        function this = SubsystemManager(harnessName)
            this.reset(harnessName)
        end

        function reset(this, harnessName)
            % reset the core properties
            assert(strcmp(get_param(harnessName, 'IsHarness'), 'on'));
            this.HarnessName = harnessName;
            this.HarnessInfo = Simulink.harness.internal.getHarnessInfoForHarnessBD(harnessName);
            this.resetSubsystemUnderTestProperties();

            isHarnessForSubsystem = strcmp(this.HarnessInfo.ownerType, 'Simulink.SubSystem');
            isHarnessForSubsystemReference = strcmp(this.HarnessInfo.ownerType, 'Simulink.BlockDiagram') && ...
                strcmp(get_param(this.SubsystemUTHandle, 'blocktype'), 'SubSystem');
            assert(isHarnessForSubsystem || isHarnessForSubsystemReference);

            this.SubsystemFullPath = this.HarnessInfo.ownerFullPath;
        end

        
        function [errMsgs, warnMsgs] = workflowTSValidation(this)
            % validation for Toolstrip
            
            runCompatibilityCheck = true;
            [errMsgs, warnMsgs] =  this.validation(runCompatibilityCheck);
        end
        
        function [errMsgs, warnMsgs] = workflowTSPrepareRun(this)
            % workflow setup for Toolstrip
            this.resetSubsystemUnderTestProperties();
            [errMsgs, warnMsgs] = this.setup();
            if isempty(errMsgs)
                this.attachPlugin();
                pvp = this.getActivationParamValuePairs();
                set_param(this.HarnessName, pvp{:});
            end
        end
        
        function workflowTeardown(this)
            
            % delete temporary files/folders
            if ~isempty(this.HiddenModelName) && bdIsLoaded(this.HiddenModelName)
                needToSave = 0;
                close_system(this.HiddenModelName, needToSave);
            end
            this.removeHiddenModelBuildDirectory();
            this.teardown();
        end
        
        function workflowSLTSetup(this, runCompatibilityCheck)
            % full setup and validation for SL Test
            arguments
                this
                runCompatibilityCheck = true;
            end
            
            [errMsgs, warnMsgs] =  this.validation(runCompatibilityCheck);
            rtw.pil.SubsystemManager.reportWarningsAndErrors(errMsgs, warnMsgs);
            
            this.resetSubsystemUnderTestProperties();
            [errMsgs, warnMsgs] = this.setup();
            rtw.pil.SubsystemManager.reportWarningsAndErrors(errMsgs, warnMsgs);
            
            this.attachPlugin();
        end
        
        function params = getActivationParamValuePairs(this)
            params = { ...
                'XILModelName', this.HiddenModelName, ...
                'XILSubsystemName', this.getEscapedSubsystemName()};
            params = [params, this.getWorkflowSpecificParamValuePairs()];
        end
        
        function attachPlugin(this)
            % attach plugin
            mgr = Simulink.PluginMgr;
            mgr.attach(this.HarnessName, 'XILSubsystemPlugin');
        end
    end
    
    methods (Access = protected)
        function subsystemName = getEscapedSubsystemName(this)
            subsystemName = regexprep(get_param(this.SubsystemUTHandle, 'Name'), '/', '//');
        end
        
        function removeHiddenModelBuildDirectory(this)
            % it's possible to invoke the workflow teardown on an object
            % that has not been through the validation/setup, for example
            % when deactivating Fast Restart from the toolstrip without
            % running any simulation.
            if isfolder(this.HiddenModelBuildDir)
                % use return arguments to avoid exceptions from rmdir
                [~, ~, ~] = rmdir(this.HiddenModelBuildDir, 's');
            end
        end

        function replaceReferecedSubsystem(this, extractedMdlName)
            % If the SUT is a referenced subsystem, replace its
            % representation in the hidden model with equivalent
            % non-referenced subsystem.
            % Downstream processing can modify the SUT in the hidden model,
            % e.g. remove mapping, so we replace the referenced subsystem
            % to avoid dirtying it.
            referencedSubsystem = get_param(this.SubsystemUTFullName, 'ReferencedSubsystem');
            if ~isempty(referencedSubsystem)
                % create a temporary model
                newSystemHandle = new_system();
                cleanUpTemp = onCleanup(@()close_system(newSystemHandle, 0));
                % add an empty subsystem to the temporary model
                tempSubsystemFullName = [get_param(newSystemHandle, 'Name') '/tempSubsys'];
                add_block('built-in/Subsystem', tempSubsystemFullName);
                % copy the contents from the SUT to the empty subsystem
                Simulink.BlockDiagram.copyContentsToSubsystem(referencedSubsystem, ...
                    tempSubsystemFullName);
                % replace the SUT in the hidden model with the new
                % subsystem
                ssInHiddenModel = [extractedMdlName '/' this.getEscapedSubsystemName()];
                pil_block_replace(tempSubsystemFullName, ssInHiddenModel, false);
            end
        end
    end
    
    methods (Access = private)
        function resetSubsystemUnderTestProperties(this)
            this.SubsystemUTFullName = Simulink.harness.internal.getLoadedHarnessCUT(this.HarnessInfo.model, this.HarnessInfo.name);
            this.SubsystemUTHandle = get_param(this.SubsystemUTFullName, 'Handle');
        end
    end
    
    methods (Static, Access = public)
        function cleanupWorkflowParameters(harness)
            set_param(harness, ...
                'XILModelName', '', ...
                'XILSubsystemName', '', ...
                'XILSubsystemWorkflow', 'None')
        end    
        
        function reportWarningsAndErrors(errMsgs, warnMsgs)
            
            for idx = 1:numel(warnMsgs)
                % issue warnings
                errMsgs{idx}.reportAsWarning();
            end
            
            if ~isempty(errMsgs)
                % TODO: pack errors into main exception and throw, just
                % throw the first one for now as we stop processing after
                % the first error anyway
                errMsgs{1}.throw();
            end
        end
    end
end
