% expandTrivialSubsystems identifies and expand ''trivial'' subsystems
% in a model based on heuristics.
function expanded = expandTrivialSubsystems(sliceXfrmr, modifiedSystems, ...
        blkHs)

%   Copyright 2014-2021 The MathWorks, Inc.

    if ~isempty(modifiedSystems)
        % Remove invalid handles
        blkHs(blkHs == -1) = [];
        trivialSys = identify_trivial_systems(modifiedSystems, blkHs, sliceXfrmr);   
        if ~isempty(trivialSys)
            expanded = find_indices(modifiedSystems, trivialSys);
            expSysCnt = numel(trivialSys);
            for indx = expSysCnt:-1:1
                sliceXfrmr.expandSubsys(trivialSys(indx));
            end
        else
            expanded = [];
        end
    else
        expanded = [];
    end
    
    
    function idx = find_indices(set, subset)
        % Given a set of doubles and a subset of doubles, find the index of
        % the second set. Order is not preserved.
        subset = unique(subset);
        subset = reshape(subset, 1, numel(subset));
        set = reshape(set, numel(set), 1);
        n = numel(set);
        m = numel(subset);
        testMatrix = repmat(set, 1, m) == repmat(subset, n,1);
        testVector = testMatrix * ones(m,1);
        idx = find(testVector);
    end
end

% This algorithm takes a list of candidate systems and reduces the list
% based on various rules for expansion.
function trivialSys = identify_trivial_systems(candidateSysHs, blkHs, sliceXfrmr)
   
    
    % Rule 1. 
    % - No enable, state or trigger ports
    % - Maximum of 2 remaining input ports
    % - Maximum of 2 remaining output ports
    % - Priority is not set
    MaxInputPorts = 2;
    MaxOutputPorts = 2;
    removeIdx = [];

    if ~isempty(sliceXfrmr.ms)
        %we should not expand the subsystem if the virtual inport/outport under
        %it are also seeds.
        allvinportparent = sliceXfrmr.ms.utilGetVirtualBlkAncestors();
        mapper = Transform.CopyToOrigMap(sliceXfrmr.origMdlName, sliceXfrmr.sliceMdlName, sliceXfrmr.ms.refMdlToMdlBlk);
    end

    for idx = 1:numel(candidateSysHs)
        toBeRemoved = false;
        if ~isempty(sliceXfrmr.ms)
            try
                origBh = mapper.getOriginalBlock(candidateSysHs(idx));
                
                if ~isempty(origBh) && ismember(origBh, allvinportparent)
                    toBeRemoved = true;
                end
            catch ex
                if ~strcmp(ex.identifier, 'Simulink:Commands:InvSimulinkObjectName') ...
                        && ~strcmp(ex.identifier, 'Simulink:utility:SIDSyntaxError')%if not added by slicer.
                    rethrow(ex);
                end
            end
        end

        
        if ~ishandle(candidateSysHs(idx))
            removeIdx(end+1) = idx;   %#ok<AGROW>
        elseif ~isempty(get_param(candidateSysHs(idx),'Priority')) ...
                || ~strcmp(get_param(candidateSysHs(idx),'DataTypeOverride'),'UseLocalSettings')
            removeIdx(end+1) = idx;   %#ok<AGROW>
        elseif toBeRemoved
            removeIdx(end+1) = idx; %#ok<AGROW>
        else
            try
                ports = get_param(candidateSysHs(idx),'PortHandles');
                if isKey(sliceXfrmr.sys2InactivePortHMap,candidateSysHs(idx))
                    activeInportH = setdiff(ports.Inport,sliceXfrmr.sys2InactivePortHMap(candidateSysHs(idx)));
                    activeOutportH = setdiff(ports.Outport,sliceXfrmr.sys2InactivePortHMap(candidateSysHs(idx)));
                else
                    activeInportH = ports.Inport;
                    activeOutportH = ports.Outport;
                end
                
                if ( ~isempty(ports.Enable) || ~isempty(ports.Trigger) || ...
                     ~isempty(ports.State) || ~isempty(ports.Ifaction) || ...
                     numel(activeInportH)>MaxInputPorts || ...
                     numel(activeOutportH)>MaxOutputPorts)
                     removeIdx(end+1) = idx;   %#ok<AGROW>
                else
                    nInport = length(activeInportH);
                    allInOutPortH = [activeInportH activeOutportH];
                    import slslicer.internal.*
                    for n = 1:length(allInOutPortH)
                        portNum = get(allInOutPortH(n),'PortNumber');
                        if n <= nInport
                            bObj = SLGraphUtil.getInportBlock(candidateSysHs(idx),portNum);
                        else
                            bObj = SLGraphUtil.getOutportBlock(candidateSysHs(idx),portNum);
                        end
                        if ~strcmp(bObj.OutDataTypeStr,'Inherit: auto') ...
                                || ~strcmp(bObj.PortDimensions,'-1') ...
                                || ~strcmp(bObj.SampleTime,'-1') ...
                                || ~strcmp(bObj.SignalType,'auto') ...
                                || ~strcmp(bObj.VarSizeSig,'Inherit') ...
                                || ~strcmp(bObj.OutMin,'[]') ...
                                || ~strcmp(bObj.OutMax,'[]')
                            removeIdx(end+1) = idx;   %#ok<AGROW>
                            break;
                        end
                    end
                    % Consider some type of rule based on the rendering space of
                    % the subsystem and its potential impact when expanded into 
                    % its parent.
                end
            catch Mex %#ok<NASGU>
                % (XXX) Check exception ID and make sure it is expected
                removeIdx(end+1) = idx;   %#ok<AGROW>
            end
        end
    end
    candidateSysHs(removeIdx) = [];
    if isempty(candidateSysHs)
        trivialSys = [];
        return;
    end

    % Rule 2.
    % Filter systems that contain Goto Visibility Tags
    removeIdx = [];
    for idx = 1:numel(candidateSysHs)
        if ~ishandle(candidateSysHs(idx))
            removeIdx(end+1) = idx;   %#ok<AGROW>
        else
            sysH = candidateSysHs(idx);
            try
                contents = find_system(sysH, 'SearchDepth',1,'BlockType','GotoTagVisibility');
                if ~isempty(contents)
                    removeIdx(end+1) = idx;   %#ok<AGROW>
                end
            catch Mex
                % (XXX) Check exception ID and make sure it is expected
                removeIdx(end+1) = idx;   %#ok<AGROW>
            end
            try
                sysGotoFrom = find_system(sysH,'FindAll','on','RegExp','on','SearchDepth',1,'BlockType','Goto|From');
                sysGotoFromTag = get(sysGotoFrom,'GotoTag');
                parentGotoFrom = find_system(get_param(sysH,'Parent'),'FindAll','on','RegExp','on','SearchDepth',1,'BlockType','Goto|From','TagVisibility','local');
                parentGotoFromTag = get(parentGotoFrom,'GotoTag'); 
                if ~isempty(intersect(sysGotoFromTag,parentGotoFromTag))
                    removeIdx(end+1) = idx;   %#ok<AGROW>
                end
            catch Mex
                removeIdx(end+1) = idx;   %#ok<AGROW>
            end
        end
    end
    candidateSysHs(removeIdx) = [];
    if isempty(candidateSysHs)
        trivialSys = [];
        return;
    end
    
    % Rule 3. 
    % - No subsystems with more than 3 Non-virtual block 
    % - No atomic subsystems with more than 1 Non-virtual block 
    MaxNonVirt = 3;
    sysBlkCnt = containers.Map(candidateSysHs, ...
        zeros(1, numel(candidateSysHs), 'uint8'));

    nonVirtBlkHs = blkHs(strcmpi(get_param(blkHs, 'Virtual'),'off'));

    blkCnt = numel(nonVirtBlkHs);

    parents = get(nonVirtBlkHs, 'Parent');
    if ~iscell(parents)
        parents = {parents};
    end
    % filtering root-level blocks
    noPIdx = cellfun(@(x) isempty(x), parents);
    parents(noPIdx) = [];
    parentHs = get_param(parents,'Handle');

    parentHs = [parentHs{:}];
    for idx = 1:blkCnt
        parentH = parentHs(idx);
        if sysBlkCnt.isKey(parentH)
            thisSysCnt = sysBlkCnt(parentH) + 1;
            
            % Check is we have too many blocks
            if (thisSysCnt > MaxNonVirt) || (thisSysCnt>1 && ...
                strcmp(get_param(parentH, 'IsSubsystemVirtual'),'off'))
                sysBlkCnt.remove(parentH);
            else
                sysBlkCnt(parentH) = thisSysCnt;
            end
        end
        
        % Early return, if no candidate exists return now.
        if sysBlkCnt.isempty()
            break;
        end
    end
    trivialSys = sysBlkCnt.keys;
    trivialSys = [trivialSys{:}];
end

