function callbackMaskVisibilities(blk,varargin)
% CALLBACKMASKVISIBILITIES controls the enabled and visible state of dialog
% fields for PID 1DOF and 2DOF blocks
%
%   blk: the masked subsystem of the PID 1dof or 2dof block

%   Copyright 2017-2023 The MathWorks, Inc.

% Do not run initialization code if model is running
invalidStatus = {'external','running','compiled', 'restarting', 'paused','terminating'};
if any(strcmpi(get_param(bdroot(blk),'SimulationStatus'),invalidStatus))
    return
end

% Get Current Dialog Settings
dlgSett = pidpack.PIDConfig.utilGetDialogSettings(blk);
isShowAutoTune = ~(dlgSett.isDialogExternal || dlgSett.isDialogExternalTs);
isShowSatLimits = pidpack.PIDConfig.utilGetNumericDialogSettings(blk,'SatLimitsSource')==1;
isUseExternalDerivative = dlgSett.isDialogUseExternalDerivative;

%% Determine which Mask Parameters to Check
if nargin>1
    changeFields = varargin{1};
else
    changeFields = {''};
end
maskObj = get_param(blk,'MaskObject');
blkObj = get_param(blk, 'Object');
params = get_param(blk,'MaskNames');
mEnables_old = get_param(blk,'MaskEnables');
mVisible_old = get_param(blk,'MaskVisibilities');
se = cell2struct(mEnables_old,params,1);
sv = cell2struct(mVisible_old,params,1);

%% Set Visibility of Mask Parameters (fields)
% Sample Time
param = 'SampleTime';
enable = (dlgSett.isDialogDiscrete && ~dlgSett.isDialogExternalTs);
visible = true;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Integrator Method
param = 'IntegratorMethod';
enable = dlgSett.isDialogDiscrete && dlgSett.isDialogIntegral;
visible = dlgSett.isDialogDiscrete && dlgSett.isDialogIntegral;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Filter Method
param = 'FilterMethod';
enable = dlgSett.isDialogDiscreteFilter;
visible = dlgSett.isDialogDiscreteFilter;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% External Sample Time Checkbox
param = 'UseExternalTs';
enable = dlgSett.isDialogDiscrete;
visible = dlgSett.isDialogDiscrete;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Use Filter Checkbox
param = 'UseFilter';
enable = dlgSett.isDialogDiscrete && dlgSett.isDialogDerivative;
visible = dlgSett.isDialogDerivative;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Derivative Source Dropdown
param = 'UseExternalDerivativeSource';
enable = dlgSett.isDialogDerivative;
visible = dlgSett.isDialogDerivative;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Use I*Ts Checkbox
param = 'UseKiTs';
enable = dlgSett.isDialogIntegral;
visible = dlgSett.isDialogIntegral;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Proportional Gain
param = 'P';
enable = dlgSett.isDialogProportional && ~dlgSett.isDialogExternal;
visible = dlgSett.isDialogProportional && ~dlgSett.isDialogExternal;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Integral Gain
param = 'I';
enable = dlgSett.isDialogIntegral && ~dlgSett.isDialogExternal;
visible = dlgSett.isDialogIntegral && ~dlgSett.isDialogExternal;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Derivative Gain
param = 'D';
enable = dlgSett.isDialogDerivative && ~dlgSett.isDialogExternal;
visible = dlgSett.isDialogDerivative && ~dlgSett.isDialogExternal;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Filter Coefficient
param = 'N';
enable = dlgSett.isDialogFilter && ~dlgSett.isDialogExternal;
visible = dlgSett.isDialogDerivative && ~dlgSett.isDialogExternal;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

if dlgSett.is2DOF
    % Setpoint Weight, b
    param = 'b';
    enable = dlgSett.isDialogProportional && ~dlgSett.isDialogExternal;
    visible = dlgSett.isDialogProportional && ~dlgSett.isDialogExternal;
    [se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);
    
    % Setpoint Weight, c
    param = 'c';
    enable = dlgSett.isDialogDerivative && ~dlgSett.isDialogExternal && ~isUseExternalDerivative;
    visible = dlgSett.isDialogDerivative && ~dlgSett.isDialogExternal && ~isUseExternalDerivative;
    [se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);
end

% Tuner Method Option
param = 'TunerSelectOption';
enable = isShowAutoTune;
visible = isShowAutoTune;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Initial Condition Source
param = 'InitialConditionSource';
enable = dlgSett.isDialogIntegral || dlgSett.isDialogDerivative;
visible = dlgSett.isDialogIntegral || dlgSett.isDialogDerivative;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Integrator Initial Condition
param = 'InitialConditionForIntegrator';
enable = dlgSett.isDialogIntegral && ~dlgSett.isDialogExtICs;
visible = dlgSett.isDialogIntegral && ~dlgSett.isDialogExtICs;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Derivative Filter Initial Condition
param = 'InitialConditionForFilter';
enable = dlgSett.isDialogFilter && ~dlgSett.isDialogExtICs;
visible = dlgSett.isDialogFilter && ~dlgSett.isDialogExtICs;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Differentiator Initial Condition
param = 'DifferentiatorICPrevScaledInput';
enable = dlgSett.isDialogDifferentiator && ~dlgSett.isDialogExtICs;
visible = dlgSett.isDialogDifferentiator && ~dlgSett.isDialogExtICs;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% External Reset
param = 'ExternalReset';
enable = dlgSett.isDialogIntegral || (dlgSett.isDialogDerivative);
visible = dlgSett.isDialogIntegral || (dlgSett.isDialogDerivative);
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Ignore Limit When Linearizing
param = 'IgnoreLimit';
enable = dlgSett.isDialogIntegral || (dlgSett.isDialogDerivative);
visible = dlgSett.isDialogIntegral || (dlgSett.isDialogDerivative);
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% External Saturation Limits
param = 'SatLimitsSource';
enable = dlgSett.isDialogLimitOutput;
visible = 'on';
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Upper Saturation Limit
param = 'UpperSaturationLimit';
enable = dlgSett.isDialogLimitOutput && ~dlgSett.isDialogUseExternalSatLimits;
visible = isShowSatLimits;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Lower Saturation Limit
param = 'LowerSaturationLimit';
enable = dlgSett.isDialogLimitOutput && ~dlgSett.isDialogUseExternalSatLimits;
visible = isShowSatLimits;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Linearize as Gain
param = 'LinearizeAsGain';
enable = dlgSett.isDialogLimitOutput && ~dlgSett.isDialogUseExternalSatLimits;
visible = isShowSatLimits;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Anti Windup Mode
param = 'AntiWindupMode';
enable = dlgSett.isDialogLimitOutput && dlgSett.isDialogIntegral;
visible = dlgSett.isDialogIntegral;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Upper Integrator Saturation Limit
param = 'UpperIntegratorSaturationLimit';
enable = dlgSett.isDialogLimitIntegrator;
visible = 'on';
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Lower Integrator Saturation Limit
param = 'LowerIntegratorSaturationLimit';
enable = dlgSett.isDialogLimitIntegrator;
visible = 'on';
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Back-Calculation Coefficient
param = 'Kb';
enable = dlgSett.isDialogIntegral && dlgSett.isDialogLimitOutput && dlgSett.AWMethod == 2; % Back-calculation
visible = dlgSett.isDialogIntegral && dlgSett.AWMethod == 2;  % Back-calculation
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Tracking Mode Check Box
param = 'TrackingMode';
enable = dlgSett.isDialogIntegral;
visible = dlgSett.isDialogIntegral;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Tracking Mode Coeffficient
param = 'Kt';
enable = dlgSett.isDialogIntegral && dlgSett.isDialogTracking;
visible = dlgSett.isDialogIntegral;
[se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible);

% Data Type String Visibilities
[se, sv] = pidpack.PIDConfig.utilDataTypeStrVisibilities(se, sv, blk, dlgSett);

% Update Enabled and Visible Properties
mEnables = struct2cell(orderfields(se,params));
mVisible = struct2cell(orderfields(sv,params));
if ~(all(strcmp(mEnables_old,mEnables)) && all(strcmp(mVisible_old,mVisible)))
    blkObj.MaskEnables = mEnables;
    blkObj.MaskVisibilities = mVisible;
end

% Toggle Use Filter Checkbox if in Continuous Time
if ~dlgSett.isDialogDiscrete
    blkObj.UseFilter = 'on';
end

% State Attribute Visibilities
pidpack.PIDConfig.utilStateAttributes(blk,dlgSett);
dlgObj = maskObj.getDialogControl('txtNoStateTab');
if strcmpi(dlgSett.Controller,'P') || (~dlgSett.isDialogIntegral && ~dlgSett.isDialogFilter)
    dlgObj.Visible = 'on';
else
    dlgObj.Visible = 'off';
end

% Initialization Visibilities
dlgObj = maskObj.getDialogControl('txtNoInitializationTab');
if strcmpi(dlgSett.Controller,'P') || (~dlgSett.isDialogIntegral && ~dlgSett.isDialogFilter)
    dlgObj.Visible = 'on';
else
    dlgObj.Visible = 'off';
end

%% Change Position of Checkboxes
if dlgSett.isDialogExternal
    ParamRow = 'new';    
else
    ParamRow = 'current';
end

% Ki*Ts Checkbox
tmpMaskParam = maskObj.Parameters(strcmp(blkObj.MaskNames,'UseKiTs'));
if ~strcmp(tmpMaskParam.DialogControl.Row,ParamRow)
    tmpMaskParam.DialogControl.Row = ParamRow;
end

% Derivative Source Checkbox
tmpMaskParam = maskObj.Parameters(strcmp(blkObj.MaskNames,'UseExternalDerivativeSource'));
if ~strcmp(tmpMaskParam.DialogControl.Row,ParamRow)
    tmpMaskParam.DialogControl.Row = ParamRow;
end

% Filter Derivative Checkbox
tmpMaskParam = maskObj.Parameters(strcmp(blkObj.MaskNames,'UseFilter'));
if ~strcmp(tmpMaskParam.DialogControl.Row,ParamRow)
    tmpMaskParam.DialogControl.Row = ParamRow;
end
%% Set Visibility of Panels 
% Integrator and Filter Method
dlgObj = maskObj.getDialogControl('grpIntFiltMethod');
if dlgSett.isDialogDiscrete && (dlgSett.isDialogIntegral || dlgSett.isDialogDiscreteFilter)
    dlgObj.Visible = 'on';
    dlgObj.Enabled = 'on';
else
    dlgObj.Visible = 'off';
    dlgObj.Enabled = 'off';
end

% Initial Conditions
dlgObj = maskObj.getDialogControl('grpInitialConditions');
if dlgSett.isDialogIntegral || (dlgSett.isDialogDerivative)
    dlgObj.Enabled = 'on';
    dlgObj.Visible = 'on';
else
    dlgObj.Enabled = 'off';
    dlgObj.Visible = 'off';
end

% External Reset
dlgObj = maskObj.getDialogControl('grpExternalReset');
if dlgSett.isDialogIntegral || (dlgSett.isDialogDerivative)
    dlgObj.Enabled = 'on';
    dlgObj.Visible = 'on';
else
    dlgObj.Enabled = 'off';
    dlgObj.Visible = 'off';
end

% Anti-windup Mode
dlgObj = maskObj.getDialogControl('grpAntiWindup');
if dlgSett.isDialogIntegral
    dlgObj.Enabled = 'on';
    dlgObj.Visible = 'on';
else
    dlgObj.Enabled = 'off';
    dlgObj.Visible = 'off';
end

% Integrator Saturation
dlgObj = maskObj.getDialogControl('grpIntegratorSaturation');
if dlgSett.isDialogIntegral
    dlgObj.Enabled = 'on';
    dlgObj.Visible = 'on';
else
    dlgObj.Enabled = 'off';
    dlgObj.Visible = 'off';
end

% Tracking Mode
dlgObj = maskObj.getDialogControl('grpTracking');
if dlgSett.isDialogIntegral
    dlgObj.Enabled = 'on';
    dlgObj.Visible = 'on';
else
    dlgObj.Enabled = 'off';
    dlgObj.Visible = 'off';
end

% Integrator State
dlgObj = maskObj.getDialogControl('grpIntegratorState');
if dlgSett.isDialogIntegral
    dlgObj.Enabled = 'on';
    dlgObj.Visible = 'on';
else
    dlgObj.Enabled = 'off';
    dlgObj.Visible = 'off';
end

% Filter State
dlgObj = maskObj.getDialogControl('grpFilterState');
if dlgSett.isDialogFilter
    dlgObj.Enabled = 'on';
    dlgObj.Visible = 'on';
else
    dlgObj.Enabled = 'off';
    dlgObj.Visible = 'off';
end

% Tune Button
dlgObj = maskObj.getDialogControl('TuneButton');
if isShowAutoTune
    dlgObj.Enabled = 'on';
    dlgObj.Visible = 'on';
else
    dlgObj.Enabled = 'off';
    dlgObj.Visible = 'off';
end

% Automated Tuning
dlgObj = maskObj.getDialogControl('grpAutoTune');
if isShowAutoTune
    dlgObj.Enabled = 'on';
    dlgObj.Visible = 'on';
else
    dlgObj.Enabled = 'off';
    dlgObj.Visible = 'off';
end


%% Update Compensator Formula
% Only update the compensator formula if the following fields are changed:
% Time domain, Form, Controller type, Integrator Method, Filter Method, Use
% Filter
sys = getfullname(blk);
if ~(startsWith(sys,'simulink') || startsWith(sys,'slpidlib')) % Don't update images if in Simulink library
updateCompFormula = strcmp(changeFields,'UpdateCompFormula');

    if updateCompFormula
        imagepath = fullfile(matlabroot,'toolbox','simulink','blocks','pid_Images');
        if dlgSett.is2DOF
            p = getPID2dofFormulaImage(blkObj, imagepath);
        else
            p = getPID1dofFormulaImage(blkObj, imagepath);
        end

        % Update Compensator Image
        dialogControlCompImage = maskObj.getDialogControl('CompensatorFormulaImage');
        currImageFilePath = dialogControlCompImage.FilePath;
        if ~strcmp(currImageFilePath,p)  % If unchanged, do nothing
            % Update Compensator Image in Dialog
            dialogControlCompImage.FilePath = p;

            % Update Compensator Image Underneath Mask
            sys = getfullname(blk);
            maskObj2 = get_param([sys '/PID Compensator Formula'],'MaskObject');
            maskObj2.Display = ['image(''' p ''',''center'');'];
        end
    end
end
end

%% 1-DOF Compensator Formula Image
function p = getPID1dofFormulaImage(source, imagepath)

Controller = source.Controller;
TimeDomain = source.TimeDomain;
Form = source.Form;

switch TimeDomain
    case 'Continuous-time'
        TimeDomain = 'CT';
        idxT = 1;
    case 'Discrete-time'
        TimeDomain = 'DT';
        idxT = 2;
end

switch source.IntegratorMethod
    case 'Forward Euler'
        IntegratorMethod = {'','0'};
    case 'Backward Euler'
        IntegratorMethod = {'','1'};
    case 'Trapezoidal'
        IntegratorMethod = {'','2'};
end

if strcmp(source.UseFilter,'on')
    switch source.FilterMethod
        case 'Forward Euler'
            FilterMethod = {'','0'};
        case 'Backward Euler'
            FilterMethod = {'','1'};
        case 'Trapezoidal'
            FilterMethod = {'','2'};
    end
else
    FilterMethod = {'','3'};
end
spc = {'','_'};

switch Controller
    case 'PID'
        f = [Controller '_' Form ...
            '_' TimeDomain spc{idxT} IntegratorMethod{idxT} FilterMethod{idxT}];
    case 'PI'
        f = [Controller '_' Form ...
            '_' TimeDomain spc{idxT} IntegratorMethod{idxT} ];
    case 'PD'
        f = [Controller '_' Form ...
            '_' TimeDomain spc{idxT} FilterMethod{idxT}];
    case 'P'
        f = Controller;
    case 'I'
        f = [Controller '_' TimeDomain ...
            spc{idxT} IntegratorMethod{idxT}];
end

f = [f '.png'];
p = fullfile(imagepath, f);

end

%% 2-DOF Compensator Formula Image
function p = getPID2dofFormulaImage(source, imagepath)

Controller = source.Controller;
TimeDomain = source.TimeDomain;
Form = source.Form;

switch TimeDomain
    case 'Continuous-time'
        TimeDomain = 'CT';
        idxT = 1;
    case 'Discrete-time'
        TimeDomain = 'DT';
        idxT = 2;
end

switch source.IntegratorMethod
    case 'Forward Euler'
        IntegratorMethod = {'','0'};
    case 'Backward Euler'
        IntegratorMethod = {'','1'};
    case 'Trapezoidal'
        IntegratorMethod = {'','2'};
end

if strcmp(source.UseFilter,'on')
    switch source.FilterMethod
        case 'Forward Euler'
            FilterMethod = {'','0'};
        case 'Backward Euler'
            FilterMethod = {'','1'};
        case 'Trapezoidal'
            FilterMethod = {'','2'};
    end
else
    FilterMethod = {'','3'};
end
spc = {'','_'};

switch Controller
    case 'PID'
        f = [Controller '2dof_' Form ...
            '_' TimeDomain spc{idxT} IntegratorMethod{idxT} FilterMethod{idxT}];
    case 'PI'
        f = [Controller '2dof_' Form ...
            '_' TimeDomain spc{idxT} IntegratorMethod{idxT} ];
    case 'PD'
        f = [Controller '2dof_' Form ...
            '_' TimeDomain spc{idxT} FilterMethod{idxT}];
end

f = [f '.png'];
p = fullfile(imagepath, f);


end

%% Local Set Param for Enable and Visible
function [se, sv] = localSetParamEnableVisible(se,sv,param,enable,visible)
if enable
    se.(param) = 'on';
else
    se.(param) = 'off';
end
if visible
    sv.(param) = 'on';
else
    sv.(param) = 'off';
end

end
