function varargout = signalBuilderToSignalEditor( sbBlockH, varargin )
% SIGNALBUILDERTOSIGNALEDITOR imports signal data and properties from
%   Signal Builder block to Signal Editor block. If the dialog for the
%   Signal Builder block is open, it will be closed.
%
%   [SIGEDITBLOCKH, SORTGRPIDX, SORTGRPNAMES] = SIGNALBUILDERTOSIGNALEDITOR( SIGBLDRBLOCKH, 'ADDPARAMNAME', ADDPARAMVALUE )
%   adds a Signal Editor block to the current model using the signal data and
%   properties from the Signal Builder block.
%
%   INPUTS:
%       SIGBLDRBLOCKH: Signal Builder block handle or path.
%
%   Input parameter Name,Value pairs:
%   'FileName':    MAT-file that stores signals.
%                  Default: 'dataset.mat'
%                  Data types: character vector or string
%
%                  Do not use a file name from one locale in a different locale.
%                  When using the block on multiple platforms, consider specifying
%                  just the MAT-file name and having the MAT-file be on the MATLAB
%                  path.
%   'Replace':     Replace Signal Builder block with Signal Editor block.
%                  Default: false
%                  Data type: boolean
%
%                  false - The function uses the Signal Builder block name
%                  for the new Signal Editor block name. The Signal Editor
%                  block name is made unique by adding a numerical suffix.
%                  true - The function names the new Signal Editor block with
%                  the Signal Builder name and deletes the old Signal Builder block.
%
%   OUTPUT:
%       NEWBLOCKH:     Signal Editor or Subsystem block handle if there is a virtual bus.
%       SORTGRPIDX:    List of Signal Builder group indices.
%
%                      They are specified as a vector and ordered as they will
%                      appear in Signal Editor block.
%       SORTGRPNAMES:  List of Signal Builder group names.
%
%                      Signal Editor scenario names, specified as a cell array in
%                      alphabetical order.  The names are unique valid MATLAB
%                      variable names generated from the Signal Builder group names.
%       SIGEDITBLOCKH: Signal Editor block handle if there is a virtual bus.
%
%                      This is equivalent to NEWBLOCKH when there is no virtual bus.
%

% Copyright 2017-2023 The MathWorks, Inc.

narginchk( 1, 5 );
nargoutchk( 0, 4 );

% convert string to char; otherwise pass through
sbBlockH = convertStringsToChars(sbBlockH);

try
    objH = get_param(sbBlockH,'Handle');

catch invalidBlock
    %This means the block is not in an open/loaded model.
    if(~ischar(sbBlockH))
        error(message('sigbldr_api:signalbuilder:BlockNotOpen',inputname(1),inputname(1)));
    else
        error(message('sigbldr_api:signalbuilder:BlockNotOpen',sbBlockH,sbBlockH));
    end
end
if  ~is_signal_builder_block(sbBlockH)
    error(message('sigbldr_api:signalbuilder:invalidBlock',getfullname(sbBlockH)));
end

if strcmp(get_param(sbBlockH,'IOType'),'siggen')
    %This means the block is a Signal Generator which is not supported.
    %(see g1911151)
    if(~ischar(sbBlockH))
        error(message('sigbldr_api:signalbuilder:SigGenConversionNotSupported',inputname(1)));
    else
        error(message('sigbldr_api:signalbuilder:SigGenConversionNotSupported',sbBlockH));
    end
end

sbBlockH = objH;

modelH = bdroot(sbBlockH);
modelName = get_param(modelH, 'Name');

% check Simulink Design Verifier parameter settings on model InitFcn (g1679079)
initFcn = get_param(modelH, 'InitFcn');
% isSLDVGenHarness = Sldv.HarnessUtils.isSldvGenHarness( modelH ); % no need to search for sigbldr
try
    testHarness = get_param(modelH,'SldvGeneratedHarnessModel');
catch
    testHarness = [];
end

if ~isempty(initFcn) && ~isempty( testHarness )
    warning(message('sigbldr_api:signalbuilder:HasInitFcn', modelName ));
end

% check to see if model can be modified
if localIsSimulating(modelH)
    % Simulating cannot modify model
    error(message('sigbldr_api:signalbuilder:IsSimulating', modelName ));
end

if localIsFastRestart(modelH)
    % In Fast Restart cannot modify model
    error(message('sigbldr_api:signalbuilder:IsFastRestart', modelName ));
end

if localIsLocked(modelH)
    % In locked library cannot be modified
    error(message('sigbldr_api:signalbuilder:IsLocked', modelName ));
end

% Check if the Signal Builder block outputs a virtual bus
busCreatorH = find_system(sbBlockH,'MatchFilter',@Simulink.match.allVariants, 'FollowLinks', 'on', 'LookUnderMasks', 'all', 'BlockType', 'BusCreator' );
virtualBusOutport = ~isempty(busCreatorH);

inputStruct = parseInputs( varargin{:} );

FileName = convertStringsToChars(inputStruct.FileName);
Replace = inputStruct.Replace;

% get file parts
[path, name, ext] = fileparts(deblank(inputStruct.FileName));

% check file extension
if isempty(ext)
    % add .mat to filename if not there
    ext = '.mat';
    FileName = fullfile(path,[name ext]);
end

% get Name, Group, Parent and Position
grpIndex = signalbuilder(sbBlockH, 'activegroup');
positionSB = get_param(sbBlockH, 'Position');
blockNameSB = getfullname(sbBlockH);
blockBaseNameSB = get_param(sbBlockH, 'Name');

% get block properties
fontNameSB = get_param(sbBlockH, 'FontName');
fontSizeSB = get_param(sbBlockH, 'FontSize');
fontWeightSB = get_param(sbBlockH, 'FontWeight');
fontAngleSB = get_param(sbBlockH, 'FontAngle');
orientationSB = get_param(sbBlockH, 'Orientation');
fgColorSB = get_param(sbBlockH, 'ForegroundColor');
bgColorSB = get_param(sbBlockH, 'BackgroundColor');
shadowSB = get_param(sbBlockH, 'DropShadow');

% get all signal names
[~, ~, sigNames, ~] = signalbuilder(sbBlockH);

% if the dialog is not open get from From Workspace block (force dialog closed)
sbBlockUD = get_param(sbBlockH, 'UserData');
if ~isempty(sbBlockUD) && ishghandle(sbBlockUD, 'figure')
    figureUD = get(sbBlockUD, 'UserData');
    close_internal(figureUD);
end

% Get all outport properties
loggingProperties = {...
    'DataLogging',...
    'DataLoggingNameMode',...
    'DataLoggingName',...
    'DataLoggingDecimateData',...
    'DataLoggingDecimation',...
    'DataLoggingSampleTime',...
    'DataLoggingLimitDataPoints',...
    'DataLoggingMaxPoints'...
};
outportHandles = get_param(sbBlockH, 'PortHandles').Outport;
outportPropsSB = cell(1, length(outportHandles));
for i=1:length(outportHandles)
    tempProps = struct;
    for j=1:length(loggingProperties)
        tempProps.(loggingProperties{j}) = get_param(outportHandles(i), loggingProperties{j});
    end
    outportPropsSB{i} = tempProps;
end

% Get handle to From Workspace block of Signal Builder
fromWsH = find_system(sbBlockH,'MatchFilter',@Simulink.match.allVariants, 'FollowLinks', 'on', 'LookUnderMasks', 'all', 'BlockType', 'FromWorkspace' );

% get SampleTime
sampleTimeSB = get_param(fromWsH, 'SampleTime');

% get Interpolate
interpolateSB = get_param(fromWsH, 'Interpolate');

% get ZeroCross
zeroCrossSB = get_param(fromWsH, 'ZeroCross');

% get OutputAfterFinalValue
outputAfterFinalValueSB = get_param(fromWsH, 'OutputAfterFinalValue');

% extract signal data to mat-file, FileName
% get Signal Builder block userdata
UD = get_param(fromWsH, 'SigBuilderData');

% get number of groups
numGroups = length(UD.dataSet);

% Get requirements if Signal Builder block has requirements attached
for nameIdx = numGroups:-1:1
    requirements{nameIdx} = rmi('get', sbBlockH, nameIdx);
    reqEmpty(nameIdx) = isempty(requirements{nameIdx})*nameIdx;
    reqNumel(nameIdx) = numel(requirements{nameIdx});
end

% Make sure existing requirements can be written to Signal Editor block
if ~all(reqEmpty)
    % some requirements exist
    if ~license('test','Simulink_Requirements')
       % no Simulink Requirement license available
       % requirements will not be able to be set on Signal Editor block
       error(message('sigbldr_api:signalbuilder:NoRMILicense', blockNameSB ));
    end
end

% export all groups
groupIdx = 1:numGroups;
% call export to MAT-File
dsNameCell = exportMatFile( FileName, UD, groupIdx, false );

if Replace
    % remove Signal Builder block
    delete_block(sbBlockH);

    % set position with offset for Signal Editor block
    % to avoid connecting incorrect ports before setting data on block
    offsetY = positionSB(4) - positionSB(2) + 10;
    offsetX = positionSB(3) - positionSB(1) + 10;

    % set position for Signal Editor block
    positionSE = positionSB;
    positionSEInit = positionSB + [offsetX offsetY offsetX offsetY];
else
    % set position with offset for Signal Editor block
    % to avoid placing block behind existing Signal Builder
    offset = positionSB(4) - positionSB(2) + 10;
    positionSE = positionSB + [0 offset 0 offset];
    positionSEInit = positionSE;
end

% If the Signal Builder block had a virtual bus outport, create a subsystem
% that will contain a Signal Editor, a Bus Creator, and an Outport
blockNameSE = blockNameSB;
newBlockH = [];
if virtualBusOutport
    % Note - use 'built-in/SubSystem' to create an empty subsystem.
    %      - 'simulink/Ports & Subsystems/Subsystem' contains an inport and outport,
    %        which we would need to delete/modify.
    subSysBlock = add_block('built-in/SubSystem', blockNameSB, 'MakeNameUnique', 'on', 'Position', positionSEInit);
    newName = getfullname(subSysBlock);
    blockNameSE = [newName, '/', strrep(blockBaseNameSB, '/', '//')];
    newBlockH = get_param(subSysBlock, 'Handle');
end

% add Signal Editor block to model
block = add_block('simulink/Sources/Signal Editor', blockNameSE, ...
    'MakeNameUnique', 'on', 'FileName', FileName);

signalEditorBlockH = get_param(block,'Handle');

if ~virtualBusOutport
    set_param(signalEditorBlockH, 'Position', positionSEInit);
    newBlockH = signalEditorBlockH;
end

% set block properties
set_param(newBlockH, 'FontName', fontNameSB, 'FontWeight', fontWeightSB, 'FontSize', fontSizeSB,...
                  'FontAngle', fontAngleSB, 'Orientation', orientationSB, ...
                  'ForegroundColor', fgColorSB, 'BackgroundColor', bgColorSB, ...
                  'DropShadow', shadowSB);

set_param(newBlockH, 'Position', positionSE ); % work around g1656580

% logic to allow setting values if not the default value for the Signal Editor Block
setSampleTime = ~strcmp(sampleTimeSB, '0');
setInterpolate = ~strcmp(interpolateSB, 'off');
setZeroCross = ~strcmp(zeroCrossSB, 'off');
setoutputAfterFinalValue = ~strcmp(outputAfterFinalValueSB, 'Setting to zero');

if (setSampleTime || setInterpolate || setZeroCross || setoutputAfterFinalValue)
    % set signal properties to signal builder settings
    for idxSignal = 1:length(sigNames)
        % set ActiveSignal
        set_param(signalEditorBlockH, 'ActiveSignal', sigNames{idxSignal});

        % set SampleTime, Interpolate, ZeroCross, and OutputAfterFinalValue
        set_param(signalEditorBlockH, 'SampleTime', sampleTimeSB, ...
            'Interpolate', interpolateSB, 'ZeroCross', zeroCrossSB, ...
            'OutputAfterFinalValue', outputAfterFinalValueSB);
    end
end

% set ActiveScenario
set_param(signalEditorBlockH, 'ActiveScenario', dsNameCell{grpIndex});

% If Virtual Bus Output, add a Bus Creator and Outport block
if virtualBusOutport
    createVirtualBus(newBlockH, signalEditorBlockH, positionSE);
end

% Set the signal logging properties
newOutportHandles = get_param(newBlockH, 'PortHandles').Outport;
if length(newOutportHandles) == length(outportPropsSB)
    for i=1:length(outportPropsSB)
        propStruct = outportPropsSB{i};
        if length(fieldnames(propStruct)) == length(loggingProperties)
            % Check if values are different than default (skip DataLoggingName since DataLoggingNameMode determines its default value)
            if (~strcmp(propStruct.DataLogging, 'off') ||...
                ~strcmp(propStruct.DataLoggingNameMode, 'SignalName') ||...
                ~strcmp(propStruct.DataLoggingDecimateData, 'off') ||...
                ~strcmp(propStruct.DataLoggingDecimation, '2') ||...
                ~strcmp(propStruct.DataLoggingSampleTime, '-1') ||...
                ~strcmp(propStruct.DataLoggingLimitDataPoints, 'off') ||...
                ~strcmp(propStruct.DataLoggingMaxPoints, '5000'))
                set_param(newOutportHandles(i),...
                    'DataLogging', propStruct.DataLogging,...
                    'DataLoggingNameMode', propStruct.DataLoggingNameMode,...
                    'DataLoggingName', propStruct.DataLoggingName,...
                    'DataLoggingDecimateData', propStruct.DataLoggingDecimateData,...
                    'DataLoggingDecimation', propStruct.DataLoggingDecimation,...
                    'DataLoggingSampleTime', propStruct.DataLoggingSampleTime,...
                    'DataLoggingLimitDataPoints', propStruct.DataLoggingLimitDataPoints,...
                    'DataLoggingMaxPoints', propStruct.DataLoggingMaxPoints);
            end
        else
            error(message('sigbldr_api:signalbuilder:OutportLoggingParamMismatch', newBlockH));
        end
    end
else
    error(message('sigbldr_api:signalbuilder:OutportMismatch', newBlockH, length(newOutportHandles), length(outportPropsSB)));
end

% get sorted group names and the sorting index
[sortGrpNames, sortGrpIdx] = sort(dsNameCell);

% set requirements on block
if ~all(reqEmpty)
    % some requirements exist. add requirements to Signal Editor block
    reqCnt = 0;
    for grpIdx = sortGrpIdx
        if ~any(reqEmpty == grpIdx)
            % only action non-empty cell arrays
            for idx = 1:reqNumel(grpIdx)
                reqCnt = reqCnt + 1;
                reqSort(reqCnt) = requirements{grpIdx}(idx); %#ok<AGROW>
                % Prepend Signal Editor group name to keywords
                reqSort(reqCnt).keywords = strtrim([sortGrpNames{sortGrpIdx(grpIdx)} ' ' reqSort(reqCnt).keywords]); %#ok<AGROW>
            end
        end
    end
    % set first requirement on Signal Editor block
    rmi('set', signalEditorBlockH, reqSort(1) );
    if (length(reqSort) > 1)
        % more than one requirement to set on Signal Editor block
        for reqIdx = 2:length(reqSort)
            rmi('cat', signalEditorBlockH, reqSort(reqIdx) );
        end
    end
end

if nargout > 0
    % return block handle to signal editor block or subsystem
    varargout{1} = newBlockH;
    if nargout > 1
        % sorting indices
        varargout{2} = sortGrpIdx;
        if nargout > 2
            % sorted group names
            varargout{3} = sortGrpNames;
            if nargout > 3
                % handle to signal editor block
                varargout{4} = signalEditorBlockH;
            end
        end
    end
end

%--------------------------------------------------------------------------
function inputStruct = parseInputs( varargin )

p = inputParser;

addParameter( p, 'FileName', 'dataset.mat', @isValidFileName);
addParameter( p, 'Replace',  false,         @isScalarLogical);

parse(p,varargin{:});
inputStruct = p.Results;

%--------------------------------------------------------------------------
function isValidFileName(input)

if isempty(input) || (~ischar(input) && ~isstring(input))
    error(message('sigbldr_api:signalbuilder:NotCharFileName'));
end

% convert string to char
input = convertStringsToChars(input);

% get file parts
[~, ~ ,ext] = fileparts(deblank(input));

if ~isempty(ext) && ~strcmpi(ext,'.mat')
    % if not a MAT-file error
    error(message('sigbldr_api:signalbuilder:NotMATFileName'));
end

%--------------------------------------------------------------------------
function isScalarLogical(input)

if isempty(input)
    error(message('sigbldr_api:signalbuilder:NotScalarLogicalReplace'));
end

if ~(islogical(input) && isscalar(input))
    error(message('sigbldr_api:signalbuilder:NotScalarLogicalReplace'));
end

%--------------------------------------------------------------------------
function isSimulating = localIsSimulating(modelH)
% determine if model is simulating
isSimulating = false;

if ~strcmp(get_param(modelH, 'simulationStatus'), 'stopped') && get_param(modelH ,'InteractiveSimInterfaceExecutionStatus') ~= 2
    isSimulating = true;
end

%--------------------------------------------------------------------------
function isFastRestart = localIsFastRestart(modelH)
% determine if model is in fast restart

isFastRestart = false;

if strcmp(get_param(modelH,'InitializeInteractiveRuns'),'on')
    isFastRestart = true;
end

%--------------------------------------------------------------------------
function isLocked = localIsLocked(modelH)
% determine if model is in locked library

isLocked = false;

if strcmpi(get_param(modelH, 'lock'), 'on')
    isLocked = true;
end

%--------------------------------------------------------------------------
function portName = getPortName(blockH, signalNumStr)
% Given a block handle and port number, return the path to a name for the port,
% accounting for slash characters within the block name.

blockName = get_param(blockH, 'Name');
fixedBlockName = strrep(blockName, '/', '//');
portName = [fixedBlockName, '/', signalNumStr];


%--------------------------------------------------------------------------
function createVirtualBus(subSysH, signalEditorH, subSysPos)
% Given a subsystem and its internal signal editor block, add a bus creator
% and an outport to the subsystem.

subSysBlock = getfullname(subSysH);
signalEditorPosition = get_param(signalEditorH, 'Position');

% Add Bus Creator block
nSignals = length(get_param(signalEditorH, 'PortHandles').Outport);
busCreatorH = add_block('simulink/Signal Routing/Bus Creator', [subSysBlock, '/Bus Creator'],...
    'Tag',     'Bus Creator', ...
    'Inputs',  num2str(nSignals));

% Reposition Bus Creator
blockDistance = 150;
signalEditorHeightMid = (signalEditorPosition(2) + signalEditorPosition(4)) / 2;
origBusCreatorPos = get_param(busCreatorH, 'Position');
busCreatorWidth = origBusCreatorPos(3) - origBusCreatorPos(1);
busCreatorHeight = origBusCreatorPos(2) - origBusCreatorPos(4);
newBusCreatorPos = [signalEditorPosition(1) + blockDistance,...
    signalEditorHeightMid + (busCreatorHeight / 2),...
    (signalEditorPosition(1) + blockDistance) + busCreatorWidth,...
    signalEditorHeightMid - (busCreatorHeight / 2)];
set_param(busCreatorH, 'Position', newBusCreatorPos);

% Attach signals from Signal Editor to Bus Creator
for i = 1:nSignals
    i_str = num2str(i);
    signalEditorOut = getPortName(signalEditorH, i_str);
    busCreatorIn =  getPortName(busCreatorH, i_str);
    add_line(subSysBlock, signalEditorOut, busCreatorIn);
end

% Add Outport block
% Note: Signal Builder hard-codes the outport name to 'Bus' when a virtual
% bus is being output, so we are replicating that here.
outportH = add_block('simulink/Sinks/Out1', [subSysBlock, '/Bus']);

% Reposition Outport block
origOutportPos = get_param(outportH, 'Position');
outportWidth = origOutportPos(3) - origOutportPos(1);
outPortHeight = origOutportPos(2) - origOutportPos(4);
newOutportPos = [newBusCreatorPos(1) + blockDistance,...
    signalEditorHeightMid + (outPortHeight / 2),...
    (newBusCreatorPos(1) + blockDistance) + outportWidth,...
    signalEditorHeightMid - (outPortHeight / 2)];
set_param(outportH, 'Position', newOutportPos);

% Attach Bus Creator to Outport
add_line(subSysBlock, 'Bus Creator/1', 'Bus/1');

% Now that the outport exists, reset position of the subsystem to cause the outport to
% re-connect to the existing inport of whatever the Signal Builder was connceted to.
set_param(subSysH, 'Position', subSysPos);