classdef SimulinkUtilities
    % This class is for internal use only. It may be removed in the future.
    
    % Contains static methods for functions that are commonly used in
    % Simulink blocks.
    %
    % matlabshared.tracking.internal.SimulinkUtilities methods:
    %   seedManager     - Manages table of initial seeds used in Simulink models
    %   isModelStopped  - Returns current simulation status for Simulink block
    %   loadModels      - Loads all Simulink models on the block's path
    %   closeModels     - Closes all specified Simulink models
    
    % Copyright 2017-2020 The MathWorks, Inc.
    
    methods(Static)
        function initialSeed = seedManager(BlockName, getSeed)
            % matlabshared.tracking.internal.SimulinkUtilities.seedManager Manages table of initial seeds used in Simulink models
            %   initialSeed = matlabshared.tracking.internal.SimulinkUtilities.seedManager(BlockName)
            %   returns the initial seed used by the Simulink block specified by the
            %   full Simulink block path, BlockName. The initial seed will be drawn
            %   from the closed interval [0, 2^32-1].
            %
            %   initialSeed = matlabshared.tracking.internal.SimulinkUtilities.seedManager(BlockName, getSeed)
            %   returns the initial seed used by the Simulink block. If getSeed is
            %   true, then a new random seed will not be generated. If getSeed is not
            %   provided, then it is set to false.
            %
            % Expects the Simulink block specified by BlockName to have the following
            % parameters on its mask:
            %   InitialSeedSource - Set to one of: 'Specify seed', 'Repeatable', or
            %                       'Not repeatable'
            %   InitialSeed       - Set to an integer on the closed interval [0, 2^32-1]
            
            % Copyright 2017 The MathWorks, Inc.
            
            persistent managedSeeds
            
            narginchk(1,2);
            
            if nargin<2
                getSeed = false;
            end
            
            if isempty(BlockName)
                initialSeed = [];
                return
            end
            
            [loadedModels, resolvedBlkName] = matlabshared.tracking.internal.SimulinkUtilities.loadModels(BlockName);
            mkClean = onCleanup(@()matlabshared.tracking.internal.SimulinkUtilities.closeModels(loadedModels));
            
            % Does this block exist?
            fndBlk = find_system(resolvedBlkName);
            
            if ~getSeed && (isempty(fndBlk) || matlabshared.tracking.internal.SimulinkUtilities.isModelStopped(BlockName))
                initialSeed = [];
                return
            end
            
            if isempty(managedSeeds)
                managedSeeds = initTable();
            end
            
            % Is block already in this table?
            row = strcmp(resolvedBlkName, managedSeeds.BlockName);
            if ~any(row)
                if getSeed
                    initialSeed = [];
                else
                    [initialSeed, managedSeeds] = add2Table(managedSeeds, resolvedBlkName);
                end
            else
                if getSeed
                    initialSeed = managedSeeds.InitialSeed(row);
                else
                    [initialSeed, managedSeeds] = updateRow(managedSeeds, resolvedBlkName, row);
                end
            end
        end
        
        function flag = isModelStopped(blockName)
            % Returns true if Simulink model containing the block specified by
            % blockHandle is currently stopped
            blks = regexp(blockName,'[^/]+','match');
            blkMdl = blks{1};
            flag = strcmp(get_param(blkMdl,'SimulationStatus'),'stopped');
        end
        
        function [loadedModels, resolvedBlkPath] = loadModels(blkPath)
            % Loads all models on the block's path, blkPath, which are not currently
            % loaded. Returns a cell array of all of the models that were loaded.
            % resolvedBlkPath is the path to the block relative to its containing model
            % reference. If the block is not inside of a model reference block, then
            % resolvedBlkPath is the same as blkPath.
            
            loadedModels = cell(0,1);
            
            % Get the block's name and path
            blks = blockparts(blkPath);
            blkName = blks{end}; % The last one is the block's name
            blks = blks(1:end-1);
            
            % First is top-level model (i.e. bdroot)
            topMdl = blks{1};
            if ~bdIsLoaded(topMdl)
                load_system(topMdl);
                loadedModels{end+1} = topMdl;
            end
            currMdl = topMdl;
            
            strtIdx = 2;
            for m = 2:numel(blks)
                thisPath = strjoin(blks(strtIdx:m),'/');
                thisBlk = [currMdl '/' thisPath];
                if strcmp(get_param(thisBlk,'BlockType'),'ModelReference')
                    currMdl = get_param(thisBlk,'ModelName');
                    strtIdx = m+1;
                    
                    if bdIsLoaded(currMdl)
                        continue
                    end
                    load_system(currMdl);
                    loadedModels{end+1,1} = currMdl; %#ok<AGROW>
                end
            end
            
            if nargout>1
                if strtIdx>numel(blks)
                    resolvedBlkPath = [currMdl '/' blkName];
                else
                    currPath = strjoin(blks(strtIdx:end),'/');
                    resolvedBlkPath = [currMdl '/' currPath '/' blkName];
                end
            end
        end
        
        function closeModels(loadedModels)
            % Closes all of the models specified by loadedModels
            for m = 1:numel(loadedModels)
                close_system(loadedModels{m}, 0);
            end
        end
    end
end

% Local helpers
function managedSeeds = initTable()
% Initialize table
BlockName = {}; InitialSeed = []; Shuffle = []; Source = {};
managedSeeds = table(BlockName, InitialSeed, Shuffle, Source);
end

function [InitialSeed, managedSeedsOut] = add2Table(managedSeedsIn, BlockName)

Source = get_param(BlockName,'InitialSeedSource');
if strcmpi(Source,'Specify seed')
    InitialSeed = getSpecifiedSeed(BlockName);
    Shuffle = false;
else
    InitialSeed = randi(2^32,1)-1;
    Shuffle = strcmpi(Source,'Not repeatable');
end
BlockName = {BlockName};
Source = {Source};
newRow = table(BlockName, InitialSeed, Shuffle, Source);
managedSeedsOut = [managedSeedsIn;newRow];
end

function [InitialSeed, managedSeeds] = updateRow(managedSeeds, BlockName, row)
% Was the block updated? If so, make sure the row matches the block's
% current settings

Source = get_param(BlockName,'InitialSeedSource');
if strcmpi(Source,'Specify seed')
    InitialSeed = getSpecifiedSeed(BlockName);
    Shuffle = false;
else
    Shuffle = strcmpi(Source,'Not repeatable');
    srcChange = ~strcmp(Source,managedSeeds.Source(row));
    if Shuffle || srcChange
        InitialSeed = randi(2^32,1)-1;
    else
        InitialSeed = managedSeeds.InitialSeed(row);
    end
end
Source = {Source};
managedSeeds(row,{'InitialSeed','Shuffle','Source'}) = [InitialSeed, Shuffle, Source];
end

function seed = getSpecifiedSeed(fullBlkPath)
% Returns specified seed evaluated in mask's workspace
p = Simulink.Mask.get(fullBlkPath);
wsVars = p.getWorkspaceVariables;
iFnd = find(strcmpi('InitialSeed',{wsVars.Name}),1);
seed = wsVars(iFnd).Value;
end

function blkParts = blockparts(fullBlkPath)
% Returns the block's full path as a cell array of its individual blocks.
% The full block's path can be reconstructed from the output by:
%   fullBlkPath = join(blkParts,'/')

fullBlkPath = strip(fullBlkPath,'right','/');
blkParts = regexp(fullBlkPath,'([^/]+([/]{2,2})*)*','match')';
end
