classdef FixedPointTool < handle
    % FixedPointTool singleton class that instantiates and maintains the Fixed-Point Tool application
    
    % Copyright 2015-2020 The MathWorks, Inc.
    
    properties(Constant,GetAccess=private)
        % Stores the class instance as a constant property
        Instance = fxptui.FixedPointTool;
    end
    
    properties(SetAccess = private, GetAccess = public)
        isReadyPostCallback = false;
        % Converter is the current converter object used for workflow actions
        Converter DataTypeWorkflow.Converter = DataTypeWorkflow.Converter.empty;
    end
    
    properties(SetAccess = private, GetAccess=private)
        DebugURL = 'toolbox/fixedpoint/fixedpointtool/web/fixedpoint/fpt-debug.html';
        ReleaseURL = 'toolbox/fixedpoint/fixedpointtool/web/fixedpoint/fpt.html';
        SimStartPublishChannel;
        SimFailPublishChannel;
        WebWindow
        TreeController
        DataController
        StartupController
        WorkflowController
        ResultInfoController
        ResultSummaryController
        SetupController
        AnalyzerController
        ExternalViewer
        Model
        ModelObject
        Subscriptions
        InitialSelectedSystem
        Listener
        CloseModelListener
        SDIListeners
        SimStartListener
        SimFailedListener
        InitSDIRecordState
        RestoreSettingsListener
        ModelHierarchy
    end
    
    properties
        isFPTCallbackSource = false;
    end
    
    events
        UpdateSUDEvent
        FPTCloseEvent
        SimulationDataCompleteEvent
    end
    
    methods(Access=private)
        function this = FixedPointTool
            % Ensure the connector is on before subscribing. Connctor depends on java & jvm to work.
            if usejava('jvm')
                connector.ensureServiceOn;
            end
            sdiEngine = Simulink.sdi.Instance.engine();
            this.SDIListeners = event.listener(sdiEngine,'runAddedEvent',@(s,e) updateForTimeSeriesData(this, e));
        end
        
        function attachPostSaveCallback(this)
            % Register a callback on FPT Model Save as
            % Pass the replace_existing flag as input argument.If supplied and true
            % then any existing callback with the same type and ID will be replaced.
            
            Simulink.addBlockDiagramCallback(this.Model,'PostSave','FPTModelSaveAs',@()fxptui.launchFPTOnModelRename(this.ModelObject, this.Model), true);
        end
        
        function createApplication(this, debugPort)
            this.WebWindow = fxptui.Web.ApplicationFramework(this.ReleaseURL, this.constructTitle, debugPort);
            this.WebWindow.addCloseCallback(@(s,e)close(this));
        end
    end
    
    methods
        function setupRestoreListener(this)
            if isempty(this.RestoreSettingsListener)
                this.RestoreSettingsListener = addlistener(this, 'SimulationDataCompleteEvent', @(s,e)this.restoreSystemSettings);
            end
        end
        
        function updateData(this,opCode, varargin)
            this.DataController.updateData(opCode, varargin{:});
            addedTree = this.ModelHierarchy.getAddedTreeData;
            this.TreeController.sendAddedTree(addedTree);
        end
        
        function model = getModel(this)
            model = '';
            if ~isempty(this.WebWindow)
                model =  this.Model;
            end
        end
        
        function modelObj = getModelObject(this)
            modelObj = this.ModelObject;
        end
        
        function err = updateSelectedSystem(this, systemPath)
            this.InitialSelectedSystem = systemPath;
            % This call will not fail when launching FPT from the canvas
            % because we can only launch from subsystems.
            blk = get_param(systemPath, 'Object');
            % g1839779 - SUD is managed by converter, Conversion Goals
            % dependency is removed
            err = this.setSystemForConversion(systemPath, class(blk));
            
            if ~isempty(err)
                fxptui.showdialog('invalidSUD', err);
            end
        end
        
        function registerWorkflowOutsideFPT(this)
            % if any simulation was created outside of the FPT before it
            % was open, capture those runs and have them registered so that
            % a user may view the run data in FPT
            
            % get the names of all the previously registered run names
            dataLayer = fxptds.DataLayerInterface.getInstance();
            facade = this.Converter.getWorkflowTopologyFacade();
            allRegisteredRunIds = facade.query(this.Converter.TopModel, 'type', 'Collection');
            allRegisteredRunNames = dataLayer.getRunNameFromID(this.Converter.TopModel, str2double(allRegisteredRunIds));
            
            % get all run names under the top model
            allRunNames = dataLayer.getAllRunNamesUsingModelSource(this.Converter.TopModel);
            
            % find the difference of all runs - registered runs
            unregisteredRunNames = setdiff(allRunNames, allRegisteredRunNames);
            
            % for each unregistered run, register the intent
            for idx = 1:numel(unregisteredRunNames)
                
                runName = unregisteredRunNames{idx};
                settings = ''; % assume empty settings
                
                % register the user intent to Declare a Workflow
                dataLayer = fxptds.DataLayerInterface.getInstance();
                [sdiRunID, fptRunID] = dataLayer.getIdFromRunName(this.Converter.TopModel, runName);
                context = fxptds.WorkflowTopology.WorkflowContextFactory.getContext(fxptds.UserIntent.SimpleSimulation, ...
                    'TopModel', this.Converter.TopModel, 'SDIRunID', sdiRunID, ...
                    'FPTRunID', fptRunID, 'Settings', settings, 'ContainerID', 1); % assume 1st workflow for backwards compatability
                facade.register(context);
                
            end
        end
        
        function dataset = getViewDataset(this)
            dataset = this.DataController.getViewDataset();
        end
        
        function shortcutManager = getShortcutManager(this)
            shortcutManager = this.Converter.ShortcutManager;
        end
    end
    
    methods(Hidden)
        function setReadyPostCallback(this, flag)
            this.isReadyPostCallback = flag;
        end
        
        function spreadsheet = getDataController(this)
            spreadsheet = this.DataController;
        end
        
        function resultSummary = getResultSummaryController(this)
            resultSummary = this.ResultSummaryController;
        end
        
        function analyzer = getAnalyzerController(this)
            analyzer = this.AnalyzerController;
        end
        
        function spreadsheet = getSpreadsheetController(this)
            spreadsheet = [];
            if isempty(this.DataController)
                return
            end
            spreadsheet = this.DataController.getSpreadsheetController;
        end
        
        function resultInfo = getResultInfoController(this)
            resultInfo = this.ResultInfoController;
        end
        
        function setupController = getSetupController(this)
            setupController = this.SetupController;
        end
        
        function result = getSelectedResult(this)
            result = this.DataController.getSelectedResult;
        end
        
        function selectResultInUI(this, scopingId)
            this.DataController.selectResult(scopingId);
        end
        
        function selectTreeNodeInUI(this, treeObject)
            this.TreeController.selectTreeNode(treeObject);
        end
        
        function selectTreeAndThenResultInUI(this, treeObj, scopingId)
            % If the block is not within the provided scope then we need to
            % select the parent tree node first and then the result
            this.DataController.setDelayedResultSelection(scopingId);
            this.TreeController.selectTreeNode(treeObj);
        end
        
        function startupController = getStartupController(this)
            startupController = this.StartupController;
        end
        
        function workflow = getWorkflowController(this)
            workflow = this.WorkflowController;
        end
        
        function tree = getTreeController(this)
            tree = this.TreeController;
        end
        
        function externalViewer = getExternalViewer(this)
            externalViewer = this.ExternalViewer;
        end
        
        function url = getURL(this)
            url = this.WebWindow.getURL;
        end
        
        function port = getDebugPort(this)
            port = this.WebWindow.getDebugPort;
        end
        
        function show(this)
            this.WebWindow.showUI;
        end
        
        function reEnableUI(this, msg)
            % Re-enable the UI
            message.publish(this.SimFailPublishChannel, msg);
            this.enableCodeView(true);
        end
        
        function webWindow = getWebWindow(this)
            webWindow = this.WebWindow;
        end
        
        function deleteBlockDiagramCallbacks(this)
            %  Remove the callback registered on FPT Model 'Save as'
            if isa(this.ModelObject, 'Simulink.BlockDiagram')
                if this.ModelObject.hasCallback('PostSave','FPTModelSaveAs')
                    Simulink.removeBlockDiagramCallback(this.ModelObject.Handle,'PostSave','FPTModelSaveAs');
                end
            end
        end
        
        function enableCodeView(this, enable)
            this.ExternalViewer.updateGlobalEnabledState(enable);
        end
        
        function title = constructTitle(this)
            
            if fixed.internal.utility.featuremanager.FeatureManager.getStatus('fxpoptInFPT') == 15
                title = fxptui.message('titleFPTool');
            else
                title = sprintf('%s - %s', fxptui.message('titleFPTool'),...
                fxptui.message('titleSUD',fxptui.removeLineBreaksFromName(this.getSystemForConversion)));
            end
        end
        
        function setTitle(this, title)
            this.WebWindow.setTitle(title);
        end
        
        function dtwErr = setConverter(this, sysPath)
            dtwErr = [];
            % g2009485 - License check is done in FixedPointTool.launch
            try
                if isempty(this.Converter)
                    this.Converter = DataTypeWorkflow.Converter(sysPath, 'TopModel', this.Model);
                else
                    this.Converter.setup(sysPath, 'TopModel', this.Model);
                end
            catch err
                % return the error message to allow the caller to
                % decide
                dtwErr = err;
            end
        end
        
        % g1712237 Remove addSubscription and deleteSubscription
        function addSubscription(this, sub)
            this.Subscriptions{end + 1} = sub;
        end
        
        function deleteSubscription(this, sub)
            allsubs = cell2mat(this.Subscriptions);
            this.Subscriptions(allsubs == sub) = [];
        end
        
        function subs = getSubscriptions(this)
            subs = this.Subscriptions;
        end
        
    end
    
    methods(Access = private)
        initControllers(this, clientData);
        setModel(this, modelName);
        publishInitialData(this, clientData);
        deleteControllers(this);
        deleteListeners(this);
        recordSignals(this);
        allDatasets = getAllDatasets(this);
        initSDIEngineListeners(this);
        deleteSDIEngineListeners(this);
        updateSUD(this, source, eventData);
        externalviewer = createExternalViewer(this);
        onModelClose(this);
        restoreIfSimNeverStarted(this);
    end
    
    methods
        open(this, debugPort);
        close(this);
        system = getSystemForConversion(this);
        modelHierarchy = getModelHierarchy(this);
    end
    
    methods(Hidden)
        isFPTOpen = isFPTLaunchedOnSameModel(this, modelObj);
        loadReferencedModels(this);
        setupCodeView(this);
        updateForTimeSeriesData(this, eventData);
        restoreSystemSettings(this);
        applyIdealizedSettings(this);
        applyEmbeddedSettings(this);
        turnOnInstrumentationAndRestoreDirty(this);
        triggerProposalFromCodeView(this);
        triggerApplyFromCodeView(this);
        result = getSelectedTreeNode(this);
        blkObj = setSystemForConversion(this, sysPath, objectClass);
        handleCompFailure(this);
    end
    
    methods (Static)
        instance = getExistingInstance;
        instance = getInstance(model);
        launch(system, debugMode);
    end
end

% LocalWords:  fixedpointtool fpt FP fxpopt
