classdef SimulinkModelWorkspaceAdapter < toolpack.AbstractAtomicComponent
    % An adapter that provides access to a SimulinkModelWorkspace object
    % including a listening mechanism.
    %
    % SimulinkModelWorkspaceAdapter Methods:
    %    clear                   - clears a variable in the stack
    %    assignin                - assigns a variable in the stack
    %    evalin                  - evaluate expression in the stack
    %    who                     - List current variables
    %    whos                    - list current variables, long form
    %    duplicate               - Duplicates given variables in the stack
    %    rename                  - renames a variable
    %    getOwnerName            - returns the name of the holding Simulink model
    %    getModelWorkspace       - returns the ModelWorkspace
    %
    %   See also toolpack.databrowser.BaseWorkspaceAdapter, toolpack.databrowser.WorkspaceView
    
    %   Author(s): Murad Abu-Khalaf , November 10, 2010
    %   Revised:
    %   Copyright 2010-2011 The MathWorks, Inc.
    
    
    %% ------ PROPERTIES
    properties (Access = private)
        % Simulink ModelWorkspace object
        ModelWorkspace
        
        % Listener to the ModelWorkspace
        ListenerToModelWorkspace
        
        % Model Close Event
        ListenerToModelCloseEvent
        
        %  Data to be used by the mUpdate method for WorkspaceEventsData       
        UpdateData_
    end    
    
    %% ----------------------------------- %
    % Model and View Construction          %
    % ------------------------------------ %
    methods (Access = public)
        
        % Constructor
        function this = SimulinkModelWorkspaceAdapter(MDL_WS,varargin)
            % SIMULINKWORKSPACEADAPTER  The constructor of the tool component
            % that provides ModelWorkspace access.
            
            % Call parent class constructor
            this = this@toolpack.AbstractAtomicComponent(varargin{:});
            
            % Add Inports/Outports
            this.addOutport('1');
            
            if strcmp(class(MDL_WS),'Simulink.ModelWorkspace')
                this.ModelWorkspace = MDL_WS;
            else
                ctrlMsgUtils.error('Controllib:databrowser:ModelWorkspaceRequired',class(this));
            end
            hMdl = get_param(this.ModelWorkspace.ownerName,'Object');
            this.ListenerToModelCloseEvent = Simulink.listener( hMdl, 'CloseEvent', @(src,dat) this.closeEventCallback);
            
            % Setup system if not initialized and generates output
            output(this);
        end
        
    end
    
    
    %% ----------------------------------- %
    % SERIALIZATION                         %
    % ------------------------------------ %
    methods
        
        % Overriding saveobj
        function S = saveobj(obj)
            S = saveobj@toolpack.AbstractAtomicComponent(obj);
            
            % Anything extra to save comes here
            % S.foo = obj.bar
        end
        
        % Overriding reload
        function obj = reload(obj, S)
            obj = reload@toolpack.AbstractAtomicComponent(obj, S);
            
            % Anything extra to reload comes here
            % obj.bar = S.foo
        end
        
    end
    methods (Static)
        
        function obj = loadobj(S)
            % Call constructor
            obj = toolpack.databrowser.SimulinkModelWorkspaceAdapter;
            obj = reload(obj, S);
        end
        
    end
    
    
    %% ----------------------------------- %
    % Ports Construction and Handling      %
    % ------------------------------------ %
    methods (Access = protected)
        % Implementing processInportEvents
        function processInportEvents(this, src, evnt) %#ok<INUSD,MANU>
        end
    end
    
    
    %% ----------------------------------- %
    % INPUTS                               %
    % ------------------------------------ %
    methods (Access = public, Hidden = true)
        
    end
    
    
    %% ----------------------------------- %
    % STATES                               %
    % ------------------------------------ %
    methods (Access = protected)
        
        % Implementing getIndependentVariables
        function props = getIndependentVariables(this) %#ok<MANU>
            props = {};
        end
        
        % Implementing mStart
        function mStart(this)
            this.Database = struct;
        end
        
        % Implementing mReset
        function mReset(this)
            this.Database = struct;
        end
        
        % Implementing mCheckConsistency
        function mCheckConsistency(this) %#ok<MANU>
            
        end
        
        % Implementing mUpdate
        function updateData = mUpdate(this)
            % Initialize event data
            %updateData =  toolpack.databrowser.WorkspaceEventData;            
            
            % Update internal state
            
            if isempty(this.UpdateData_)
                this.UpdateData_ =  toolpack.databrowser.WorkspaceEventData;
            end
            updateData = this.UpdateData_;
            this.UpdateData_ = [];
        end
        
        % Implementing mOutput
        function mOutput(this) %#ok<MANU>
            
            % (A) Compute expensive outputs using lazy evaluation
            
            % (B) Compute outputs that are not expensive directly here
        end
        
        % Implementing mGetState
        function state=mGetState(this) %#ok<STOUT,MANU>
        end
        
        % Implementing mSetState
        function mSetState(this,state) %#ok<INUSD,MANU>
        end
        
    end
    
    
    %% ----------------------------------- %
    % OUTPUTS                              %
    % ------------------------------------ %
    methods (Access = public)
        
        % STORED OUTPUTS
        
        % OUTPUTS NOT STORED
        
    end
    
    %% ----------------------------------- %
    % Wrappers and/or Common methods       %
    % ------------------------------------ %
    methods (Access = public)
        
        function clear(this, varargin)
            % CLEAR  Clears the variable specified.
            %
            %   CLEAR(OBJ, VAR1, VAR2, ...) clears the specified variables
            %   from the ModelWorkspace object.
            %
            %   VAR1, VAR2, ... are strings representing the variables to
            %   be cleared from the workspace. If the variable does not
            %   exists, nothing happens.
            %
            %   See also ASSIGNIN.
            
            eventData = toolpack.databrowser.WorkspaceEventData;
            if isempty(varargin)
                this.ModelWorkspace.clear;
                eventData.WSClear = true;
            else
                for i=1:numel(varargin)
                    this.ModelWorkspace.clear(varargin{i});
                end
                eventData.WSDelete = true;
            end            
            this.UpdateData_ = eventData;
            this.update;
        end
        
        function assignin(this, varargin)
            % ASSIGNIN  Assigns a variable in the workspace.
            %
            %   ASSIGNIN(OBJ,VARNAME, VARVALUE) assigns the value VARVALUE
            %   to the variable VARNAME in the ModelWorkspace.
            %
            %   VARNAME is a string representing the variable name.
            %   VARVALUE is the value associated with the variable.
            %
            %   ASSIGNIN(OBJ,VAR1NAME,VAR1VALUE,VAR2NAME,VAR2VALUE,...)
            %   supports a multi variable assignment in a single workspace
            %   update.
            %
            %   See also EVALIN.
            
            ni = nargin - 1;
            
            if mod(ni,2)~=0
                ctrlMsgUtils.error('Controllib:databrowser:InconsistentNameValuePair');
            end
            for i=1:2:ni
                if isvarname(varargin{i})
                    varName = varargin{i};
                else
                    ctrlMsgUtils.error('Controllib:databrowser:InvalidVariableNames');
                end
                varValue = varargin{i+1};
                this.ModelWorkspace.assignin(varName,varValue);
            end
            eventData = toolpack.databrowser.WorkspaceEventData;
            eventData.WSChange = true;
            this.UpdateData_ = eventData;
            this.update;
        end
        
        function varargout = evalin(this,expression)
            % EVALIN  Evaluate expression in the workspace.
            %
            %   EVALIN(OBJ,'expression') evaluates 'expression' in the
            %   context of the ModelWorkspace.
            %
            %   [X,Y,Z,...] = EVALIN(OBJ,'expression') returns output
            %   arguments from the expression.
            %
            %   See also ASSIGNIN.
            
            flag = get_param(this.getOwnerName,'Dirty');
            
            % Get old variables list
            oldWho = this.ModelWorkspace.evalin('who');
            
            % The code below does not introduce any intermediate variables
            varname = expression;
            if ~isempty(varname) && (strcmp(varname(end),',') || strcmp(varname(end),';'))
                % Robust to ',' and ';' to support evalin expressions
                varname(end) = [];
            end
            if this.isVar(varname)
                varargout = {this.ModelWorkspace.evalin(varname)};
                if strcmp(flag,'off')
                    set_param(this.getOwnerName,'Dirty','off');
                end
                return;
            end
            
            n = nargout;
            if n == 0
                this.ModelWorkspace.evalin(expression);
            else
                str = toolpack.databrowser.SimulinkModelWorkspaceAdapter.getOutStr(n);
                eval([str ' = ' 'this.ModelWorkspace.evalin(' '''' expression '''' ');' ]);
                varargout = cell(n,1);
                for i=1:n
                    varargout{i} = eval(['y' num2str(i)]);
                end
            end
            
            % Get new variables list            
            newWho = this.ModelWorkspace.evalin('who');
            
            % Compare old vs. new lists to issue relevant workspace flags.
            eventData = toolpack.databrowser.WorkspaceEventData;
            if numel(newWho)==0
                eventData.WSClear = true;
            else
                eventData.WSChange = true;
            end
            if (~isempty(newWho) && ~isempty(oldWho))
                for i=1:numel(oldWho)
                    chk = cellfun(@(x) ~strcmp(oldWho{i},x),newWho, 'UniformOutput', true);
                    if all(chk)
                        eventData.WSDelete = true;
                        break;
                    end
                end
            end
            this.UpdateData_ = eventData;
            
            % Update the tool component
            this.update;
        end
        
        function varargout = who(this, varargin)
            % WHO  List current variables.
            %
            %   C = WHO(OBJ) returns a cell array containing the names
            %   of the variables in the ModelWorkspace.
            %
            %   See also WHOS.
            
            flag = get_param(this.getOwnerName,'Dirty');
            
            if isempty(varargin)
                exp = 'builtin(''who'')';
            else
                if numel(varargin) == 2 && ischar(varargin{1}) && ischar(varargin{2}) && ...
                        strcmpi(varargin{1},'-regexp')
                    pattern = varargin{2};
                    exp = ['builtin(''who'',''-regexp'',''' pattern ''')'];
                else
                    ctrlMsgUtils.error('Controllib:databrowser:RegexpOnly');
                end            
            end
            
            if nargout == 0
                exp = strrep(exp,'''','''''');
                str = evalc(['this.ModelWorkspace.evalin(''' exp ''')']);
                disp(deblank(str));
            else
                vars = this.ModelWorkspace.evalin(exp);
                n = numel(vars);
                if n==0
                    varargout = {{}};
                else
                    varargout = {vars};
                end
            end
            
            if strcmp(flag,'off')
                set_param(this.getOwnerName,'Dirty','off');
            end
        end
        
        function S = whos(this,varargin)
            % WHOS  List current variables, long form.
            %
            %   S = WHOS(OBJ) returns a structure with fields:
            %       name        -- variable name
            %       size        -- variable size
            %       bytes       -- number of bytes allocated for the array
            %       class       -- class of variable
            %
            %   See also WHO.
            
            flag = get_param(this.getOwnerName,'Dirty');
            
            if isempty(varargin)
                exp = 'builtin(''whos'')';
            else
                if numel(varargin) == 2 && ischar(varargin{1}) && ischar(varargin{2}) && ...
                        strcmpi(varargin{1},'-regexp')
                    pattern = varargin{2};
                    exp = ['builtin(''whos'',''-regexp'',''' pattern ''')'];
                else
                    ctrlMsgUtils.error('Controllib:databrowser:RegexpOnly');
                end
            end
            S = this.ModelWorkspace.evalin(exp);
            S = rmfield(S,{'global','nesting','persistent'});
            
            if strcmp(flag,'off')
                set_param(this.getOwnerName,'Dirty','off');
            end
        end
        
        function duplicate(this, varargin)
            % DUPLICATE  Duplicates given variables in the workspace.
            %
            %   DUPLICATE(OBJ, 'VAR1', 'VAR2',...) creates new variables
            %   in the ModelWorkspace that have the same value of the
            %   provided variables names VAR1,VAR2,... but with different
            %   names.
            %
            %   See also RENAME.
            
            % All variable names must be strings
            chk = cellfun(@isvarname,varargin, 'UniformOutput', true);
            if ~all(chk)
                ctrlMsgUtils.error('Controllib:databrowser:InvalidVariableNames');
            end
            for i=1:numel(varargin)
                cNames = this.who;
                copyname= workspacefunc('getcopyname',varargin{i},cNames);
                this.ModelWorkspace.evalin([copyname '=' varargin{i} ';']);
            end
            eventData = toolpack.databrowser.WorkspaceEventData;
            eventData.WSChange = true;
            this.UpdateData_ = eventData;
            this.update;
        end
        
        function rename(this, oldname, newname)
            % RENAME  Modifies the name of an existing workspace
            % variable.
            %
            %   RENAME(OBJ, OLDNAME, NEWNAME) renames the workspace
            %   variable OLDNAME to NEWNAME.
            %
            %   OLDNAME must be a string representing the name of an
            %   existing variable in the ModelWorkspace.
            %
            %   NEWNAME is a string that will be used as the new name of
            %   the variable.
            %
            %   See also DUPLICATE.
            
            if ~isvarname(newname)
                ctrlMsgUtils.error('Controllib:databrowser:RenameFailed',oldname,newname);
            end
            
            if strcmp(oldname,newname)
                return;
            end
            this.ModelWorkspace.evalin([newname '=' oldname ';']);
            this.ModelWorkspace.clear(oldname);            
            eventData = toolpack.databrowser.WorkspaceEventData;
            eventData.WSChange = true; 
            eventData.WSDelete = true;
            eventData.WSRename = true; 
            eventData.setRenameData(oldname,newname);
            this.UpdateData_ = eventData;
            this.update;
        end
        
        function r = getOwnerName(this)
            % GETOWNERNAME  Returns the owner name of the holding Simulink
            % model.
            %
            %   The ownerName is stored with the ModelWorkspace object, and
            %   cannot set explicitly.
            %
            %   See also getModelWorkspace.
            r = this.ModelWorkspace.ownerName;
        end
        
        function r = getModelWorkspace(this)
            % GETMODELWORKSPACE  Returns the ModelWorkspace object.
            %
            %   The ModelWorkspace is passed upon the creation of this
            %   tool component object, it cannot be set or modified
            %   afterwards.
            %
            %   See also getModelWorkspace.
            r = this.ModelWorkspace;
        end
        
    end
    
    %% ------  HELPER PRIVATE METHODS
    methods (Access = private )
        function F = isVar(this,varname)
            % ISVAR  Returns if a variable exists in current workspace.
            %
            %   F = ISVAR(OBJ,VARNAME) returns a logical value that
            %   indicates whether the variable VARNAME exists in the
            %   LocalWorkspaceModel with object OBJ.
            %
            %   VARNAME must be a string representing the name of the
            %   workspace variable.
            C = this.ModelWorkspace.evalin('who');            
            F = any(strcmp(varname,C));
        end
        
        function closeEventCallback(this)
            % Remove from Container
            try
                if isa(this.getParent,'toolpack.Container')
                    this.getParent.remove(this.Name);
                end
            catch E
                rethrow(E);
            end
            
            % If not referenced outside the composite component, it will be
            % deleted. However, to make sure it is indeed deleted, this
            % step is performed explicitly
            this.delete;
        end
    end
        
    methods (Static, Access = private )
        function str = getOutStr(n)
            str = '';
            if n~=0
                for i=1:n
                    str = [str 'y' num2str(i) ' ']; %#ok<AGROW>
                end
                str = ['[ ' str ']'];
            end
        end
    end
end