classdef FPTOptimizationSettings < handle
    % FPTOptimizationSettings packages the optimization settings
    % to be sent to the client

    % Copyright 2021 The MathWorks, Inc.

    methods
        function data = packageOptimizationData(this, optimizationOptions, objName)
            % Convert option values to strings. The widgets on the client require
            % the values to be in string format.
            options = properties(optimizationOptions);
            options = setdiff(options, {'AdvancedOptions'});
            for optionIndex = 1:length(options)
                option = options{optionIndex};

                optionVal = optimizationOptions.(option);
                if isnumeric(optionVal)
                    data.(option) = num2str(optionVal);
                else
                    data.(option) = optionVal;
                end
            end

            % use function approximation utility to encode the vector of
            % word lengths
            data.AllowableWordLengths = FunctionApproximation.internal.Utils.getCompactStringForIntegerVector(optimizationOptions.AllowableWordLengths);

            data.SafetyMargin = num2str(optimizationOptions.AdvancedOptions.SafetyMargin);
            data.PerformNeighborhoodSearch = optimizationOptions.AdvancedOptions.PerformNeighborhoodSearch;

            data.UseBitWidthSum = (DataTypeOptimization.Objectives.ObjectiveType.BitWidthSum == optimizationOptions.ObjectiveFunction);

            data.UseDerivedRangeAnalysis = optimizationOptions.AdvancedOptions.UseDerivedRangeAnalysis;
            data.SimulationScenarios = this.getSimulationScenarios(optimizationOptions, objName);

            data.ToleranceSettings = this.getToleranceSettings(optimizationOptions);
        end

    end

    % These methods are hidden and not private for testing
    methods (Hidden)
        function allScenarioNames = getSimulationScenarios(~, options, objName)
            simIn = options.AdvancedOptions.SimulationScenarios;

            % create a new variable to hold on to a single or a vector of 
            % SimulationInput objects specified in the options object
            % g2482356
            if numel(simIn) >= 1
                scenarioName = [objName '_Imported_SimulationScenarios'];
    
                % put the newly created variable in base workspace
                assignin('base', scenarioName, simIn);
            end

            % add any exisiting scenarios in the workspace
            baseWorkspace = Simulink.data.BaseWorkspace;
            allScenarioNames = baseWorkspace.whos('Simulink.SimulationInput');
        end

        function toleranceSettings = getToleranceSettings(this, options)
            toleranceSettings = options.getTolerancesTable();

            fptInstance = fxptui.FixedPointTool.getExistingInstance;
            topModel = fptInstance.getModel;

            % clear signal tolerances
            dataLayer = fxptds.DataLayerInterface.getInstance();
            dataLayer.clearSignalToleranceStore(topModel);

            % g2485134 - when importing an options object that contain signals
            % that are not currently showing, we should enable dataLogging
            % on the signals so they appear on the table
            this.enableSignalLogging(toleranceSettings);

            % set store with list of all signals which have signal logging on
            dataLayer.setSignalTolerancesStore(topModel, fptInstance.Converter.AllSystems, 0);

            % update store settings
            for signalIdx = 1:height(toleranceSettings)
                signal = toleranceSettings(signalIdx, :);

                tolerances.columnName = signal.Tolerance_Type{1};
                tolerances.columnValue = num2str(signal.Tolerance_Value);

                signalId.BlockPath = signal.Path{1};
                signalId.Port = signal.Port_Index;

                tolerances.rowData = struct();
                tolerances.rowData.signalId = signalId;

                dataLayer.updateTolerances(topModel, tolerances);
            end

            % package client data
            toleranceSettings = this.packageToleranceSettings();

        end

        function toleranceSettings = packageToleranceSettings(~)
            toleranceSettings = [];

            fptInstance = fxptui.FixedPointTool.getExistingInstance;
            topModel = fptInstance.getModel;

            dataLayer = fxptds.DataLayerInterface.getInstance();
            dataStore = dataLayer.getSignalTolerancesStore(topModel);
            if ~isempty(dataStore)
                portIndices = [dataStore.PortIndex{:}];

                signalNames = {dataStore.Name};
                signalNames = [signalNames{:}];
                nonEmptySignalIndices = cellfun(@(x) ~isempty(x), signalNames, 'UniformOutput', false);
                nonEmptySignalIndices = [nonEmptySignalIndices{:}];

                signalsWithPortIndex = dataStore(portIndices > 0 & nonEmptySignalIndices, {'ID', 'Name', 'AbsTol', 'RelTol', 'TimeTol'});
                toleranceSettings = table2struct(signalsWithPortIndex);
            end
            if size(toleranceSettings, 1) == 1
                toleranceSettings = {toleranceSettings};
            end
        end

        function enableSignalLogging(~, toleranceSettings)
            for signalIdx = 1:height(toleranceSettings)
                signal = toleranceSettings(signalIdx, :);

                signalPath = signal.Path{1};
                portIndex = signal.Port_Index;
                portHandle = get_param(signalPath,'PortHandles');
                set_param(portHandle.Outport(portIndex),'DataLogging','on');
            end
        end
    end

end



