function dialogCallbacks(blkH,callbackIdentifier)
% dialogCallbacks Dialog callbacks for the recursive polynomial model
% estimator block
%
%   Inputs:
%     blkH               - Block handle
%     callbackIdentifier - Source of the callback

%   Copyright 2013-2023 The MathWorks, Inc.
switch callbackIdentifier
    case 'comboBoxModelStructure'
        localCallbackComboBoxModelStructure(blkH);
    case 'comboBoxEstimationMethod'
        % Same callback fcn with the RLS block
        controllib.internal.blocks.rls.dialogCallbacks(blkH,callbackIdentifier);
    case 'comboBoxInitialEstimateSource'
        localCallbackComboBoxInitialEstimateSource(blkH);
    case 'comboBoxHistory'
        localCallbackComboBoxHistory(blkH);
    otherwise
        assert(false);
end
end

function localCallbackComboBoxModelStructure(blkH)
% localCallbackComboBoxModelStructure ModelStructure widget callback
%
% * Update visibility of the appropriate polynomial fields among A, B, C,
% D, F
% * Update visibility of InitialOutputs, InitialInputs
%
% Inputs:
%   -blkH: Handle to the online polynomial system identification block

% History='Finite' is only supported for AR, ARX, OE
%
% Get the old model structure from blocks underneath mask. These are not
% updated yet.
blockPath = getfullname(blkH);
estimatorBlockPath = [blockPath '/Estimator'];
oldModelStructure = get_param(estimatorBlockPath,'MaskType'); % 'rarx', 'rarmax' etc.
oldModelStructure = upper(extractAfter(oldModelStructure,1));
% Get the new model structure
newModelStructure = get_param(blkH,'ModelStructure');
% Get History
isHistoryFinite = controllib.internal.blocks.rls.isHistoryFinite(blkH);
% Validate. In case of error revert back to the old setting
if isHistoryFinite && ~localIsFiniteHistoryEstimationSupported(newModelStructure)
    set_param(blkH,'ModelStructure',oldModelStructure);
    error(message('Controllib:blocks:recursiveEstimationFiniteHistoryUnsupportedModelStructure'));
end

maskNames = get_param(blkH,'MaskNames');
maskVisibilities = get_param(blkH,'MaskVisibilities');
initialEstimateSource = get_param(blkH,'InitialEstimateSource');
isInitialEstimateSourceNoneOrExternal = ismember(initialEstimateSource,...
   {slsvInternal('slsvGetEnStringFromCatalog',...
   'Controllib:blocks:recursiveEstimationMaskInitialEstimateNone'),...
   slsvInternal('slsvGetEnStringFromCatalog',...
   'Controllib:blocks:recursiveEstimationMaskInitialEstimateExternal')});
activePolynomials = ...
    recursiveEstimation.getRequiredPolynomialsForModelStructure(newModelStructure);
allPolynomials = localGetAllPolynomialNames();
% Show/hide edit boxes
hasChange1 = false();
for kk=1:numel(allPolynomials)
    idx = strcmp(allPolynomials{kk},maskNames);
    if ismember(allPolynomials{kk},activePolynomials)
        desiredVisibility = 'on';
    else
        desiredVisibility = 'off';
    end
    if ~strcmp(maskVisibilities{idx}, desiredVisibility)
        hasChange1 = true();
        maskVisibilities{idx} = desiredVisibility;
    end
    
    % The visibility of the nk and B0 edit boxes are tied together if the
    % initial estimate source is None or External.
    if isInitialEstimateSourceNoneOrExternal && strcmp(allPolynomials{kk},'B0')
        idx = strcmp('nk',maskNames);
        if ~strcmp(maskVisibilities{idx},desiredVisibility)
            hasChange1 = true();
            maskVisibilities{idx} = desiredVisibility;
        end
    end
end

% Update InitialOutputs, InitialInputs
[hasChange2,maskVisibilities] = ...
    localConfigureInitialInputsAndInitialOutputs(...
        maskNames,maskVisibilities,...
        newModelStructure,isHistoryFinite,initialEstimateSource);

% Apply changes, if necessary
if hasChange1 || hasChange2
    set_param(blkH,'MaskVisibilities',maskVisibilities);
end
end

function localCallbackComboBoxInitialEstimateSource(blkH)
% localCallbackComboBoxInitialEstimateModel InitialEstimateSource widget callback
%
%   Inputs:
%     blkH - Block handle

% Get mask state
maskNames = get_param(blkH,'MaskNames');
maskPrompts = get_param(blkH,'MaskPrompts');
maskVisibilities = get_param(blkH,'MaskVisibilities');
maskEnables = get_param(blkH,'MaskEnables');
isHistoryFinite = controllib.internal.blocks.rls.isHistoryFinite(blkH);
% g1816845 - Use MaskValues instead of get_param because some of these
% widgets may have Visible='off'
maskValues = get_param(blkH,'MaskValues');
initialEstimateSource = maskValues{strcmp(maskNames,'InitialEstimateSource')};
estimationMethod =  maskValues{strcmp(maskNames,'EstimationMethod')};
modelStructure = maskValues{strcmp(maskNames,'ModelStructure')};

% Configure
[hasChange,maskPrompts,maskEnables,maskVisibilities] = ...
    localInitializationProperties(...
    maskNames, maskPrompts, maskEnables, maskVisibilities,...
    isHistoryFinite, initialEstimateSource, estimationMethod, modelStructure);
% Apply the changes
if hasChange
    set_param(blkH,...
        'MaskPrompts',maskPrompts,...
        'MaskVisibilities',maskVisibilities,...
        'MaskEnables',maskEnables);
end
% Translate the A0, B0, ... widgets: Meaning of these widgets depends on
% the InitialEstimateSource and History widgets
localTranslateInitialParameterData(blkH, initialEstimateSource);
end

function [hasChange,maskPrompts,maskEnables,maskVisibilities] = ...
    localInitializationProperties(...
    maskNames, maskPrompts, maskEnables, maskVisibilities,...
    isHistoryFinite, initialEstimateSource, estimationMethod, modelStructure)
% Configure widgets that control model/data initialization:
% * A0, B0, C0, D0, F0
% * nk
% * P0
% * InitialInputs, InitialOutputs

% A0, B0, ...
[hasChange1,maskPrompts] = localConfigurePolynomials(...
    maskNames, maskPrompts, initialEstimateSource);
% nk
[hasChange2,maskVisibilities] = localConfigureNk(...
       maskNames, maskVisibilities, initialEstimateSource, modelStructure);
% Parameter covariance settings
[hasChange3, maskVisibilities, maskEnables] = ...
    controllib.internal.blocks.rls.dialogCallbackCovariance(...
       maskNames, maskVisibilities, maskEnables,...
       isHistoryFinite, estimationMethod, initialEstimateSource);
% InitialInputs, InitialOutputs
[hasChange4,maskVisibilities] = ...
    localConfigureInitialInputsAndInitialOutputs(...
        maskNames, maskVisibilities,...
        modelStructure,isHistoryFinite,initialEstimateSource);
% Has anything changed on the mask?
hasChange = hasChange1 | hasChange2 | hasChange3 | hasChange4;
% Caller must invoke set_param, hence all outputs are used
assert(nargout==4);
end


function localTranslateInitialParameterData(blkH, initialEstimateSource)
% Translate the InitialParameterData widget
%
% A0, B0, ... widgets have 2 different meanings, based on InitialEstimateSource widget:
% * # of parameters
% * Initial values of the polynomial coefficients
%
% The widget stores:
% * # of parameters, if InitialEstimateSource='None' or 'External'
% * Initial parameter estimates, if InitialEstimateSource='Internal'

switch initialEstimateSource
    case {...
            slsvInternal('slsvGetEnStringFromCatalog','Controllib:blocks:recursiveEstimationMaskInitialEstimateNone'),...
            slsvInternal('slsvGetEnStringFromCatalog','Controllib:blocks:recursiveEstimationMaskInitialEstimateExternal'),...
            }
        newInitialEstimateSourceType = 'Orders';
    case slsvInternal('slsvGetEnStringFromCatalog','Controllib:blocks:recursiveEstimationMaskInitialEstimateInternal')
        newInitialEstimateSourceType = 'Coefficients';
end

controllib.internal.blocks.rls.dialogCallbackStoreDialogData(blkH,'model',newInitialEstimateSourceType);
end


function [hasChange,maskPrompts] = localConfigurePolynomials(...
    maskNames, maskPrompts, initialEstimateSource)
%
%
% Algorithms require na, nb, ..., nk values. These can be directly asked to
% the user, or derived from initial estimates for A(q), B(q), ...
% polynomials.
switch initialEstimateSource
    case {...
            slsvInternal('slsvGetEnStringFromCatalog','Controllib:blocks:recursiveEstimationMaskInitialEstimateNone'),...
            slsvInternal('slsvGetEnStringFromCatalog','Controllib:blocks:recursiveEstimationMaskInitialEstimateExternal'),...
            }
        % User is providing na, nb, .., nk directly
        promptStr.A0 = 'Ident:simulink:recursiveEstimationMaskNumberOfParametersA';
        promptStr.B0 = 'Ident:simulink:recursiveEstimationMaskNumberOfParametersB';
        promptStr.C0 = 'Ident:simulink:recursiveEstimationMaskNumberOfParametersC';
        promptStr.D0 = 'Ident:simulink:recursiveEstimationMaskNumberOfParametersD';
        promptStr.F0 = 'Ident:simulink:recursiveEstimationMaskNumberOfParametersF';
    case slsvInternal('slsvGetEnStringFromCatalog','Controllib:blocks:recursiveEstimationMaskInitialEstimateInternal')
        % User is providing initial coefficients of A(q), B(q), ...
        promptStr.A0 = 'Ident:simulink:recursiveEstimationMaskInitialA';
        promptStr.B0 = 'Ident:simulink:recursiveEstimationMaskInitialB';
        promptStr.C0 = 'Ident:simulink:recursiveEstimationMaskInitialC';
        promptStr.D0 = 'Ident:simulink:recursiveEstimationMaskInitialD';
        promptStr.F0 = 'Ident:simulink:recursiveEstimationMaskInitialF';
end

% Change mask state variables, if necessary
hasChange = false();
allPolynomials = localGetAllPolynomialNames();
for kk=1:numel(allPolynomials)
    idx = strcmp(allPolynomials{kk},maskNames);
    if ~strcmp(maskPrompts{idx}, promptStr.(allPolynomials{kk}))
        hasChange = true();
        maskPrompts{idx} = promptStr.(allPolynomials{kk});
    end
end
% Caller must invoke set_param, both outputs must be used
assert(nargout==2);
end

function [hasChange,maskVisibilities] = localConfigureNk(...
       maskNames, maskVisibilities, initialEstimateSource, modelStructure)
% Configure the visibility of the nk widget   
%
% The widget is NOT visible if:
% * Model structure does not contain B or F polynomial (AR or ARMA)
% OR
% * InitialEstimateSource='Internal'
%
% In the latter case, nk is derived from the provided initial value of B
% polynomial

% Determine the visibility
activePolynomials = ...
    recursiveEstimation.getRequiredPolynomialsForModelStructure(modelStructure);
if ~any(ismember({'B0','F0'},activePolynomials))
    % time-series model
    desiredVisibility = 'off';
elseif strcmp(initialEstimateSource, ...
        slsvInternal('slsvGetEnStringFromCatalog','Controllib:blocks:recursiveEstimationMaskInitialEstimateInternal'))
    desiredVisibility = 'off';
else
    desiredVisibility = 'on';
end
% Set desired mask states
idx = strcmp(maskNames,'nk');
hasChange = ~strcmp(maskVisibilities{idx},desiredVisibility);
if hasChange
    maskVisibilities{idx} = desiredVisibility;
end
% Caller must invoke set_param, hence both outputs are used
assert(nargout==2);
end

function [hasChange,maskVisibilities] = ...
    localConfigureInitialInputsAndInitialOutputs(...
        maskNames,maskVisibilities,...
        modelStructure,isHistoryFinite,initialEstimateSource)
% Configure the InitialInputs and InitialOutputs widgets
%
% Inputs:
%     maskNames             - Value of the MaskNames block property
%     maskVisibilities      - Value of the MaskVisibilities block property
%     modelStructure        - AR, ARX, ...
%     isHistoryFinite       - Did user choose 'Finite' in the History widget?
%     initialEstimateSource - Value of the InitialEstimateSource widget
%
% Visibility: 
% * InitialOutputs is visible only when History='Finite' and
% InitialEstimateSource='Internal'
% * InitialInputs is visible only when History='Finite' and
% InitialEstimateSource='Internal' and this is not a time-series model

% Determine the desired visibility
if isHistoryFinite && ...
        strcmp(initialEstimateSource, ...
               slsvInternal('slsvGetEnStringFromCatalog',...
                            'Controllib:blocks:recursiveEstimationMaskInitialEstimateInternal'))
    desiredVisibilityInitialOutputs = 'on';
    if localIsTimeSeriesModel(modelStructure)
        desiredVisibilityInitialInputs = 'off';
    else
        desiredVisibilityInitialInputs = 'on';
    end
else
    desiredVisibilityInitialOutputs = 'off';
    desiredVisibilityInitialInputs = 'off';
end
% Apply changes to InitialOutputs
idx = strcmp('InitialOutputs',maskNames);
hasChange1 = ~strcmp(maskVisibilities{idx},desiredVisibilityInitialOutputs);
if hasChange1
    maskVisibilities{idx} = desiredVisibilityInitialOutputs;
end
% Apply changes to InitialRegressors
idx = strcmp('InitialInputs',maskNames);
hasChange2 = ~strcmp(maskVisibilities{idx},desiredVisibilityInitialInputs);
if hasChange2
    maskVisibilities{idx} = desiredVisibilityInitialInputs;
end
% Has anything changed on the mask?
hasChange = hasChange1 | hasChange2;
% Caller must invoke set_param, hence both outputs are used
assert(nargout==2);
end

function localCallbackComboBoxHistory(blkH)
% localCallbackComboBoxHistory Callback for the History widget
%
% * Ensure the chosen History option is supported for the chosen ModelStructure
% * Update all dependent widgets
%
%
%   History widget impacts:
%   * WindowLength editbox visibility: 'on' if History='Finite', 'off'
%   otherwise
%   * EstimationMethod combobox visibility: 'on' if History='Infinite'.
%   * AdaptationParameter, NormalizationBias visibility: 'off' if History='Finite'. 
%   * InitialParameterData, P0, InitialOutputs, InitialRegressors:
%   Visibility depends on History

% History='Finite' is only supported by AR and ARX for now
isHistoryFinite = controllib.internal.blocks.rls.isHistoryFinite(blkH);
modelStructure = get_param(blkH,'ModelStructure');
if isHistoryFinite && ~localIsFiniteHistoryEstimationSupported(modelStructure)
    set_param(blkH,'History',slsvInternal('slsvGetEnStringFromCatalog','Controllib:blocks:recursiveEstimationMaskInfinite'));
    error(message('Controllib:blocks:recursiveEstimationFiniteHistoryUnsupportedModelStructure'));
end

% Get necessary mask parameters for updates
maskNames = get_param(blkH,'MaskNames');
maskVisibilities = get_param(blkH,'MaskVisibilities');
maskPrompts = get_param(blkH,'MaskPrompts');
maskEnables = get_param(blkH,'MaskEnables');
% g1816845 - Use MaskValues instead of get_param because some of these
% widgets may have Visible='off'
maskValues = get_param(blkH,'MaskValues');
initialEstimateSource = maskValues{strcmp(maskNames,'InitialEstimateSource')};
estimationMethod =  maskValues{strcmp(maskNames,'EstimationMethod')};
modelStructure = maskValues{strcmp(maskNames,'ModelStructure')};

% EstimationMethod configuration
[hasChange1,maskVisibilities] = ...
    controllib.internal.blocks.rls.dialogCallbackEstimationMethod(...
    maskNames, maskVisibilities, isHistoryFinite);
% WindowLength configuration
[hasChange2,maskVisibilities] = ...
    controllib.internal.blocks.rls.dialogCallbackWindowLength(...
    maskNames, maskVisibilities, isHistoryFinite);
% AdaptationParameter configuration
[hasChange3,maskVisibilities,maskPrompts] = ...
    controllib.internal.blocks.rls.dialogCallbackAdaptationParameter(...
        maskNames, maskVisibilities, maskPrompts,...
        isHistoryFinite, estimationMethod);
% NormalizationBias configuration
[hasChange4,maskVisibilities] = ...
    controllib.internal.blocks.rls.dialogCallbackNormalizationBias(...
        maskNames, maskVisibilities,...
        isHistoryFinite, estimationMethod);
% Configure: A0, ..., F0, nk, P0, InitialInputs, InitialOutputs
[hasChange5,maskPrompts,maskEnables,maskVisibilities] = ...
    localInitializationProperties(...
    maskNames, maskPrompts, maskEnables, maskVisibilities,...
    isHistoryFinite, initialEstimateSource, estimationMethod, modelStructure);
% Apply changes, if there were any
if hasChange1 || hasChange2 || hasChange3 || hasChange4 || hasChange5
    set_param(blkH,...
        'MaskVisibilities',maskVisibilities,...
        'MaskPrompts',maskPrompts,...
        'MaskEnables',maskEnables);
end
% Translate InitialParameterData value: Meaning of the InitialParameterData
% depends on InitialEstimateSource and History
localTranslateInitialParameterData(blkH, initialEstimateSource);
end

function tf = localIsFiniteHistoryEstimationSupported(modelStructure)
% localIsFiniteHistoryEstimationSupported Is finite history estimation supported?
%
% Inputs:
%    modelStructure - AR, ARX, ...
switch modelStructure
    case {'AR', 'ARX', 'OE'}
        tf = true();
    case {'ARMA', 'ARMAX', 'BJ'}
        tf = false();
    otherwise
        assert(false);
end
end

function tf = localIsTimeSeriesModel(modelStructure)
% localIsTimeSeriesModel Is a given model structure a time series model?
%
% Inputs:
%    modelStructure - AR, ARX, ... 
switch modelStructure
    case {'AR','ARMA'}
        tf = true();
    case {'ARX','ARMAX','OE','BJ'}
        tf = false();
    otherwise
        assert(false);
end
end

function str = localGetAllPolynomialNames()
% localGetAllPolynomialNames Return a cell array of strings with all the
% names of the polynomials used in polynomial models
str = {'A0','B0','C0','D0','F0'};
end

% LocalWords:  online Controllib nk na nb ARMA
