function setupStateflowUpgradeAdvisorChecks()
    %

    % Copyright 2021-2023 The MathWorks, Inc.

    cm = DAStudio.CustomizationManager;
    % register custom checks
    cm.addModelAdvisorCheckFcn(@defineUpgradeChecks);
    % register custom tasks
    cm.addModelAdvisorTaskAdvisorFcn(@defineUpgradeTasks);
end

function defineUpgradeChecks
    check = ModelAdvisor.Check('mathworks.stateflow.MachineParentedData.check');
    check.Title = message('Stateflow:upgradeadvisor:DeprecateMpdCheckTitle').getString();
    check.setCallbackFcn(@checkForMPD_StyleTwo,'None','StyleTwo');
    check.Visible = true;
    check.Enable = true;
    check.SupportLibrary = true;
    check.CSHParameters.MapKey = 'ma.simulink';
    check.CSHParameters.TopicID = 'mathworks.stateflow.MachineParentedData.check';

    action = ModelAdvisor.Action;
    action.setCallbackFcn(@convertMPDtoDSM);
    action.Name = message('Stateflow:upgradeadvisor:DeprecateMpdActionName').getString();
    action.Description = message('Stateflow:upgradeadvisor:DeprecateMpdActionDescription').getString();
    action.Enable = true;
    check.setAction(action);
    ma = ModelAdvisor.Root;
    ma.register(check);
end

function defineUpgradeTasks
    task = ModelAdvisor.Task('mathworks.stateflow.MachineParentedData.task');
    task.DisplayName = message('Stateflow:upgradeadvisor:DeprecateMpdTaskDisplayName').getString();
    task.Description = message('Stateflow:upgradeadvisor:DeprecateMpdTaskDescription').getString();
    task.setCheck('mathworks.stateflow.MachineParentedData.check');
    ma = ModelAdvisor.Root;
    ma.register(task);
    ua = UpgradeAdvisor;
    ua.addTask(task);
end

function machineDataUsed = findMachineDataInLinkedCharts(modelName, targetId)
    machineData = [];
    machineDataUsed = [];
    machineLibIds = [];
    libraryChartIds = [];

    linkCharts = find(sfroot,'-isa','Stateflow.LinkChart');

    for j=1:length(linkCharts)
        if strcmp(linkCharts(j).Machine.Name,modelName)
            id = sf('Private','block2chart',sf('get', linkCharts(j).Id, '.handle'));
            libraryChart = find(sfroot,'Id',id);
            if ~isempty(libraryChart)
                machineLibIds = [machineLibIds libraryChart.Machine.Id];
                libraryChartIds = [libraryChartIds libraryChart.Id];
            end
        end
    end

    if ~isempty(machineLibIds)
        machineLibIds = unique(machineLibIds);
        libraryChartIds = unique(libraryChartIds);
        for k=1:length(machineLibIds)
            machineData = [machineData sf('DataOf',machineLibIds(k))];
        end
        machineData = unique(machineData);
        machineDataInModel = containers.Map('KeyType','char','ValueType','double');
        for i = 1:1:length(machineData)
            mpdsH = find(sfroot,'Id',machineData(i));
            machineDataInModel(mpdsH.Name) = mpdsH.Id;
        end
        machineDataInCharts = containers.Map('KeyType','int64','ValueType','any');
        for i=1:length(libraryChartIds)
            machineDataUsed = [machineDataUsed ...
                find_machine_data_use_in_chart(libraryChartIds(i), machineData, ...
                machineDataInCharts, machineDataInModel, targetId)];
        end
        machineDataUsed = unique(machineDataUsed);
    end
end

function [ResultDescription, ResultDetails] = checkForMPD_StyleTwo(system)

    ResultDescription = {};
    ResultDetails = {};

    modelName = bdroot(system);
    machineId = sf('find','all','machine.name',modelName);
    if isempty(machineId)
        machineData = [];
    else
        machineData = sf('DataOf',machineId);
        targetId = sfprivate('acquire_target', machineId, 'sfun');
        machineData = [machineData findMachineDataInLinkedCharts(modelName,targetId)];
    end

    modelAdvisor = Simulink.ModelAdvisor.getModelAdvisor(system);
    if(isempty(machineData))
        result = ModelAdvisor.Paragraph(message('Stateflow:upgradeadvisor:DeprecateMpdCheckResultParagraph1').getString());
        ResultDescription{end+1} = result;
        addItem(result,ModelAdvisor.LineBreak);
        addItem(result,ModelAdvisor.LineBreak);
        addItem(result,ModelAdvisor.Text(message('Stateflow:upgradeadvisor:DeprecateMpdPassed').getString(),{'pass','bold'}));
        addItem(result,ModelAdvisor.LineBreak);
        addItem(result,ModelAdvisor.Text(message('Stateflow:upgradeadvisor:DeprecateMpdCheckResultPassed').getString()));
        ResultDetails{end+1} = {};
        modelAdvisor.setCheckResultStatus('Passed');
        modelAdvisor.setActionEnable(false);
    else
        result = ModelAdvisor.Paragraph(message('Stateflow:upgradeadvisor:DeprecateMpdCheckResultParagraph1').getString());
        ResultDescription{end+1} = result;
        addItem(result,ModelAdvisor.LineBreak);
        addItem(result,ModelAdvisor.LineBreak);
        addItem(result,ModelAdvisor.Text(message('Stateflow:upgradeadvisor:DeprecateMpdFailed').getString(),{'fail','bold'}));
        addItem(result,ModelAdvisor.LineBreak);
        addItem(result,ModelAdvisor.Text(message('Stateflow:upgradeadvisor:DeprecateMpdCheckResultFailed1').getString()));
        ResultDetails{end+1} = {};
        for i = 1:numel(machineData)
            ResultDetails{end}{end+1} = ModelAdvisor.Text(sf('GetHyperLinkedNameForObject',machineData(i)));
            ResultDetails{end}{end+1} = ModelAdvisor.LineBreak;
        end
        ResultDescription{end+1} = ModelAdvisor.Paragraph(message('Stateflow:upgradeadvisor:DeprecateMpdCheckResultFailed2').getString());
        ResultDetails{end+1} = {};

        modelAdvisor.setCheckResultStatus('Failed');
        modelAdvisor.setActionEnable(true);
    end
end

function result = convertMPDtoDSM(taskobj)
    mdladvObj = taskobj.MAObj;
    modelName = bdroot(mdladvObj.System);

    machineId = sf('find','all','machine.name',modelName);
    if isempty(machineId)
        result = ModelAdvisor.Paragraph(message('Stateflow:upgradeadvisor:DeprecateMpdCheckResultPassed').getString());
        mdladvObj.setActionEnable(false);
        return;
    end

    machineH = find(sfroot,'Id',machineId);
    targetId = sfprivate('acquire_target', machineId, 'sfun');
    machineDataInModel = containers.Map('KeyType','char','ValueType','double');
    mpdsH = find(machineH,'-isa','Stateflow.Data','SSId',0);
    for i = 1:1:length(mpdsH)
        machineDataInModel(mpdsH(i).Name) = mpdsH(i).Id;
    end
    machineData = sf('DataOf',machineId);
    machineDataInLinkedCharts = findMachineDataInLinkedCharts(modelName,targetId);
    
    charts = sf('get',machineId,'machine.charts');

    failed_mpds = [];
    failed_msgs = [];
    skipped_mpds = [];

    if ~machineH.IsLibrary
        % Create Simulink.Signal and Simuilink.Parameter objects for each
        % machine parented data (mpd).
        % failed_mpds: SIDs of mpds for which creating SImulink objects failed.
        % failed_msgs: Error messages for each SID in failed_mpds.
        % skipped_mpds: SIDs of mpds for which no Simulink object was created
        % intentionally, and not as a result of an error.
        [failed_mpds, failed_msgs, skipped_mpds] = create_simulink_model_signals_for_machine_data(modelName,...
            [machineData machineDataInLinkedCharts]);
    end

    if ~isempty(failed_mpds)
        [~,iA,~] = intersect(machineData,failed_mpds);
        machineData(iA) = [];
    end
    if ~isempty(skipped_mpds)
        [~,iA,~] = intersect(machineData,skipped_mpds);
        machineData(iA) = [];
    end

    if ~isempty(failed_mpds)
        [~,iA,~] = intersect(machineDataInLinkedCharts,failed_mpds);
        machineDataInLinkedCharts(iA) = [];
    end
    if ~isempty(skipped_mpds)
        [~,iA,~] = intersect(machineDataInLinkedCharts,skipped_mpds);
        machineDataInLinkedCharts(iA) = [];
    end
    
    % At this point machineData contains only SIDs of mpds, for which
    % Simulink objects were created.

    more_mpds_failed = false;

    % Iterate over each chart in the model and create chart Data objects
    % with DSM scope that resolve to those Simulink signals that are used
    % in that chart.
    machineDataInCharts = containers.Map('KeyType','uint64','ValueType','any');
    for i=1:length(charts)
        % Find those SIDs that are used in the chart, inluding deep search
        % through atomic subcharts.
        accessedMachineDataInChart = find_machine_data_use_in_chart(charts(i), ...
            machineData, machineDataInCharts, machineDataInModel, targetId);

        % failed_chart_mpds: SIDs of mpds for which we failed to create
        % chart DSM Data object.
        failed_chart_mpds = [];
        % failed_chart_msgs: Error messages for each SID in
        % failed_chart_mpds.
        failed_chart_msgs = [];

        [failed_chart_mpds, failed_chart_msgs] = create_dsms_for_chart(charts(i), ...
            accessedMachineDataInChart,machineH);

        if ~isempty(failed_chart_mpds)
            failed_mpds = [failed_mpds failed_chart_mpds];
            failed_msgs = [failed_msgs failed_chart_msgs];
            more_mpds_failed = true;
        end
    end

    if more_mpds_failed
        [~,iA,~] = intersect(machineData,failed_mpds);
        machineData(iA) = [];
    end

    % At this point machineData vector contains only SIDs of machine
    % parented data objects that succesfully produced Simulink object
    % paired with a chart DSM Data object. The rest is either in
    % failed_mpds or has been skipped from conversion (skipped_mpds).

    lockAfter = false;
    if machineH.IsLibrary && strcmp(get_param(machineH.Name,'Lock'),'on')
        lockAfter = true;
        set_param(machineH.Name,'Lock','off');
    end
    delete_machine_data(machineData);
    delete_machine_data(skipped_mpds);

    if lockAfter
        set_param(machineH.Name, 'Lock', 'on');
    end

    if(isempty(failed_mpds))
        result = ModelAdvisor.Paragraph(message('Stateflow:upgradeadvisor:DeprecateMpdConversionSuccesfulParagraph1').getString());
        addItem(result,ModelAdvisor.LineBreak);
        addItem(result,ModelAdvisor.LineBreak);
        addItem(result,ModelAdvisor.Text(message('Stateflow:upgradeadvisor:DeprecateMpdConversionSuccesfulParagraph2').getString()));
        mdladvObj.setActionEnable(false);
    else
        result = ModelAdvisor.Paragraph(message('Stateflow:upgradeadvisor:DeprecateMpdConversionUnuccesfulParagraph1').getString());
        addItem(result,ModelAdvisor.LineBreak);
        addItem(result,ModelAdvisor.LineBreak);
        addItem(result,ModelAdvisor.Text(message('Stateflow:upgradeadvisor:DeprecateMpdConversionUnuccesfulParagraph2').getString()));
        addItem(result,ModelAdvisor.LineBreak);
        for i = 1:numel(failed_mpds)
            addItem(result,ModelAdvisor.LineBreak);
            lineText = sprintf('%s : %s',sf('GetHyperLinkedNameForObject',failed_mpds(i)),failed_msgs{i}{1});
            addItem(result,ModelAdvisor.Text(lineText));
        end
        mdladvObj.setActionEnable(true);
    end
end

function [failed_mpds, failed_msgs] = create_dsms_for_chart(chartId,chartAccessedMachineData,machineH)
    failed_mpds = [];
    failed_msgs = [];

    if(isempty(chartAccessedMachineData))
        return;
    end

    rt = sfroot;
    chartObject = find(machineH,'Id',chartId);

    lockAfter = false;
    if ~machineH.IsLibrary
        mdlWks = get_param(machineH.Name,'ModelWorkspace');
    else
        if strcmp(get_param(machineH.Name,'Lock'),'on')
            lockAfter = true;
            set_param(machineH.Name,'Lock','off');
        end
    end

    for i=1:length(chartAccessedMachineData)
        machineDataId = chartAccessedMachineData(i);
        machineData = find(machineH,'Id',machineDataId);
        machineDataName = machineData.Name;

        try
            dsmObject = Stateflow.Data(chartObject);
            dsmObject.Name = machineDataName;
            if ~isequal(dsmObject.Name,machineDataName)
                delete(dsmObject);
                if ~machineH.IsLibrary
                    evalin(mdlWks,sprintf('clear(''%s'');',machineDataName));
                end
                failed_msgs{end + 1} = {message('Stateflow:upgradeadvisor:DeprecateMpdFailDataNameExists').getString()};
                failed_mpds = [failed_mpds machineDataId];
            else
                machineDataScope = machineData.Scope;
                if isequal(machineDataScope,'Parameter')
                    dsmObject.Scope = 'Parameter';
                elseif isequal(machineDataScope,'Constant') 
                    dsmObject.Scope = 'Constant';
                    dsmObject.Props.InitialValue = machineData.Props.InitialValue;
                    dsmObject.DataType = machineData.DataType;
                    dsmObject.Props.Array.Size = machineData.Props.Array.Size;
                    dsmObject.Props.Array.FirstIndex = machineData.Props.Array.FirstIndex;
                else
                    dsmObject.Scope = 'Data Store Memory';
                end
            end

        catch ME
            if ~machineH.IsLibrary
                evalin(mdlWks,sprintf('clear(''%s'');',machineDataName));
            end
            failed_msgs{end + 1} = {ME.message};
            failed_mpds = [failed_mpds machineDataId];
        end
    end

    if lockAfter
        set_param(machineH.Name,'Lock','on');
    end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function machineData = find_machine_data_use_in_chart(chartId, commonData, machineDataInCharts, machineDataInModel, targetId)
    machineData = [];

    % If we already checked this chart before, the result is stored in the map.
    if machineDataInCharts.isKey(chartId)
        value = machineDataInCharts(chartId);
        machineData = value{1};
    else
        sf('Parse',chartId);
        % Starting with R2023b machine data symbols are treated as
        % unresolved.
        % Find unresolved symbols used in this chart and compare them
        % against ids of machine-parented symbols - those that are in both
        % sets are the machine-parented data used in this chart.
        [unresolvedInData,unresolvedOutData,unresolvedLocalData, ...
         unresolvedInEvents,unresolvedOutEvents,unresolvedLocalEvents, ...
         unreslovedInMessages,unresolvedOutMessages,unresolvedLocalMessages, ...
         unresolvedFunctions,unresolvedNonRTWData,unresolvedNonRTWFunctions] = ...
         sf('UnresolvedSymbolsIn',chartId,targetId);
        machineData = [machineData, ...
                       names_to_ids(unresolvedInData,machineDataInModel), ...
                       names_to_ids(unresolvedOutData,machineDataInModel), ...
                       names_to_ids(unresolvedLocalData,machineDataInModel)];
        machineData = unique(machineData);
        % Until now machineData array contained all unresolved symbols.
        % Now, after crossreferencing them against the list of
        % machine-parented data in the model its content will be reduced
        % only to those machine-parented data that are used in this chart.
        machineData = intersect(machineData, commonData);

        % ... then find all machine-parented data used in atomic subcharts
        % of this chart, if there are any.
        % Once it's done we will have list of all machine-parented data
        % used in this chart and charts below it.
        chartH = idToHandle(sfroot,chartId);
        atomicSubcharts = find(chartH,'-isa','Stateflow.AtomicSubchart');
        for i = 1:length(atomicSubcharts)
            subchartSymbols = find_machine_data_use_in_chart(atomicSubcharts(i).Subchart.Id, ...
                commonData, machineDataInCharts, machineDataInModel, targetId);
            machineData = [machineData subchartSymbols];
            machineData = unique(machineData);
        end
        machineDataInCharts(chartId) = {machineData};
    end
end



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [failed_mpds, failed_msgs, skipped_mpds] = create_simulink_model_signals_for_machine_data(modelName,machineData)

    failed_mpds = [];
    failed_msgs = [];
    skipped_mpds = [];

    mdlWks = get_param(modelName,'ModelWorkspace');
    rt = sfroot;
    for i = 1:length(machineData)
        dataH = rt.find('id',machineData(i));
        nameCollision = false;

        if evalin(mdlWks,sprintf('exist(''%s'', ''var'')',dataH.Name)) == 1 
           if (evalin(mdlWks,sprintf('isa(%s,''Simulink.Signal'')',dataH.Name)) || ...
              evalin(mdlWks,sprintf('isa(%s,''Simulink.Parameter'')',dataH.Name))) && ...
              ~(isequal(dataH.Scope,'Imported') || isequal(dataH.Scope, 'Constant'))
                nameCollision = true;
           end
        end

        if nameCollision
           failed_msgs{end + 1} = {message('Stateflow:upgradeadvisor:DeprecateMpdFailSignalNameExists').getString()};
           failed_mpds = [failed_mpds machineData(i)];

        elseif ~isequal(dataH.Scope, 'Constant') && ~isempty(dataH.Props.Array.FirstIndex) && ...
               strcmp(dataH.Props.Array.FirstIndex,'0') == 0
           failed_msgs{end + 1} = {message('Stateflow:upgradeadvisor:DeprecateMpdFailFirstIndex').getString()};
           failed_mpds = [failed_mpds machineData(i)];

        else
           if isequal(dataH.Scope,'Imported')
               skipped_mpds = [skipped_mpds machineData(i)];
           elseif ~isequal(dataH.Scope, 'Constant')
               try
                   simulinkObjectCreated = false;
                   if isequal(dataH.Scope,'Parameter')
                       evalin(mdlWks,sprintf('%s = Simulink.Parameter;',dataH.Name));
                       simulinkObjectCreated = true;
                       paramValue = replace(dataH.Props.InitialValue, '''', '''''');
                       if isempty(paramValue)
                           paramValue = '1'; % This is a default value for Simulink.Parameter.
                       end
                       evalin(mdlWks,sprintf('%s.Value = %s;',dataH.Name,paramValue));
                   else
                       evalin(mdlWks,sprintf('%s = Simulink.Signal;',dataH.Name));
                       simulinkObjectCreated = true;
                       evalin(mdlWks,sprintf('%s.SamplingMode = ''Sample based'';',dataH.Name));
                       evalin(mdlWks,sprintf('%s.DimensionsMode = ''auto'';',dataH.Name));
                       evalin(mdlWks,sprintf('%s.InitialValue = ''%s'';',dataH.Name, ...
                           replace(dataH.Props.InitialValue,'''','''''')));
                       dataSize = dataH.Props.Array.Size;   
                       if isempty(dataSize)
                           dataSize = '1';
                       else
                           dataSize = sfprivate('upgrade_advisor_utils', 'CheckSyntaxForBrackets', dataSize);
                       end
                       evalin(mdlWks,sprintf('%s.Dimensions = %s;',dataH.Name,dataSize));
                   end

                   evalin(mdlWks,sprintf('%s.DataType = ''%s'';',dataH.Name,dataH.DataType));

                   
                   if isequal(dataH.Props.Complexity,'Off')
                       evalin(mdlWks,sprintf('%s.Complexity = ''real'';',dataH.Name));
                   else
                       evalin(mdlWks,sprintf('%s.Complexity = ''complex'';',dataH.Name));
                   end
               catch ME
                   if simulinkObjectCreated
                       evalin(mdlWks,sprintf('clear(''%s'');',dataH.Name));
                   end
                   failed_msgs{end + 1} = {ME.message};
                   failed_mpds = [failed_mpds machineData(i)];
               end
           end
        end
    end
end

function delete_machine_data(machineData)
    if matlab.internal.feature('SFBatchDeleteImprovements')
        machineDataH = sf('IdToHandle', machineData);
        delete(machineDataH);
    else
        for i = length(machineData):-1:1
            sf('delete', machineData(i));
        end
    end
end

function machineData = names_to_ids(symbols, machineDataInModel)
    machineData = [];   
    for i = 1:1:length(symbols)
        if machineDataInModel.isKey(symbols(i).symbolName)
            machineData = [machineData machineDataInModel(symbols(i).symbolName)];
        end
    end
end
