function filename = save_system(varargin)
%SAVE_SYSTEM Save a Simulink system.
%   SAVE_SYSTEM saves the current top-level system to a file with its
%   current name.
%
%   SAVE_SYSTEM('SYS') saves the specified top-level system to a file with
%   its current name.  The system must be currently open.
%     SYS can be a string, a cell array of strings, a numeric handle, or
%     an array of numeric handles.
%
%   SAVE_SYSTEM('SYS','NEWNAME') saves the specified top-level system to a
%   file with the specified new name.  The system must be currently open.
%   NEWNAME can be empty, in which case the current name is used.
%     If SYS refers to more than one block diagram, NEWNAME must be a cell
%     array of new names.
%
%   Additional arguments must be supplied as name-value pairs after the NEWNAME
%   argument (which can be empty).  Allowed names are:
%     ErrorIfShadowed: true or false (default: false)
%     BreakUserLinks: true or false (default: false)
%     OverwriteIfChangedOnDisk: true or false (default: false)
%     SaveModelWorkspace: true or false (default: false)
%     SaveDirtyReferencedModels: true or false (default: false)
% The same options are applied to all the block diagrams which are saved.
% Options are case-insensitive on all platforms.
%
% SAVE_SYSTEM returns the full name of the file which was saved, as a string.
% If multiple files were saved, the return value is a cell array of strings.
%
% Examples:
%   save_system('mymodel')
%   save_system('mymodel','mynewmodel')
%   save_system('mymodel',OverwriteIfChangedOnDisk=true)

%   Copyright 1996-2021 The MathWorks, Inc.

try
    if nargin==0
        % Need to treat this as a special case.  If we have a default value
        % for the first argument in the "arguments" block in i_save_system then
        % it's possible for the first two inputs to be treated as a name-value
        % pair.
        f = i_save_system(gcs);
    else
        f = i_save_system(varargin{:});
    end
catch E
    throw(E); % suppress internal stack trace
end

if nargout
    filename = f;
end

end

%--------------------
function filename = i_save_system(sys,newsys,opts)

arguments
    sys {handlesOrStrings}; % no default value - see explanation above
    newsys {stringsOrEmpty} = [];
    opts.ErrorIfShadowed {logicalOrOnOff} = false;
    opts.OverwriteIfChangedOnDisk {logicalOrOnOff} = false;
    opts.SaveModelWorkspace {emptyOrLogicalOrOnOff} = [];
    opts.BreakUserLinks {logicalOrOnOff} = false;
    opts.BreakToolboxLinks {logicalOrOnOff} = false;
    opts.BreakLinks {logicalOrOnOff} = false;
    opts.BreakAllLinks {logicalOrOnOff} = false;
    opts.SaveDirtyReferencedModels {logicalOrOnOff} = false;
    opts.ExportToVersion(1,1) string = "";
    opts.SaveAsVersion(1,1) string = "";
    opts.TargetReleaseFormat(1,1) string = "";
    opts.ExportToXML {logicalOrOnOff} = false;
    opts.AllowPrompt {logicalOrOnOff} = false;
    opts.SkipSaveFcns {logicalOrOnOff} = false;
    opts.SaveWindowConfig {logicalOrOnOff} = false; % only used when SimulinkViewStateManagement feature is on
end

% Sort out input arguments.

is_return_cellarray = isnumeric(sys);
[sys,newsys] = i_getsys(sys,newsys);

opts.ErrorIfShadowed = i_onoff(opts.ErrorIfShadowed);
opts.OverwriteIfChangedOnDisk = i_onoff(opts.OverwriteIfChangedOnDisk);
opts.SaveModelWorkspace = i_onoff(opts.SaveModelWorkspace);
opts.BreakUserLinks = i_onoff(opts.BreakUserLinks);
opts.BreakToolboxLinks = i_onoff(opts.BreakToolboxLinks);
opts.BreakLinks = i_onoff(opts.BreakLinks);
opts.BreakAllLinks = i_onoff(opts.BreakAllLinks);
opts.SaveDirtyReferencedModels = i_onoff(opts.SaveDirtyReferencedModels);
opts.ExportToXML = i_onoff(opts.ExportToXML);
opts.AllowPrompt = i_onoff(opts.AllowPrompt);
opts.SkipSaveFcns = i_onoff(opts.SkipSaveFcns);
opts.SaveWindowConfig = i_onoff(opts.SaveWindowConfig);

if strlength(opts.SaveAsVersion)
    % Legacy synonym for ExportToVersion.
    if strlength(opts.ExportToVersion)
        error(message('Simulink:Commands:SaveSystemIncompatibleOptions'));
    end
    opts.ExportToVersion = opts.SaveAsVersion;
end

if any(opts.ExportToXML) && strlength(opts.ExportToVersion)
    error(message('Simulink:Commands:SaveSystemIncompatibleOptions'));
end

if any(opts.BreakLinks)
    warning(message('Simulink:Commands:SaveSysBreakLinksDeprecated'));
end

% Prevent editor UI events (ex: MouseMoveEvent) from getting processed during save callbacks, g1750126
scopedMessageBlocker = [];
if ~isempty(bdroot) && get_param(bdroot, 'open') == "on"
    scopedMessageBlocker = feval('SLM3I.ScopedGLIMMessageBlocker'); %#ok<FVAL> feval to avoid load on file parse
end

opts = i_tidyopts(opts);
if numel(sys)==1
    % Save one model.
    filename = i_save(sys,newsys,opts);
    if is_return_cellarray
        filename = {filename};
    end
else
    % Save multiple models.  Collect any errors to report at the end.
    if isempty(newsys)
        newsys = strings(size(sys));
    else
        newsys = string(newsys);
    end
    filename = cell(size(sys));
    collected_errors = [];
    for i=1:numel(sys)
        try
            filename{i} = i_save(sys(i),newsys(i),opts);
        catch E
            collected_errors = [collected_errors E]; %#ok<AGROW>
        end
    end
    if ~isempty(collected_errors)
        if numel(collected_errors)==1
            throw(collected_errors);
        else
            me = MException(message('Simulink:Commands:SaveSystemFailed'));
            for i=1:numel(collected_errors)
                me = me.addCause(collected_errors(i));
            end
            throw(me);
        end
    end
end

delete(scopedMessageBlocker);

end

%------------------
% Destination model names/files, or empty if we're using the default.
function stringsOrEmpty(v)
if isempty(v)
    return;
end
if ~isstring(v) && ~ischar(v) && ~iscellstr(v)
    error(message('Simulink:Commands:SaveSysBadSaveAsName'));
end
end

%------------------
% Supplied model names or handles
function handlesOrStrings(v)
if isnumeric(v)
    return;
end
if ~isstring(v) && ~ischar(v) && ~iscellstr(v)
    error(message('Simulink:Commands:InvSimulinkObjSpecifier'));
end
end

%------------------
% The values for our name-value pairs can be "on" or "off" of a logical
% scalar.
function logicalOrOnOff(v)
if islogical(v) && isscalar(v)
    return;
end
if isnumeric(v) && isscalar(v)
    if v==0 || v==1
        return;
    end
end
if isStringScalar(v) || ischar(v)
    if v=="on" || v=="off"
        return;
    end
end
error(message('Simulink:Commands:MustBeLogicalOrOnOff'));
end

%------------------
% Special handling for the "SaveModelWorkspace" flag because we need to be
% able to detect that it wasn't specified.
function emptyOrLogicalOrOnOff(v)
if isempty(v)
    return; % unspecified
end
logicalOrOnOff(v);
end

%------------------
function out = i_onoff(val)
% Convert a value already validated by logicalOrOnOff into a logical.
if isempty(val)
    out = []; % Special case for SaveModelWorkspace
elseif islogical(val)
    out = val;
elseif isnumeric(val)
    out = logical(val);
else
    s = string(val);
    out = s=="on";
end
end

%----------------------
function filename = i_save(sys,newsys,opts)
% Call Simulink.internal.saveSystem or Simulink.exportToVersion as
% appropriate.
if strlength(opts.ExportToVersion)
    exportVer = opts.ExportToVersion;
    % Only 3 options are valid when passing through to
    % Simulink.exportToVersion.
    filename = Simulink.exportToVersion(sys,newsys,exportVer,...
        'AllowPrompt',opts.AllowPrompt,...
        'BreakUserLinks',opts.BreakUserLinks,...
        'BreakToolboxLinks',opts.BreakToolboxLinks);
else
    filename = Simulink.internal.saveSystem(sys,newsys,opts);
end
end

%----------------------
function [sys_out,newsys_out] = i_getsys(sys,newsys)
% Identify the system(s) referred to by the first input to save_system, and
% return their numeric handles.
if isnumeric(sys)
    sys_out = zeros(size(sys));
    for i=1:numel(sys)
        sys_out(i) = Simulink.internal.resolveSystem(sys(i),[]);
    end
    if ~isempty(newsys)
        newsys_out = string(newsys);
    else
        newsys_out = [];
    end
else
    sys = string(sys);
    if ~isempty(newsys)
        newsys = string(newsys);
        if ~isstring(newsys) && ~ischar(newsys) && ~iscellstr(newsys)
            error(message('Simulink:Commands:SaveSysBadSaveAsName'));
        end
        if numel(newsys) ~= numel(sys)
            error(message('Simulink:Commands:InputArgSizeMismatch'));
        end
    else
        newsys = strings(size(sys));
    end
    sys_out = zeros(size(sys));
    newsys_out = strings(size(sys));
    for i=1:numel(sys)
        [sys_out(i),newsys_out(i)] = Simulink.internal.resolveSystem(sys(i),newsys(i));
    end
end

end

%----------------------
function opts = i_tidyopts(opts)
% Remove superfluous fields from "opts"
if opts.BreakAllLinks || opts.BreakLinks
    opts.BreakUserLinks = true;
    opts.BreakToolboxLinks = true;
end
opts = rmfield(opts,["SaveAsVersion","BreakAllLinks","BreakLinks"]);
end
