classdef (Hidden) AbstractData < handle
    % Abstract Data Class for Model Reduction App
    
    % Author(s): S. Gumussoy, A. Ouellette
    % Copyright 2015-2023 The MathWorks, Inc. 
    
    properties
        Tag string
        ID string
        IsDirty (1,1) logical = false;
    end
    properties(Access = protected, SetObservable, AbortSet)
        Target                                    % current system
        TargetList                                % available systems
        ReduceSpec                                % mor API
        ReducedSystem                             % output system
        Options                                   % options for non-sparse
        SparseOptions                             % options for sparse
        ComparisonPlot = 'modelResponse';         % top plot
        PlotFreqVector = [];                      % vector for plotting
        EventManager = [];
        EventManagerEnabled (1,1) logical = false;
        UsedInEmbeddedApps (1,1) logical = false;
    end
    properties (Access = protected)
        TargetFRD
        ReducedFRD
    end
    properties (Transient)
        TargetNameChangedListener
        App
    end
    events
        CreateReducedModel_ToolData
        ToolNameChanged
        ToolDataChanged
        ReducedSystemChanged
        ComparisonPlotChanged
        FrequencyRangeChanged        
    end 
    methods      
        %% Constructor/destructor
        function this = AbstractData(Target,App)
            setTarget(this,Target);
            if isa(App,'mrtool.internal.ModelReducerApp')
                this.App = App;
                TargetList = App.Models;
            else %for testing purposes, app is TargetList
                this.App = [];
                TargetList = App(:);
            end
            setTargetList(this,TargetList);
            setOptions(this);
            setSparseOptions(this);
            this.ReduceSpec = localCreateReduceSpec(this);
        end
        function delete(this)
            delete(this.TargetNameChangedListener);
        end
        %% Get/set
        function setTarget(this,Target,sparseInitData)
            arguments
                this
                Target (1,1) mrtool.data.ModelWrapper
                sparseInitData struct = struct.empty
            end
            if ~isequal(this.Target,Target)
                if isempty(this.Target)
                    this.Target = Target;
                    this.TargetNameChangedListener = addlistener(Target,'Name','PostSet', @(es,ed) cbTargetNameChanged(this));
                else
                    delete(this.TargetNameChangedListener)
                    this.Target = Target;
                    this.TargetNameChangedListener = addlistener(Target,'Name','PostSet', @(es,ed) cbTargetNameChanged(this));
                    if ~isUsedInEmbeddedApps(this)
                        update(this,sparseInitData);
                    end
                end
            end
        end
        function setTargetList(this,TargetList)
            arguments
                this
                TargetList (:,1) mrtool.data.ModelWrapper
            end
            this.TargetList = TargetList;
        end
        function Target = getTarget(this)
            Target = this.Target;
        end            
        function TargetList = getTargetList(this)
            TargetList = this.TargetList;
        end
        function TargetListSystem = getTargetListSystem(this)
            TargetListSystem = {this.TargetList.System};            
        end
        function TargetSystem = getTargetSystem(this)
            TargetSystem = this.Target.System;
        end           
        function Name = getTargetName(this)
            Name = this.Target.Name;
        end
        function Names = getTargetListName(this)
            Names = {this.TargetList.Name};
        end     
        function setEventManager(this,EventManager)
            this.EventManager = EventManager;
        end
        function EventManager = getEventManager(this)
            EventManager = this.EventManager;            
        end
        %% Event Manager
        function enableEventManager(this,flag)
            arguments
                this
                flag (1,1) logical
            end
            this.EventManagerEnabled = flag;            
        end
        function postMessage(this,msg,WaitbarStatus)
            arguments
                this
                msg
                WaitbarStatus = 'on'
            end
            if this.EventManagerEnabled
                postActionStatus(this.EventManager,WaitbarStatus,msg);
            end                            
        end
        function clearMessage(this)
            if this.EventManagerEnabled
                clearActionStatus(this.EventManager);
            end            
        end
        %% Reduced System
        function computeReducedSystem(this,loadingFlag)
            arguments
                this
                loadingFlag (1,1) logical = false
            end
            if ~this.UsedInEmbeddedApps
                postMessage(this, getString(message('Control:mrtool:StatusMessageComputingReducedModel')));
                ReducedModel = localComputeReducedSystem(this);
                ReducedModel.SamplingGrid = struct('Order',order(ReducedModel));
                setReducedSystem(this,ReducedModel,loadingFlag);
                clearMessage(this);
            end
        end
        function setReducedSystem(this,ReducedModel,loadingFlag)
            arguments
                this
                ReducedModel DynamicSystem
                loadingFlag (1,1) logical = false
            end
            this.ReducedSystem = ReducedModel;
            computeReducedFRD(this);
            if ~loadingFlag
                setDirty(this,true);
            end
        end
        function System = getReducedSystem(this,loadingFlag)
            arguments
                this
                loadingFlag (1,1) logical = false
            end         
            if isempty(this.ReducedSystem)
                computeReducedSystem(this,loadingFlag)                
            end
            System = this.ReducedSystem;
        end
        function Model = getReducedModel(this)
            Model = mrtool.data.ModelWrapper(this.Target.Name,this.ReducedSystem);                   
        end
        function ReduceSpec = getReduceSpec(this)
            ReduceSpec = this.ReduceSpec;
        end  
        function createReducedSystem(this)
            % create multiple models if exists
            [~,~,nArray] = size(this.ReducedSystem);
            for ii = 1:nArray
                Model = mrtool.data.ModelWrapper(this.Target.Name,this.ReducedSystem(:,:,ii));
                ed = ctrluis.toolstrip.dataprocessing.GenericEventData( ...
                    struct('Action','CreateSystem','ReducedModel',Model));
                notify(this,'CreateReducedModel_ToolData',ed);
            end
        end
        %% Visualizations
        function setComparisonPlot(this,plotValue)
            arguments
                this
                plotValue {mustBeMember(plotValue,{'modelResponse','absoluteError','relativeError'})}
            end
            this.ComparisonPlot = plotValue;
            notify(this,'ComparisonPlotChanged');
        end
        function Result = getComparisonPlot(this)
            Result = this.ComparisonPlot;
        end
        function freqs = getPlotFreqVector(this)
            freqs = this.PlotFreqVector;
        end
        function setPlotFreqVector(this,plotVector)
            arguments
                this
                plotVector (1,:) double
            end
            if ~isequal(this.PlotFreqVector,plotVector)
                this.PlotFreqVector = plotVector;
                if ~isempty(this.ReducedSystem)
                    computeReducedFRD(this);
                end
                if ~isempty(this.Target)
                    this.Target.SparseFreqVector = plotVector;
                    computeTargetFRD(this);
                end
            end
        end
        function data = getSparseData(this)
            data = struct('FreqVector',this.PlotFreqVector,...
                'Options',this.SparseOptions);
        end
        %% Load/Save Session
        function loadSession(this,SessionData)            
            if ~isequal(this.Target,SessionData.Target)
                this.Target = SessionData.Target;
            end
            if isfield(SessionData,'PlotFreqVector')
                setPlotFreqVector(this,SessionData.PlotFreqVector); 
            end
            loadSessionToolData(this,SessionData); % loading session data specific to tool data
        end
        function SessionData = saveSession(this,SessionData)
            SessionData.Target = this.Target;
            SessionData.ComparisonPlot = this.ComparisonPlot;
            SessionData.PlotFreqVector = this.PlotFreqVector;
            % append tool data specific to individual tool
            SessionData = saveSessionToolData(this,SessionData);
        end
        function setDirty(this, flag)
            this.IsDirty = flag;            
        end
        %% For Embedded Apps
        function setUsedInEmbeddedApps(this,flag)
            arguments
                this
                flag (1,1) logical
            end
            this.UsedInEmbeddedApps = flag;
        end
        function flag = isUsedInEmbeddedApps(this)
            flag = this.UsedInEmbeddedApps;
        end
        %% For plotting
        function computeTargetFRD(this)
            if isempty(this.PlotFreqVector)
                [~,w] = sigma(this.Target.System);
            else
                w = this.PlotFreqVector;
            end
            h = freqresp(this.Target.System,w);
            this.TargetFRD = frd(h,w,this.Target.System);
            if ~isempty(this.ReducedFRD) && isequal(this.TargetFRD.Frequency,this.ReducedFRD.Frequency) && ...
                isequal(iosize(this.TargetFRD),iosize(this.ReducedFRD))
                notify(this,'ToolDataChanged');
            end
        end
        function computeReducedFRD(this)
            if isempty(this.PlotFreqVector)
                [~,w] = sigma(this.Target.System);
            else
                w = this.PlotFreqVector;
            end
            h = freqresp(this.ReducedSystem,w);
            this.ReducedFRD = frd(h,w,this.ReducedSystem);
            if ~isempty(this.TargetFRD) && isequal(this.TargetFRD.Frequency,this.ReducedFRD.Frequency) && ...
                isequal(iosize(this.TargetFRD),iosize(this.ReducedFRD))
                notify(this,'ToolDataChanged');
            end
        end
        function frd = getTargetFRD(this)
            frd = this.TargetFRD;
        end
        function frd = getReducedFRD(this)
            frd = this.ReducedFRD;
        end
        function alpha = getRegularization(this)
            if ~issparse(this.Target.System)
                sys = ss(this.Target.System);
                gpeak = ltipack.util.estimGain(sys.A,sys.B,sys.C,sys.D,sys.E,sys.Ts); % damped peak gain
                alpha = 1e-5*gpeak;
            else
                fnrm = fnorm(this.TargetFRD);
                respData = fnrm.ResponseData;
                respData = respData(respData~=0);
                if ~isempty(respData)
                    alpha = min(respData);
                else %failsafe
                    alpha = 1;
                end
            end
        end
        function cbTargetNameChanged(this)
            notify(this,'ToolNameChanged');
        end
    end
    methods (Abstract)
        setOptions(this);
        setSparseOptions(this);
        applyOptions(this,updateSystem);
        update(this,sparseInitData);
    end
    methods (Abstract, Access=protected)
        localCreateReduceSpec(this);     
        localComputeReducedSystem(this);
    end 
end
