classdef CallbackHandler < handle
    % Class to handle simulating the model from FPT.
    
    % Copyright 2017-2019 The MathWorks, Inc.
    
    properties(SetAccess = private)
        NextHandler fxptui.Web.CallbackService.CallbackHandler = fxptui.Web.CallbackService.CallbackHandler.empty;
    end
    
    properties(SetAccess = protected)
        % Set in Constructor
        FPTInstance
        hasValidConverter
        
        % Set in Initialize
        Converter
        AccelModeHandler
        FastRestartHandler
        ShortcutManager
        DataLayer
        
    end
    
    methods
        function this = CallbackHandler(~)
            this.FPTInstance = fxptui.FixedPointTool.getExistingInstance;
            
            % In between SUD selection and callback, a user may: close a
            % model reference listed as the SUD or rename the subsystem
            % set the hasValidConverter flag to true or false to let
            % Callbacks know not to proceed
            this.setHasValidConverter();
            if this.hasValidConverter
                this.initialize();
            else
                this.refreshRunBrowser();
            end
            
        end
        
        function initialize(this)
            this.Converter = this.FPTInstance.Converter;
            
            this.AccelModeHandler = fixed.internal.simulationmodehandler.AccelModeHandler(this.FPTInstance.getModel);
            
            % Initialize fast restart handler
            this.FastRestartHandler = fixed.internal.simulationmodehandler.FastRestartHandler(this.FPTInstance.getModel);
            
            this.ShortcutManager = this.FPTInstance.getShortcutManager;
            
            this.DataLayer = fxptds.DataLayerInterface.getInstance();
        end
        
        function refreshRunBrowser(this)
            % clicking on a callback button will change the state of
            % FPT to show that current view. if the callback was
            % unsuccessful, before getting into the execution, we need
            % to let the WorkflowBrowser know to update the view to the
            % last known good
            dc = this.FPTInstance.getDataController();
            rbc = dc.getRunBrowserController();
            runInfo = dc.getRunBrowserData();
            runInfo.isError = true;
            rbc.updateRunBrowser(runInfo);
        end
        
        function resetSimulationInputsList(this)
            ctrl = this.FPTInstance.getWorkflowController;
            ctrl.importSimulationInputsFromWorkspace();
        end
        
        function fastRestartHandler = getFastRestartHandler(this)
            %% Get fast restart handler
            fastRestartHandler = this.FastRestartHandler;
        end
        
        function setHasValidConverter(this)
            this.hasValidConverter = true;
            
            % in the event that after SUD selection, the SUD was deleted
            % check for SUD before using the converter
            sud = this.FPTInstance.getSystemForConversion;
            try
                this.validateConverter(sud);
                
            catch err
                this.hasValidConverter = false;
                % Setting switch condition to be empty causes arbitrary
                % errors to be thrown in the ui.
                % In this specific case, when a valid converter is not
                % found for the sud, an appropriate error can be thrown
                fxptui.showdialog('', err);
            end
        end
        
        function validateConverter(this, sud)
            if isempty(sud)
                err = MException(message('SimulinkFixedPoint:autoscaling:invalidSystemToScale'));
                throw(err);
            end
            
            % check for FXP Designer license
            if ~hasFixedPointDesigner()
                err = MException(message('SimulinkFixedPoint:autoscaling:licenseCheck'));
                throw(err);
            end
            
            % verify that the converter is non-empty, not deleted
            this.FPTInstance.Converter.assertDEValid();
            
            % verify that the model is unlocked
            model = this.FPTInstance.Converter.TopModel;
            this.FPTInstance.Converter.assertModelUnlocked(model);
            
            % FPT only allows selection of a system to be under the model hierarchy of the
            % TopModel like a model reference or subsystem
            % loadSystems will re-open any model references that were
            % closed prior to this callback and assert the top model is
            % loaded
            this.FPTInstance.Converter.loadSystems();
            
            % verify that the SUD is loaded
            sudObj = get_param(sud, 'object');
            if isa(sudObj, 'Simulink.ModelReference') || isa(sudObj, 'Simulink.BlockDiagram')
                this.FPTInstance.Converter.assertModelLoaded(sud);
            end
        end
        
        function setConverter(this, converter)
            this.Converter = converter;
        end

        function settings = applyTolerances(this, settings)
            % APPLYTOLERANCES Applies the tolerances held in SignalTolerancesStore and
            % adds each of the tolerances to the settings object
            
            signalTolerancesStore = this.DataLayer.getSignalTolerancesStore(this.FPTInstance.getModel());
            toleranceFields = {'AbsTol', 'RelTol', 'TimeTol'};            
           
            for rowIndex = 1:height(signalTolerancesStore)
                row = signalTolerancesStore(rowIndex,:);
                blockPath = row.BlockPath{:};
                portIndex = row.PortIndex{:};
                for idy = 1:numel(toleranceFields)
                    tolType = toleranceFields{idy};
                    tolValue = row.(tolType){1};
                    if ~isempty(tolValue)
                        tolValue = str2double(tolValue);                        
                        % add tolerance to settings object
                        addTolerance(settings, blockPath, portIndex, tolType, tolValue);
                    end
                end
            end
            
        end
        
        function simIn = getSimulationInputScenarios(~, simInputName)
            baseWorkspace = Simulink.data.BaseWorkspace;
            
            % variable list from base workspace
            allSimulationInputs = baseWorkspace.whos('Simulink.SimulationInput');
            assert(ismember(simInputName, allSimulationInputs), ...
                message('FixedPointTool:fixedPointTool:simulationInputError', simInputName));
            
            % evaluate the variable to get the object
            simIn = evalin('base', simInputName);
        end    
        
        function executeActions(~, workflow, actionID)
            engine = fxptui.internal.WorkflowEngineFactory.getWorkflowEngine(actionID, workflow.WorkflowSetting);
            
            actions = workflow.getActions(actionID);
            
            for idx = 1:numel(actions)
                action = actions(idx);
                action.execute(engine, workflow.WorkflowSetting);
            end
        end
        
        setIdealizedSettings(this, runName);
        
        changeRunNameAndRestoreDirty(this, runName);
        
        isDifferent = isRunNameDifferent(this, runName);
        
        [simOut, isMultiSim] = simulate(this, runName);
        
        cleanupAfterSimulation(this);
        
        delete(this);
        
        function handle(this, context)
            if ~isempty(this.NextHandler)
                this.NextHandler.handle(context);
            end
        end
        
        function setNextHandler(this, nextHandler)
            this.NextHandler = nextHandler;
        end
        
        function setSignalTolerancesStore(this)
            sdiRunId = this.DataLayer.getSDIRunIDForLastCollection(this.FPTInstance.Converter.TopModel);
            if ~isempty(sdiRunId)
                this.DataLayer.setSignalTolerancesStore(this.FPTInstance.Converter.TopModel, this.FPTInstance.Converter.AllSystems, sdiRunId);
            end
        end
        
        function updateSystemBehaviorSettings(this)
            workflowController = this.FPTInstance.getWorkflowController();
            workflowController.publishSystemBehaviorSettings();
        end
    end
    
end
