function newcfg = upgradeCoderConfigObject(config, varargin)
%UPGRADECODERCONFIGOBJECT Convert a coder.config object to use the Toolchain approach.
%
% CODER.MAKE.UPGRADECODERCONFIGOBJECT(CFG) attempts to upgrade CFG to
% use the Toolchain approach by updating certain settings. 
%
% CFG can fall under any of the following cases:
%   -------------------  --------------------------------------------------
%   CFG Settings         Upgrade Action
%   -------------------  --------------------------------------------------
%   Toolchain-compliant  No CFG updates are necessary. The configuration
%                        is already using the toolchain approach.
%   -------------------  --------------------------------------------------
%   Upgradable           Automatic updates can be done. The configuration
%                        can be upgraded to use the toolchain approach.
%   -------------------  --------------------------------------------------
%   Non-upgradable       Automatic updates cannot be done. The configuration
%                        cannot be upgraded and will continue to use the
%                        template makefile approach. 
%   -------------------  --------------------------------------------------
%
% CODER.MAKE.UPGRADECODERCONFIGOBJECT(CFG, 'force', true) is the
% same as above except all cases are deemed upgradable. This should be done
% with caution as manual updates to the build process are required in order
% for the upgrade to be complete. 

% Copyright 2012-2018 The MathWorks, Inc.

narginchk(1,nargin);
if (~isa(config,'coder.CodeConfig'))
    error(message('Coder:common:NotAConfigObject'));
end

interactive = true;
force = false;
userResponse = -1;
if nargin>1
    for i=1:2:numel(varargin)
        if strcmpi(varargin{i}, 'interactive')
            interactive = varargin{i+1};
        elseif strcmpi(varargin{i}, 'force')
            force = varargin{i+1};
        elseif strcmpi(varargin{i}, 'userResponse')
            userResponse = varargin{i+1};
        end
    end
end

% -----------------------
% Existing values
% -----------------------
GenerateMakefile.origValue              = config.GenerateMakefile;
if GenerateMakefile.origValue
    GenerateMakefile.origStrValue       = 'true';
else
    GenerateMakefile.origStrValue       = 'false';
end
RTWCompilerOptimization.origValue       = config.CCompilerOptimization;
MakeCommand.origValue                   = strtrim(config.MakeCommand);
TemplateMakefile.origValue              = config.TemplateMakefile;
BuildConfiguration.origValue            = config.BuildConfiguration;

% -----------------------
% Default values
% -----------------------
GenerateMakefile.defaultValue           = true;
GenerateMakefile.defaultStrValue        = 'true';
RTWCompilerOptimization.defaultValue    = 'off';
MakeCommand.defaultValue                = 'make_rtw';
TemplateMakefile.defaultValue           = 'default_tmf';
BuildConfiguration.defaultValue         = 'Faster Builds';

% -----------------------
% Upgradable settings
% -----------------------
GenerateMakefile.upgradable             = false;
RTWCompilerOptimization.upgradable      = strcmpi(RTWCompilerOptimization.origValue, 'on');
MakeCommand.upgradable                  = false;
TemplateMakefile.upgradable             = false;

% ---------------------
% Make a copy
% ---------------------
% newcfg = copy(config); % this creates a true copy
newcfg = config;

% ----------------------------------------------
% Determine if already compliant
% ----------------------------------------------
GenerateMakefile.isUsingDefault         = (GenerateMakefile.origValue == GenerateMakefile.defaultValue);
RTWCompilerOptimization.isUsingDefault  = strcmpi(RTWCompilerOptimization.origValue, RTWCompilerOptimization.defaultValue);
MakeCommand.isUsingDefault              = strcmpi(MakeCommand.origValue, MakeCommand.defaultValue);
TemplateMakefile.isUsingDefault         = strcmpi(TemplateMakefile.origValue, TemplateMakefile.defaultValue);

compliant = ...
    GenerateMakefile.isUsingDefault && ...
    RTWCompilerOptimization.isUsingDefault && ...
    MakeCommand.isUsingDefault && ...
    TemplateMakefile.isUsingDefault;

% ----------------------------------------------
% Determine which settings are not-upgradable
% ----------------------------------------------
nonUpgradableList = {};
if ~GenerateMakefile.isUsingDefault && ~GenerateMakefile.upgradable
    nonUpgradableList{end+1} = 'GenerateMakefile';
end
if ~RTWCompilerOptimization.isUsingDefault && ~RTWCompilerOptimization.upgradable
    nonUpgradableList{end+1} = 'RTWCompilerOptimization';
end
if ~MakeCommand.isUsingDefault && ~MakeCommand.upgradable
    nonUpgradableList{end+1} = 'MakeCommand';
end
if ~TemplateMakefile.isUsingDefault && ~TemplateMakefile.upgradable
    nonUpgradableList{end+1} = 'TemplateMakefile';
end
nonUpgradable = ~isempty(nonUpgradableList);

% ----------------------------------------------
% If compliant, display compliance state
% ----------------------------------------------
nl = char(10);
displayStartBar(nl);
if compliant
    fprintf([ ...
        '---------------------------------------\n', ...
        'Configuration is Toolchain-Compliant\n', ...
        '---------------------------------------\n', ...
        'The configuration is already using the toolchain approach.\n', ...
        'All required configuration settings are already set to their default values.\n']);
end

% ----------------------------------------------
% 
% ----------------------------------------------
if force
    forceUpgradeConfig();
else
    safeUpgradeConfig();
end

    function safeUpgradeConfig
        
        % ----------------------------------------------
        % Determine if settings are upgradable
        % ----------------------------------------------
        upgradable = ~compliant && ...
            (GenerateMakefile.isUsingDefault        || GenerateMakefile.upgradable) && ...
            (RTWCompilerOptimization.isUsingDefault || RTWCompilerOptimization.upgradable) && ...
            (MakeCommand.isUsingDefault             || MakeCommand.upgradable) && ...
            (TemplateMakefile.isUsingDefault        || TemplateMakefile.upgradable);

        % ----------------------------------------------
        % Display info about upgrade state
        % ----------------------------------------------
        if nonUpgradable
            fprintf([ ...
                '-------------------------------------\n', ...
                'Configuration is Not Upgradable \n', ...
                '-------------------------------------\n', ...
                'One or more of the non-default configuration settings are not automatically upgradable.\n', ...
                'Therefore, the configuration cannot be upgraded to use the toolchain approach.\n' ...
                ]);
        elseif upgradable
            fprintf([ ...
                '--------------------------------\n', ...
                'Configuration is Upgradable \n', ...
                '--------------------------------\n', ...
                'One or more of the non-default configuration settings are automatically upgradable.\n', ...
                'Therefore, the configuration can be upgraded to use the toolchain approach.\n' ...
                ]);
        end
        
        % ----------------------------------------------
        % Display the existing settings as FYI to user
        % ----------------------------------------------
        displayConfigSettings();
        
        % --------------------------------------------------------
        % If not upgradable, tell them about the 'force' option
        % --------------------------------------------------------
        if nonUpgradable
            displayForceUpgradeRecommendation();
        end
        
        % ----------------------------------------------
        % If compliant, do another check on the TMF
        % ----------------------------------------------
        if compliant
            checkTemplateMakefile(TemplateMakefile);
        end
        
        % ----------------------------------------------
        % Return if there is nothing else to do
        % ----------------------------------------------
        if (compliant || nonUpgradable)
            displayEndBar(nl);
            return
        end
        
        % -------------------------------------------------------------
        % Everything below this fall under the "Upgradable" use case
        % -------------------------------------------------------------       
        
        % -------------------------------
        % Ask user if interactive==true
        % -------------------------------
        if interactive            
            proceed = [];
            while isempty(proceed)
                userQuestion = 'Proceed with upgrade? [y]/n : ';
                if ischar(userResponse)
                    userAnswer = userResponse;
                    fprintf([userQuestion '%s\n'], userAnswer);
                else
                    userAnswer = input(userQuestion, 's');
                end
                proceed = continueWithUpgrade(userAnswer);
            end
        else
            proceed = true;
        end
        if proceed
            disp('Upgrading configuration ...');
        else
            displayUpgradeTitle();
            fprintf('The configuration was not upgraded.\n')
            displayEndBar(nl);
            return
        end
        
        % -------------------------------
        % Perform the upgrade
        % -------------------------------

        % Verify that the compiler optimization level is the default value
        if RTWCompilerOptimization.upgradable && strcmpi(RTWCompilerOptimization.origValue, 'on')
            newcfg.CCompilerOptimization = RTWCompilerOptimization.defaultValue;
            newcfg.BuildConfiguration = 'Faster Runs';
            
            disp('Done.');

            displayUpgradeHeader();
            fprintf(parameterReport('CCompilerOptimization', RTWCompilerOptimization.origValue, newcfg.CCompilerOptimization));
            fprintf(parameterReport('BuildConfiguration', BuildConfiguration.origValue, newcfg.BuildConfiguration));
            displayEndBar(nl);
        end
        
        % ----------------------------------------------------------
        % Now that it is compliant, perform extra check on the TMF
        % ----------------------------------------------------------
        checkTemplateMakefile(TemplateMakefile);
        
    end % safeUpgradeConfig


    function forceUpgradeConfig()
                            
        if compliant
            % Display existing config settings
            displayConfigSettings();
            % Perform extra check on the TMF 
            checkTemplateMakefile(TemplateMakefile);
            return
        end        
        
        fprintf([ ...
            '-------------------------------------------\n', ...
            'Configuration is Not Toolchain-Compliant\n', ...
            '-------------------------------------------\n', ...
            'One or more of the configuration settings are set to non-default values.\n', ...
            'The configuration will be upgraded to use the toolchain approach.\n\n' ...
            ]);        
        displayConfigSettings();

        % -------------------------------
        % Perform the upgrade
        % -------------------------------

        disp('Upgrading configuration ...');
        
        % Keep track of parameters that have been modified
        modified = {};
        
        % Verify that generate-makefile checkbox is the default value
        if ~GenerateMakefile.isUsingDefault
            newcfg.GenerateMakefile = GenerateMakefile.defaultValue; % upgrade and move on
            modified{end+1} = 'GenerateMakefile';
        end
        
        % Verify that the compiler optimization level is the default value
        if strcmpi(RTWCompilerOptimization.origValue, 'on')
            newcfg.CCompilerOptimization = RTWCompilerOptimization.defaultValue;
            modified{end+1} = 'CCompilerOptimization';
            newcfg.BuildConfiguration = 'Faster Runs';
            modified{end+1} = 'BuildConfiguration';
        elseif strcmpi(RTWCompilerOptimization.origValue, 'custom')
            newcfg.CCompilerOptimization = RTWCompilerOptimization.defaultValue;
            modified{end+1} = 'CCompilerOptimization';
            newcfg.BuildConfiguration = 'Specify';
            modified{end+1} = 'BuildConfiguration';
        end
        
        % Verify that the make command is the default value
        if ~any(strcmp(MakeCommand.origValue, {MakeCommand.defaultValue, 'make_rtw_target'}))
            if ~isempty(strfind('make_rtw_target', MakeCommand.origValue))
                MakeCommand.defaultValue = 'make_rtw_target';
            else
                MakeCommand.defaultValue = 'make_rtw';
            end
            newcfg.MakeCommand = MakeCommand.defaultValue;
            modified{end+1} = 'MakeCommand';
        end
        
        % Verify that this matches the default TMF name
        if ~strcmp(TemplateMakefile.origValue, TemplateMakefile.defaultValue)
            newcfg.TemplateMakefile = TemplateMakefile.defaultValue;
            modified{end+1} = 'TemplateMakefile';
        end
               
        disp('Done.');
        
        % -------------------------------
        % Display upgrade report
        % -------------------------------

        displayUpgradeHeader();
        if isModified(modified, 'GenerateMakefile')
            fprintf(parameterReport('GenerateMakefile', GenerateMakefile.origStrValue, GenerateMakefile.defaultStrValue));
        end
        if isModified(modified, 'CCompilerOptimization')
            fprintf(parameterReport('CCompilerOptimization', RTWCompilerOptimization.origValue, newcfg.CCompilerOptimization));
        end
        if isModified(modified, 'BuildConfiguration')
            fprintf(parameterReport('BuildConfiguration', BuildConfiguration.origValue, newcfg.BuildConfiguration));
        end
        if isModified(modified, 'MakeCommand')
            fprintf(parameterReport('MakeCommand', MakeCommand.origValue, newcfg.MakeCommand));
        end
        if isModified(modified, 'TemplateMakefile')
            fprintf(parameterReport('TemplateMakefile', TemplateMakefile.origValue, newcfg.TemplateMakefile));
        end
        displayDivBar(nl);
        
        % ----------------------------------------------------------
        % Display info about non-upgradable settings that were updated
        % ----------------------------------------------------------
        
        displayForceUpgradeReport(nonUpgradable, nonUpgradableList);
        displayEndBar(nl);

        % ----------------------------------------------------------
        % Now that it is compliant, perform extra check on the TMF
        % ----------------------------------------------------------
        checkTemplateMakefile(TemplateMakefile);                        
        
    end % forceUpgradeConfig

    function displayUpgradeTitle
        fprintf([...
            '\n',...
            '--------------------\n', ...
            'Upgrade Report \n', ...
            '--------------------\n', ...
            ]);
    end

    function displayUpgradeHeader
        displayUpgradeTitle();
        fprintf([...
            'The configuration has been upgraded to become Toolchain-compliant.', nl, ...
            'The following modifications were made: ', nl ...
            ]);
    end

    function displayConfigSettings()
        fprintf(nl);
        fprintf(['---------------------  ----------------------------------------------------', nl]);
        fprintf(['Parameter               Value', nl]);
        fprintf(['---------------------  ----------------------------------------------------', nl]);
        fprintf(['GenerateMakefile        ' GenerateMakefile.origStrValue]);
        appendDefaultDesc(GenerateMakefile, numel(GenerateMakefile.origStrValue));
        fprintf(['CCompilerOptimization   ' RTWCompilerOptimization.origValue]);
        appendDefaultDesc(RTWCompilerOptimization);
        fprintf(['MakeCommand             ' MakeCommand.origValue]);
        appendDefaultDesc(MakeCommand);
        fprintf(['TemplateMakefile        ' TemplateMakefile.origValue]);
        appendDefaultDesc(TemplateMakefile);
        fprintf(['---------------------  ----------------------------------------------------', nl, nl]);
        
    end % displayConfigSettings

    function appendDefaultDesc(structData, bufferLength)
        maxLength = 20;
        if nargin==1
            bufferLength = numel(structData.origValue);
        end
        numSpaces = maxLength - bufferLength;
        if numSpaces<1
            numSpaces = 1;
        end
        spaces = repmat(' ', 1, numSpaces);
        if structData.isUsingDefault
            fprintf([spaces '(default) \n']);
        else
            fprintf([spaces '(non-default'])
            if ~force
                if structData.upgradable
                    fprintf(', upgradable');
                else
                    fprintf(', not upgradable');
                end
            end
            fprintf(') \n');
        end
    end % appendDefaultDesc
end

% -------------------------------------------------------------------------
% Check if the Template Makefile script points to a shipping template file.
% -------------------------------------------------------------------------
function customTMF = checkTemplateMakefile(TemplateMakefile)
customTMF = true;
tmfPath = which(TemplateMakefile.defaultValue,'-all');

if (isempty(tmfPath))
    return;
else
    tmfPath = tmfPath{1};
end

[mpath, ~, mext] = fileparts(tmfPath);
customTMF = (isempty(mext) || (~strcmpi(mext, '.m') && ~strcmpi(mext, '.p')) || ~contains(mpath, matlabroot));
if customTMF
    fprintf([ ...
        '!!! The configuration setting "TemplateMakefile" is set to its default value, "%s".\n', ...
        'However, this value point to a file (%s) \nthat is not shipped by MATLAB Coder ', ...
        'and is therefore, not the default template makefile.\n\n', ...
        ], TemplateMakefile.defaultValue, tmfPath);
end
end

function upgrade = continueWithUpgrade(userAnswer)
userAnswer = strtrim(userAnswer);
if isempty(userAnswer) || strcmpi(userAnswer, 'y')
    upgrade = true;
elseif strcmpi(userAnswer, 'n')
    upgrade = false;
else
    upgrade = [];
end
end

function str = parameterReport(varargin)
str = sprintf([...
    ' "%s" setting: \n', ...
    '\tOld value: %s \n', ...
    '\tNew value: %s \n', ...
    ], varargin{:});
end

function resp = isModified(modified, param)
resp = any(strcmpi(modified, param));
end

function displayForceUpgradeRecommendation
fprintf([ ...
    'If you want to still upgrade the configuration and force it to use the toolchain \n', ...
    'approach, invoke the following command: \n', ...
    '\tcoder.make.upgradeCoderConfigObject(cfg, ''force'', true) \n', ...
    'After you do this, you may need to make manual modifications to the build process \n', ...
    'in order for the upgrade to become complete. \n']);
end

function displayForceUpgradeReport(nonUpgradable, nonUpgradableList)
if nonUpgradable
    fprintf([ ...
        'The following settings are updated but these do not have direct \n', ...
        'equivalents in the toolchain approach: \n' , ...
        '\t%s\n', ...
        'You may need to make manual modifications to the build process in order \n', ...
        'for the upgrade to become complete. \n', ...
        ], coder.make.internal.cell2str(nonUpgradableList, ','))
end
end

function displayStartBar(nl)
fprintf([nl, ...
    '===========================================================================', nl]);
end
function displayEndBar(nl)
fprintf([...
    '===========================================================================', nl, nl]);
end
function displayDivBar(nl)
fprintf([...
    '---------------------------------------------------------------------------', nl]);
end


% function displayNoncomplianceReport
% fprintf([...
%     '============================================================================\n', ...
%     ' The configuration has not been upgraded to become Toolchain-compliant. \n', ...
%     '============================================================================\n' ...
%     ]);

% function oldCode
%
% % Keep track of parameters that have been modified
% modified = {};
%
% % Verify that generate-makefile checkbox is the default value
% if ~GenerateMakefile.isUsingDefault
%     recommendation = sprintf([ ...
%         '!!! You need to make manual adjustments after.\n' ...
%         ]);
%     proceed = askUser('GenerateMakefile', 'false', 'true', recommendation);
%     if proceed
%         newcfg.GenerateMakefile = GenerateMakefile.defaultValue; % upgrade and move on
%         modified{end+1} = 'GenerateMakefile';
%     else
%         displayNoncomplianceReport();
%         return; % exit
%     end
% end
%
% % Verify that the compiler optimization level is the default value
% if strcmpi(RTWCompilerOptimization.origValue, 'on')
%     recommendation = sprintf([ ...
%         'Parameter "BuildConfiguration" is set to "Faster Runs".\n', ...
%         ]);
%     proceed = askUser('CCompilerOptimization', RTWCompilerOptimization.origValue, 'off', recommendation);
%     if proceed
%         newcfg.CCompilerOptimization = RTWCompilerOptimization.defaultValue;
%         modified{end+1} = 'CCompilerOptimization';
%         newcfg.BuildConfiguration = 'Faster Runs';
%         modified{end+1} = 'BuildConfiguration';
%     else
%         displayNoncomplianceReport();
%         % Make sure we return a copy of the original config
%         newcfg = copy(config);
%         % Terminate the upgrade
%         return;
%     end
% elseif strcmpi(RTWCompilerOptimization.origValue, 'custom')
%     recommendation = sprintf([ ...
%         'Parameter "BuildConfiguration" is set to "Specify".\n', ...
%         '!!! You need to make manual adjustments after.\n' ...
%         ]);
%     proceed = askUser('CCompilerOptimization', RTWCompilerOptimization.origValue, 'off', recommendation);
%     if proceed
%         newcfg.CCompilerOptimization = RTWCompilerOptimization.defaultValue;
%         modified{end+1} = 'CCompilerOptimization';
%         newcfg.BuildConfiguration = 'Specify';
%         modified{end+1} = 'BuildConfiguration';
%     else
%         displayNoncomplianceReport();
%         % Make sure we return a copy of the original config
%         newcfg = copy(config);
%         % Terminate the upgrade
%         return;
%     end
% end
%
% % Verify that the make command is the default value
% if ~any(strcmp(strtrim(MakeCommand.origValue), {MakeCommand.defaultValue, 'make_rtw_target'}))
%     if ~isempty(strfind('make_rtw_target', MakeCommand.origValue))
%         MakeCommand.defaultValue = 'make_rtw_target';
%     else
%         MakeCommand.defaultValue = 'make_rtw';
%     end
%     recommendation = sprintf([ ...
%         '!!! You need to make manual adjustments after.\n' ...
%         ]);
%     proceed = askUser('MakeCommand', MakeCommand.origValue, MakeCommand.defaultValue, recommendation);
%     if proceed
%         newcfg.MakeCommand = MakeCommand.defaultValue;
%         modified{end+1} = 'MakeCommand';
%     else
%         displayNoncomplianceReport();
%         % Make sure we return a copy of the original config
%         newcfg = copy(config);
%         % Terminate the upgrade
%         return;
%     end
% end
%
% % Verify that this matches the default TMF name
% if ~strcmp(TemplateMakefile.origValue, TemplateMakefile.defaultValue)
%     recommendation = sprintf([ ...
%         '!!! You need to make manual adjustments after.\n' ...
%         ]);
%     proceed = askUser('TemplateMakefile', TemplateMakefile.origValue, TemplateMakefile.defaultValue, recommendation);
%     if proceed
%         newcfg.TemplateMakefile = TemplateMakefile.defaultValue;
%         modified{end+1} = 'TemplateMakefile';
%     else
%         displayNoncomplianceReport();
%         % Make sure we return a copy of the original config
%         newcfg = copy(config);
%         % Terminate the upgrade
%         return;
%     end
% end
%
% % Verify that this is indeed a .m file
% if (isempty(tmfPath))
%     tmfPath = which(fname);
% end
% if iscellstr(tmfPath) % get the first match if multiple values are returned
%     tmfPath = tmfPath{1};
% end
%
% [mpath, ~, mext] = fileparts(tmfPath);
% idx = strfind(mpath, matlabroot);
% if isempty(mext) || (~strcmpi(mext, '.m') && ~strcmpi(mext, '.p')) || isempty(idx)
%     tmp = coder.make.util.File(tmfPath);
%     msg = message('Coder:common:NotUsingDefaultTMF', tmp.EscapedFullPathName);
%     warning(msg);
% end
%
% compliant = true;
% if compliant
%     if isempty(modified)
%         fprintf([...
%             '==================================================================\n', ...
%             ' The configuration is already Toolchain-compliant. \n', ...
%             ' No changes are necessary. \n', ...
%             '==================================================================\n' ...
%             ]);
%     else
%         fprintf([...
%             '==================================================================\n', ...
%             ' A copy of the configuration has been upgraded to become \n', ...
%             ' Toolchain-compliant. \n', ...
%             ' The following show the modifications made: \n', ....
%             '==================================================================\n' ...
%             ]);
%         if isModified(modified, 'GenerateMakefile')
%             fprintf(parameterReport('GenerateMakefile', 'false', 'true'));
%         end
%         if isModified(modified, 'CCompilerOptimization')
%             fprintf(parameterReport('CCompilerOptimization', RTWCompilerOptimization.origValue, newcfg.CCompilerOptimization));
%         end
%         if isModified(modified, 'BuildConfiguration')
%             fprintf(parameterReport('BuildConfiguration', BuildConfiguration.origValue, newcfg.BuildConfiguration));
%         end
%         if isModified(modified, 'MakeCommand')
%             fprintf(parameterReport('MakeCommand', MakeCommand.origValue, newcfg.MakeCommand));
%         end
%         if isModified(modified, 'TemplateMakefile')
%             fprintf(parameterReport('TemplateMakefile', TemplateMakefile.origValue, newcfg.TemplateMakefile));
%         end
%     end
% end
%

% function str = nondefaultReport(varargin)
% str = sprintf([ ...
%     '\nParameter "%s" is set to "%s" - a non-default value. \n', ...
%     'In order to upgrade the configuration to use Toolchain, ', ...
%     'this parameter must be reset to its default value of "%s". \n', ...
%     ], varargin{:});

% function str = continueQuestion
% str = sprintf('Continue with reset? [Y]/N: ');
%
% function proceed = askUser(varargin)
% if 0
%     fprintf([ ...
%         nondefaultReport(varargin{1:end-1}), ...
%         varargin{end}, '\n']);
% else
%     fprintf(nondefaultReport(varargin{1:end-1}));
%     if ~isempty(varargin{end})
%         fprintf('!!! Cannot automatically upgrade this parameter.\n');
%         proceed = false;
%         return
%     end
% end
% proceed = [];
% while isempty(proceed)
%     userAnswer = input(continueQuestion, 's');
%     proceed = continueWithUpgrade(userAnswer);
% end

% LocalWords:  Toolchain toolchain tmf newcfg CCompiler fname tmp
