function [ViewFig,ViewObj] = linearSystemAnalyzer(varargin)
%linearSystemAnalyzer  Opens the Linear System Analyzer App.
%
%   linearSystemAnalyzer opens the Linear System Analyzer.  The Linear
%   System Analyzer is an interactive user interface for analyzing the time
%   and frequency responses of linear systems and comparing such systems.
%   See LTIMODELS for details on how to model linear systems in the Control
%   System Toolbox.
%
%   linearSystemAnalyzer(SYS1,SYS2,...,SYSN) opens a Linear System Analyzer
%   containing the step response of the LTI models SYS1,SYS2,...,SYSN.  You
%   can specify a distinctive color, line style, and marker for each
%   system, as in
%      sys1 = rss(3,2,2);
%      sys2 = rss(4,2,2);
%      linearSystemAnalyzer(sys1,'r-*',sys2,'m--');
%
%   linearSystemAnalyzer(PLOTTYPE,SYS1,SYS2,...,SYSN) further specifies
%   which responses to plot in the Linear System Analyzer.  PLOTTYPE may be
%   any of the following strings (or a combination thereof):
%        1) 'step'           Step response
%        2) 'impulse'        Impulse response
%        3) 'lsim'           Linear simulation plot
%        4) 'initial'        Initial condition plot
%        5) 'bode'           Bode diagram
%        6) 'bodemag'        Bode Magnitude diagram
%        7) 'nyquist'        Nyquist plot
%        8) 'nichols'        Nichols plot
%        9) 'sigma'          Singular value plot
%       10) 'pzmap'          Pole/Zero map
%       11) 'iopzmap'        I/O Pole/Zero map
%   For example,
%      linearSystemAnalyzer({'step';'bode'},sys1,sys2)
%   shows the step and Bode responses of the LTI models SYS1 and SYS2.
%
%   linearSystemAnalyzer(PLOTTYPE,SYS,EXTRAS) allows you to specify the
%   additional input arguments supported by the various response types. See
%   the HELP text for each response type for more details on the format of
%   these extra arguments. Note that specifying plot or data options is not
%   supported. Use the Preferences dialog to modify plots after launching
%   the Linear System Analyzer. If an LSIM plot is specified without
%   additional input arguments, the Linear Simulation Tool automatically
%   opens so that initial states and/or driving inputs can be assigned
%   interactively.
%
%   H = linearSystemAnalyzer(...) opens a Linear System Analyzer and
%   returns the handle to the Linear System Analyzer figure.
%
%   Two additional options are available for manipulating previously
%   opened Linear System Analyzers:
%
%   linearSystemAnalyzer('clear',VIEWERS) clears the plots and data from
%   the Linear System Analyzers with handles VIEWERS.
%
%   linearSystemAnalyzer('current',SYS1,SYS2,...,SYSN,VIEWERS) adds the
%   responses of the systems SYS1,SYS2,... to the Linear System Analyzers
%   with handles VIEWERS.
%
%   See also STEP, IMPULSE, LSIM, INITIAL, LTI/IOPZMAP, PZMAP,
%            BODE, LTI/BODEMAG, NYQUIST, NICHOLS, SIGMA.

%   Copyright 1986-2021 The MathWorks, Inc.

varargin = controllib.internal.util.hString2Char(varargin);

ni = nargin;
plottype = {};
currentflag = false;
argoffset = 0;
if ni > 0 && ischar(varargin{1})
   varargin{1} = varargin(1);  % convert first char argument to cellstr
end

% Process first string argument
if ni > 0 && iscellstr(varargin{1})
   FirstArg = varargin{1};
   switch FirstArg{1}
      case 'current'
         currentflag = true;
         ViewerHandles = varargin{end};
         varargin = varargin(2:end-1);
         plottype = {};
      case 'clear'
         LocalClearViewer(varargin{2});
         return
      otherwise
         plottype = FirstArg;
         varargin = varargin(2:end);
   end
   argoffset = 1;
end

% Plot Types checking
ValidTypes = ltiplottypes('Alias');
if ~isempty(setdiff(plottype,ValidTypes))
   error(message('Control:analysis:ltiview1'))
elseif length(plottype)>6
   error(message('Control:analysis:ltiview2'))
end
SINGLEPLOT = isscalar(plottype);

% Get Systems from Input List
try
   InNames = cell(length(varargin),1);
   for xx=1:length(varargin)
      InNames{xx} = inputname(argoffset + xx);
      % Convert Ident models to LTI models
      argj = varargin{xx};
      if isa(argj,'idlti')
         varargin{xx} = LocalCheckIDLTI(argj);
      end
   end
   [sysList,ExtraArg,plotOptions] = DynamicSystem.parseRespFcnInputs(varargin,InNames);
   % Throw error if plot options or data options are used in input
   % arguments, or if extra arguments are used with multiple plot types.
   if ~(isempty(ExtraArg) || SINGLEPLOT) || ~isempty(plotOptions)
      error(message('Control:analysis:ltiview3'))
   end
catch E
   throw(E);
end
   
% Current option
if currentflag
   LocalCurrentViewer({sysList.System},{sysList.Name},ViewerHandles);
   return
end

% Check compatibility and create LTI sources
try
   % Single plot type: treat as call to corresponding command and check compatibility
   if SINGLEPLOT
      [sysList,Settings,CheckExtra] = localSinglePlotCheck(plottype{1},sysList,ExtraArg);
   end
   src = handle([]);
   for ct=1:length(sysList)
      src(ct,1) = getPlotSource(sysList(ct).System,sysList(ct).Name);
      if SINGLEPLOT
         checkCompatibility(src(ct,1),plottype{1},CheckExtra{:});
      end
   end
catch ME
   throw(ME)
end

% Show waitbar
hWaitbar = waitbar(0,getString(message('Control:viewer:msgLTIViewerWaitBar')));

% Create an instance of the Viewer
ViewObj = viewgui.ltiviewer;
if SINGLEPLOT && ~isempty(Settings)
   % Single plot: Apply extra settings (e.g., time vector)
   localSinglePlotSettings(ViewObj,plottype{1},sysList,Settings);
end
waitbar(0.25,hWaitbar);

% Set sources
ViewObj.Systems = src;
waitbar(0.5,hWaitbar);

% Set current views
if isempty(plottype)
   plottype = {'step'};
end
ViewObj.setCurrentViews(plottype);
waitbar(0.75,hWaitbar);

% Set Viewer Visibility 
set(ViewObj.Figure,'Visible','on');

% To trigger exception for invalid data/plottype
ViewObj.notify('ModelImport',controllib.app.internal.GenericEventData(src));
waitbar(1,hWaitbar);

% Set PlotStyles if any
for ct = 1:length(sysList)
   if ~isempty(sysList(ct).Style)
      ViewObj.setstyle(ViewObj.Systems(ct),sysList(ct).Style);
   end
end
close(hWaitbar);
%
% Call the start-up message box
LocalStartUpMsgBox(ViewObj)
%
% Output args
if nargout
   ViewFig = double(ViewObj.Figure);
end


%--------------- LOCAL FUNCTIONS ----------------------------------
%
%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% LocalStartUpMsgBox %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%
function LocalStartUpMsgBox(this)
% Reads the cstprefs.mat file and shows start-up message box if required
h = this.Preferences.ToolboxPreferences;
if strcmp(h.StartUpMsgBox.LTIviewer,'on')
   Handles = startupdlg(this.Figure,'LTIviewer', h);
   set(Handles.Figure, 'Name', getString(message('Control:viewer:strGettingStartedLTIViewer')));
   set(Handles.HelpBtn,'Callback', 'ctrlguihelp(''viewermainhelp'');');
   set(Handles.TextMsg,'String',getString(message('Control:viewer:msgGettingStartedLTIViewer')));
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% localSinglePlotCheck %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [sysList,Settings,CheckExtra] = localSinglePlotCheck(plottype,sysList,ExtraArg)
% Checks system compatibility with plot type and validates extra settings.
Settings = {}; CheckExtra = {};
switch plottype
   case 'step'
      [sysList,t,p,Config] = DynamicSystem.checkStepInputs(sysList,ExtraArg);
      Settings = {t,p,Config};  CheckExtra = {Config.InitialState,p,false};
   case 'impulse'
      [sysList,t,p,Config] = DynamicSystem.checkStepInputs(sysList,ExtraArg);
      Settings = {t,p,Config};  CheckExtra = {Config.InitialState,p,false};
   case {'bode','bodemag','sigma','nyquist','nichols'}
      [sysList,f] = DynamicSystem.checkBodeInputs(sysList,ExtraArg);
      if ~isempty(f)
         if iscell(f)
            fc = [f{:}];
            f = {fc(1) fc(end)};
         end
         Settings = {f};
      end
   case 'lsim'
      [sysList,t,x0,u,p,InterpRule] = DynamicSystem.checkLsimInputs(sysList,ExtraArg);
      if ~(isempty(t) && max(size(u))==0)
         % Watch for linearSystemAnalyzer('lsim',sys) (opens LSIM UI)
         Settings = {u,t,x0,p,InterpRule};  CheckExtra = {x0,p,false};
      end
   case 'initial'
      [sysList,t,p,Config] = DynamicSystem.checkInitialInputs(sysList,ExtraArg);
      Settings = {t,p,Config};  CheckExtra = {Config.InitialState,p,false};
end
      

function localSinglePlotSettings(this,plottype,sysList,Settings)
% Applies extra settings in single plot case.
switch plottype
   case {'step','impulse'}
      t = Settings{1};  p = Settings{2};  Config = Settings{3};
      if ~isempty(t)
         this.Preferences.TimeVector = t;
         this.Preferences.TimeVectorUnits = sysList(1).System.TimeUnit;
      end
      I = find(strcmp(plottype,{this.AvailableViews.Alias}));
      this.AvailableViews(I).CreateFcn = {@createView this plottype p Config};
   
   case {'bode','bodemag','sigma','nyquist','nichols'}
      f = Settings{1};
      this.Preferences.FrequencyVector = f;
      TimeUnits = sysList(1).System.TimeUnit;
      if strcmpi(TimeUnits,'seconds')
         FreqUnits = 'rad/s';
      else
         FreqUnits = ['rad/',TimeUnits(1:end-1)];
      end
      this.Preferences.FrequencyVectorUnits = FreqUnits;
   case 'lsim'
      % Overwrite default CreateFcn to capture u,t,x0 settings
      I = find(strcmp('lsim',{this.AvailableViews.Alias}));
      this.AvailableViews(I).CreateFcn = [{@createView this 'lsim'} Settings];
   case 'initial'
      t = Settings{1};  p = Settings{2};  Config = Settings{3};
      if ~isempty(t)
         this.Preferences.TimeVector = t;
         this.Preferences.TimeVectorUnits = sysList(1).System.TimeUnit;
      end
      I = find(strcmp('initial',{this.AvailableViews.Alias}));
      this.AvailableViews(I).CreateFcn = {@createView this 'initial' p Config};
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% LocalCurrentViewer %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function LocalCurrentViewer(Systems,Names,Viewers)
% Adds systems in specified viewers
%----Update the specified LTI Viewers with the new systems

for ctViewer=1:length(Viewers)
   %---Is the argument a valid handle and does the userdata have the
   % viewgui object
   if ~ishghandle(Viewers(ctViewer)) || ...
         ~isa(get(Viewers(ctViewer),'UserData'),'viewgui.ltiviewer')
      error(message('Control:analysis:ltiview4'))
   else
      %---Add the systems to the Viewer
      ViewerHandles = get(Viewers(ctViewer),'UserData');
      importsys(ViewerHandles,Names,Systems);
   end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% LocalClearViewer %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function LocalClearViewer(varargin)
% Clears systems in specified viewers
Viewers = varargin{end};
for ctViewer=1:length(Viewers)
   %---Is the argument a valid handle and does the userdata have the
   % viewgui object
   if ~ishghandle(Viewers(ctViewer)) || ...
         ~isa(get(Viewers(ctViewer),'UserData'),'viewgui.ltiviewer')
      error(message('Control:analysis:ltiview4'))
   else
      %---Add the systems to the Viewer
      ViewerHandles = get(Viewers(ctViewer),'UserData');
      ViewerHandles.Systems = [];
   end
end

%--------------------------------------------------------------------------
function sys = LocalCheckIDLTI(sys)
% Convert all models to cstb @lti subclasses
% Must comes from IDENT at this point

% Check the number of inputs to the model; Spectrum models are disallowed.
% Otherwise, time series models are accepted as is. Negative frequencies
% are not removed from IDFRD.
nu = size(sys,2);

if nu == 0 && isa(sys,'idfrd')
   % If the model is an output spectrum model error out. Otherwise, accept
   % model as is.
   error(message('Control:analysis:rfinputs10'))
end

