classdef BlockParameterSpreadsheet < Simulink.io.FileType
% BlockParameterSpreadsheet illustrates how to read and write to a custom spreadsheet format for block parameters using the Simulink.io.FileType class and methods.
%   The custom spreadsheet containing block parameters has the following format.
%   Row one has the following column headers for columns 1 through 3 BlockPath, ParamName, ParamValue.
%   The following rows represent a single block parameter whos values columns 1 through 3 contain the block path to the block of the parameter,
%   the parameter name, and finally the parameter value. To export represent each sheet as a vector of Simulink.Simulation.BlockParameters.
%
%   Examples:
%         % Write sheets of simulation parameters for model vdp block Mu and Constant
%         sheetOfParams(1) = Simulink.Simulation.BlockParameter('vdp/Mu','Gain','5');
%         sheetOfParams(2) = Simulink.Simulation.BlockParameter('vdp/Constant','Value','5');
%         sheet2OfParams(1) = Simulink.Simulation.BlockParameter('vdp/Mu','Gain','2');
%         sheet2OfParams(2) = Simulink.Simulation.BlockParameter('vdp/Constant','Value','2.5');
%         
%         % Export to spreadsheet
%         aFileType = Simulink.io.BlockParameterSpreadsheet;
%         export( aFileType, 'vdpParameterSheet.xlsx', {'Sim1' 'Sim2'}, {sheetOfParams sheet2OfParams},false);
%         
%         % Whos on file
%         variablesOnFile = whos( aFileType );
%
%         % Read a spreadsheet containing parameters and simulate all cases
%         aFileType = Simulink.io.BlockParameterSpreadsheet('vdpParameterSheet.xlsx');
%         paramsOnFile = load( aFileType );
%         sheetNames = fieldnames(paramsOnFile);
%         simInput = Simulink.SimulationInput.empty(length(sheetNames),0);
%         for k =1: length(sheetNames)
%             simInput(k) = Simulink.SimulationInput;
%             simInput(k).ModelName = 'vdp';
%             simInput(k).BlockParameters = paramsOnFile.(sheetNames{k});
%         end
%         
%         % par sim
%         load_system('vdp');
%         parsim(simInput);

%   Copyright 2021 The MathWorks, Inc.

    properties ( Constant )
        RowHeaderNames = ["BlockPath", "ParamName", "ParamValue"];
        ColumnRange = "A1:C1";
    end

    %======================================================================
    methods ( Static )

        %------------------------------------------------------------------
        function isSupported = isFileSupported( fileLocation )
            isSupported = false;

            [ ~, ~, ext ] = fileparts( fileLocation );

            %If the extension is a MAT-file.
            if any(strcmpi(ext, {'.xls' '.xlsx'}))

                if exist(fileLocation,'file')
                    headerCells = readcell(fileLocation,"Range", ...
                        Simulink.io.BlockParameterSpreadsheet.ColumnRange);

                    isSupported = all(Simulink.io.BlockParameterSpreadsheet.RowHeaderNames.contains( headerCells ));
                else
                    isSupported = false;
                end
            end
        end

        %------------------------------------------------------------------
        function aFileReaderDescription = getFileTypeDescription( )
            aFileReaderDescription = 'An application specific spreadsheet type for block parameters.';
        end

    end

    %======================================================================
    methods

        %------------------------------------------------------------------
        function validateFileNameImpl( ~, fileName )

            if isempty( fileName )
                return;
            end

            %validateFileNameImpl
            [ ~, ~, ext ] = fileparts( fileName );

            %If the extension is a xls or xlsx.
            if ~any(strcmpi(ext, {'.xls' '.xlsx'}))
                throw(MException('BlockParameterSpreadSheet:notASpreadsheet', ...
                    'This file format is not supported by BlockParameterSpreadsheet.'));
            end
            
        end

        %------------------------------------------------------------------
        function aList = whosImpl( aFile )
            aList = [];

            varNames = cellstr(sheetnames(aFile.FileName));
            if isempty( varNames )

                return;
            end

            aList = struct;

            %For each name. Note 1st element is reader so ignore
            for kName = length( varNames ):-1: 1

                aList( kName ).name = ...
                    varNames{ kName };

                %Optional return type. See documentation for supported types names.
                aList( kName ).type = 'SimulationBlockParam';
            end
        end

        %------------------------------------------------------------------
        function varOut = loadAVariableImpl( aFile, varName )

            varOut = struct;


            varNames = cellstr(sheetnames(aFile.FileName));
            isTheVarName = strcmp( varNames, varName );

            if any( isTheVarName )
                data = readtable(aFile.FileName,'sheet',varName);

                theParams = Simulink.Simulation.BlockParameter.empty(0, length(data.BlockPath));
                for kParam = 1: length(data.BlockPath)

                    % some params could be text some could be numeric,
                    % or all could be numeric
                    if iscell( data.ParamValue )
                        paramVal = data.ParamValue{kParam};
                    else
                        paramVal = data.ParamValue(kParam);
                    end

                    theParams( kParam ) = ...
                        Simulink.Simulation.BlockParameter(...
                            data.BlockPath{kParam}, ...
                            data.ParamName{kParam}, ...
                            paramVal);
                end
                varOut.(varName) = theParams;
            else
                throw(MException('BlockParameterSpreadSheet:noVarOnFile', ...
                    [varName ' is not on file.']));
            end

        end

        %------------------------------------------------------------------
        function varOut = loadImpl( aFile )
            varOut = struct;

            varNames = cellstr(sheetnames(aFile.FileName));

            for k = 1: length( varNames )

                varOut.(varNames{ k }) = ...
                    importAVariable( aFile, varNames{k});
            end
        end

        %------------------------------------------------------------------
        function [didWrite, errMsg] = exportImpl( ~, fileName, cellOfVarNames, ...
                                                  cellOfVarValues, ~)
            didWrite = false;
            errMsg = '';

            %For each variable
            for kVar = 1: length( cellOfVarNames )

                if ~isa(cellOfVarValues{kVar}, 'Simulink.Simulation.BlockParameter')
                    throw(MException('BlockParameterSpreadSheet:exportNotBlockParam', ...
                        'Simulink.io.BlockParameterSpreadSheet supports exporting only scalar or vector objects of type Simulink.Simulation.BlockParameter.'));
                end
                
                % each value in the cell is expected to be an vector of Simulink.SimulationBlockParameters
                blockParamsForSheet = cellOfVarValues{kVar};

                
                
                nBlockParams = length(blockParamsForSheet);
                BlockPath = cell(nBlockParams,1);
                ParamName = cell(nBlockParams,1);
                ParamValue = cell(nBlockParams,1);

                % for each param assign the parameter properties in the cell arrays
                for kParam = 1: nBlockParams
                    BlockPath{kParam} = blockParamsForSheet(kParam).BlockPath;
                    ParamName{kParam} = blockParamsForSheet(kParam).Name;
                    ParamValue{kParam} = blockParamsForSheet(kParam).Value;
                end

                % create a table from the cell arrays
                tableToWrite = table(BlockPath, ParamName, ParamValue);
                try
                    % try to write table
                    writetable( tableToWrite, fileName, "sheet", cellOfVarNames{kVar});
                    didWrite = true;
                catch ME

                    % return error message and failure to write
                    errMsg = ME.message;
                    didWrite = false;
                end
            end
        end
    end

end
