classdef LinkedVariableObserver < handle & internal.matlab.datatoolsservices.WorkspaceListener
% This undocumented function may be removed in a future release.

% Copyright 2020-2021 The MathWorks, Inc.

properties
    EventSource;
    PostRefreshCallbackArgs;
    LastAns = [];
    SkipNextUpdate = false;
end

properties (SetObservable)
    Enabled = true;
end

methods (Static)
    function observer = getInstance
        mlock
        persistent linkedVariableObserver;
        if isempty(linkedVariableObserver)
            linkedVariableObserver = datamanager.LinkedVariableObserver;
        end
        observer = linkedVariableObserver;
    end
end

methods(Access='private')
    function obj = LinkedVariableObserver()
        obj@internal.matlab.datatoolsservices.WorkspaceListener(false);
    end
end

methods
    
    function activate(this)
        internal.matlab.datatoolsservices.WorkspaceListener.enableLXEListeners
        % WorkspaceListener will add listener only if listener is not
        % already added
        this.addListeners();
    end
    
    function deactivate(this)
        this.removeListeners();
    end
    
    function postRefresh(this, callbackArgs)
        % Called from linked plot dialogs to update linked plots
        if nargin>=2
            this.PostRefreshCallbackArgs = callbackArgs;
        else
            this.PostRefreshCallbackArgs = {};
        end
        expr = 'linkplotfunc(datamanager.LinkedVariableObserver.getInstance.getPlotFuncInputArgsForEval{:})';
        internal.matlab.datatoolsservices.executeCmd(expr);   
    end
    
    function linkedPlotFuncInputArgs = getPlotFuncInputArgsForEval(this)
        % Create an expression that can be used as an input arg for
        % linkedplotfunc
        if isempty(this.PostRefreshCallbackArgs)
            linkedPlotFuncInputArgs = {[], 'stackdepth',2};
        else
            linkedPlotFuncInputArgs = cat(2,this.PostRefreshCallbackArgs,{'stackdepth',2});
        end
    end
    
    
    function setEnabled(this,state)
        
        if this.Enabled && ~state
            this.deactivate;
            
        elseif ~this.Enabled && state
            this.activate;
            
        end
        this.Enabled = state;
    end
    
    function state = isEnabled(this)
        state =  this.Enabled;
    end
    
    function workspaceUpdated(this,varNames,eventType)

        % Abort if this is an update to "ans" only and its value has
        % not changed.
        if eventType==internal.matlab.datatoolsservices.WorkspaceEventType.VARIABLE_CHANGED && ...
                isequal(varNames,{'ans'})
            if evalin('caller','exist("ans","var")')
                currentAns = evalin('caller','ans');
                if isequal(this.LastAns,currentAns)
                    % com.mathworks.util.Log.log
                    return
                end
            else
                return
            end
        end
        if evalin('caller','exist("ans","var")')
            this.LastAns = evalin('caller','ans');
        end
        
        % If there are no more linked plots then there is no need to listen
        % for workspace events
        linkedFigures = findall(groot,'-depth',1,'-property','LinkPlot','LinkPlot',true);
        if isempty(linkedFigures)
            this.setEnabled(false);
            return
        end       
        
        if this.SkipNextUpdate
            % com.mathworks.util.Log.log
            this.SkipNextUpdate = false;
            return
        end
        
        % Asynchronously call linkplotfunc when MATLAB is idle so that
        % the dbstack originates in the caller's workspace. There is no
        % eventSrc since this is an out of band change (not a data
        % edit)
        internal.matlab.datatoolsservices.executeCmd('linkplotfunc([], "stackdepth", 2)');
        
    end
    
    function executeFromDataSource(this,expression,eventSource)
        % Execute a MATLAB expression from a defined data source (currently a figure). This call prevents the resulting
        % call to linkplotfunc generated by the workspace event from clearing the corresponding figure undo stack and
        % forces a redraw of any brushing annotations for the effected figure.
        
        % Call the data editing expression with the workspace listener
        % off and the event source set to the current figure (so that
        % the data edit is undoable)
        this.EventSource = eventSource;
        this.SkipNextUpdate = true;
        internal.matlab.datatoolsservices.executeCmd([expression,'linkplotfunc(datamanager.LinkedVariableObserver.getInstance.EventSource, "retainUndo", true, "redrawBrushing", true, "stackdepth", 2)']);
    end
    
    function delete(this)
        this.deactivate;
    end
end
end

