function varargout = mpcDesigner(data, varargin)
% MPCDESIGNER open the MPC Designer app in MATLAB.
%
% mpcDesigner() opens MPC Designer app.  You can start the design process
% by importing a plant or a controller, or open a saved design session.
%
% mpcDesigner(mpcobj) designs the specified MPC controller in the app.
%
% mpcDesigner(mpcobjs) designs a set of MPC controllers in the app.
% "mpcobjs" must be a cell array of MPC controllers with the same i/o
% channel configuration.
%
% mpcDesigner(mpcobjs, names) allow you to specify a name for each
% controller in the app.  "names" must be a cell array of unique strings
% with the same length as mpcobjs.
%
% mpcDesigner(lti_plant) generates a default MPC controller using a linear
% time invariant system as the prediction model and launch MPC Designer
% app.
%   (1) "lti_plant" is a single LTI model in "tf", "zpk" or "ss" format.  
%   (2) By default, all the inputs are manipulated variables (MVs) and all
%       the outputs are measured outputs (MOs).  
%   (3) To specify custom signal types for each i/o channel, use
%       "setmpcsignals" command on "lti_plant" before launching the app.
%   (4) If the plant is stable and in continuous time, app automatically
%       chooses an initial sample time to faciliate controller design.
%
% mpcDesigner(ident_plant) generates a default MPC controller using a
% linear system from System Identification Toolbox as the prediction model
% and launch MPC Designer app.
%   (1) "ident_plant" is a single "idtf", "idss", "idproc", "idpoly" or
%       "idgrey" system
%   (2) By default, all the inputs are manipulated variables (MVs) and all
%       the outputs are measured outputs (MOs).  
%   (3) To specify custom signal types for each i/o channel, use
%       "setmpcsignals" command on "ident_plant" before launching the app.
%   (4) To include noise channels in deisgn, convert the ident model to
%       state space system by using "ss(ident_plant,'aug')" in advance.
%   (5) If the plant is stable and in continuous time, app automatically
%       chooses an initial sample time to faciliate controller design.
% 
% Both "lti_plant" and "ident_plant" systems cannot have direct feedthrough
% between a manipulated variable and any output.
%
% mpcDesigner(SESSIONDATA) opens the app with a previously saved session
% where SESSIONDATA is either a SessionData variable in the workspace or a
% MAT file that stores the saved session.
%
% See also MPC

%	Author:  R.Chen
%	Copyright 2015 The MathWorks, Inc.

% check input number
narginchk(0,2)
% check input data
if nargin==0
    % launch with nothing
    app = mpcdesigner.MPCDesigner();
elseif nargin==1
    data = controllib.internal.util.hString2Char(data);
    if isa(data, 'mpc')
        % single mpc
        % check hessian and estimator validity
        [result, msg] = mpcdesigner.util.checkHessian(data);
        switch result
            case 0
                app = mpcdesigner.MPCDesigner(data, inputname(1));
            case 1
                app = mpcdesigner.MPCDesigner();
                msg = getString(message('MPC:mpcdesigner:ErrorHessianLargeCommandLine'));
                title = getString(message('MPC:mpcdesigner:TitleErrorDialog'));
                uialert(app.AppContainer,msg,title);
            case 2
                ctrlMsgUtils.error('MPC:mpcdesigner:ErrorDefaultEstimator');
            case 3
                ctrlMsgUtils.error('MPC:mpcdesigner:ErrorOtherBadSituation', msg);
        end
    elseif iscell(data)
        if all(cellfun('isclass',data,'mpc')) && ~any(cellfun(@isempty,data))
            % mpc cell array
            OK = localCheckCompatible(data);
            if OK
                app = mpcdesigner.MPCDesigner(data);
            else
                ctrlMsgUtils.error('MPC:mpcdesigner:IncompatibleMPCObjects');            
            end
        else
            ctrlMsgUtils.error('MPC:mpcdesigner:ErrorCellArrayOfMPCObjects');            
        end
    elseif isa(data,'mpcapp.data.SessionData') 
        % old session data object
        data.AppData.Plants = mpcdesigner.data.PlantWrapper.convert(data.AppData.Plants);
        data.AppData.Controllers = mpcdesigner.data.ControllerWrapper.convert(data.AppData.Controllers);
        data.AppData.Scenarios = mpcdesigner.data.ScenarioWrapper.convert(data.AppData.Scenarios);
        app = localLoadSessionData(data);
    elseif isa(data,'mpcdesigner.data.SessionData')
        % new session data object
        app = localLoadSessionData(data);
    elseif ischar(data)
        % MAT file
        try
            session_data = mpcdesigner.util.loadValidateSessionFile(data);
        catch ME
            throw(ME);
        end
        app = localLoadSessionData(session_data);
    else
        % if it is one of idss, idtf, idproc, idpoly, idgrey, convert to SS
        % without noise channel
        if isa(data,'idParametric')
            data = ss(data);
        end
        % assume to be a single valid lti plant.  error our if not
        [pass, ~, id] = mpcdesigner.util.checkPlant(data);
        if ~pass
            ctrlMsgUtils.error(id);
        end
        % check hessian and estimator validity
        ts = mpcdesigner.util.getInitialTs(data);
        [result, msg] = mpcdesigner.util.checkHessian(data, ts);
        switch result
            case 0
                app = mpcdesigner.MPCDesigner(data, inputname(1));
            case 1
                app = mpcdesigner.MPCDesigner();
                msg = getString(message('MPC:mpcdesigner:ErrorHessianLargeCommandLine'));
                title = getString(message('MPC:mpcdesigner:TitleErrorDialog'));
                uialert(app.AppContainer,msg,title);
            case 2
                ctrlMsgUtils.error('MPC:mpcdesigner:ErrorDefaultEstimator');
            case 3
                ctrlMsgUtils.error('MPC:mpcdesigner:ErrorOtherBadSituation', msg);
        end
    end
else
    % mpc cell array with names
    data = controllib.internal.util.hString2Char(data(:));
    if iscell(data) && all(cellfun('isclass',data,'mpc')) && ~any(cellfun(@isempty,data))
        % mpc cell array
        OK = localCheckCompatible(data);
        if ~OK
            ctrlMsgUtils.error('MPC:mpcdesigner:IncompatibleMPCObjects');            
        end
        names = varargin{1};
        if iscell(names) && all(cellfun('isclass',names,'char'))
            names = unique(names(:),'stable');
            if length(names)==length(data)
                app = mpcdesigner.MPCDesigner(data, names);
            else
                ctrlMsgUtils.error('MPC:mpcdesigner:IncompatibleMPCNames');
            end
        else
            ctrlMsgUtils.error('MPC:mpcdesigner:IncompatibleMPCNames');
        end
    else
        ctrlMsgUtils.error('MPC:mpcdesigner:ErrorFirstInput');            
    end
end
if nargout
    varargout{1} = app;
end

function IsCompatible = localCheckCompatible(data)
if length(data(:))==1
    IsCompatible = true;
else
    for ct=1:length(data(:))
        MV{ct} = getMVIndex(data{ct}); %#ok<*AGROW>
        MD{ct} = getMDIndex(data{ct});
        UD{ct} = getUDIndex(data{ct});
        MO{ct} = getMYIndex(data{ct});
        UO{ct} = getUYIndex(data{ct});
    end
    IsCompatible = isequal(MV{:}) && isequal(MD{:}) && isequal(UD{:}) && isequal(MO{:}) && isequal(UO{:});
end

function app = localLoadSessionData(data)
if isempty(data.AppData.MPCBlockPath)
    % ml session
    % open an empty app
    app = mpcdesigner.MPCDesigner();
    % load session data
    loadSession(app, data);
else
    % sl session
    % open SL model
    mdl = data.AppData.SimulinkModel;
    open_system(mdl);
    % open an empty app
    blk = data.AppData.MPCBlockPath;
    tmp = get_param(blk,'mpcobj');
    set_param(blk,'mpcobj','');
    blkh = get_param(blk,'Handle');
    mgr = mpcdesigner.manager.AppManager.getInstance();
    app = mgr.open(blkh);       
    if isInitialized(app.AppData)
        % if old app exists, delete it first
        delete(app);
        app = mgr.open(blkh);       
    end
    set_param(blk,'mpcobj',tmp);
    % load session
    loadSession(app,data);
end
