% handleSPFException - helper method to try and throw the best localised
% error message given the remote information collected or missing

% Copyright 2020 The MathWorks, Inc.

function err = handleSPFException(messageID, messageString, localizedMessage, exceptionClass, messageArguments, stackTrace)

% Check if remote exception class has known mapping
err = iMapKnownException(exceptionClass);
if ~isempty(err)
    iLogStackTrace(messageString, stackTrace);
    return
end

% Attempt to recreate remote error message in client locale
err = iRecreateRemoteError(messageID, messageArguments);
if ~isempty(err)
    iLogStackTrace(messageString, stackTrace);
    return
end

% Fall back to using remote message
if ~isempty(localizedMessage)
    % Use message localised to remote end
    errorMessage = localizedMessage;
else
    % Use non-localised message which may be empty
    errorMessage = messageString;
end

if ~isempty(exceptionClass)
    if isempty(errorMessage)
        % Stub message from class
        errorMessage = exceptionClass;
    else
        % Prepend exception class when the wrapped exception is not a
        % MatlabIdentified one. Useful for some exceptions like
        % IndexOutOfBoundsException when the message otherwise is just the
        % index.
        if strcmp(messageID, 'parallel:cluster:UnidentifiedJavaException')
            errorMessage = sprintf('%s : %s', exceptionClass, errorMessage);
        end
    end
end

if isempty(errorMessage)
    % We know nothing somehow!
    errorMessage = message('parallel:cluster:mjs:Unknown').getString();    
end
        
if ~isempty(messageID)
    err = MException(messageID, errorMessage);
else
    errorIdentifier = 'parallel:cluster:mjs:TransportUnidentifiedJavaException';
    errorMessage = message(errorIdentifier, exceptionClass, errorMessage);
    err = MException(errorIdentifier, errorMessage);
end
iLogStackTrace(messageString, stackTrace);

end

function iLogStackTrace(loggingMessage, stackTrace)
    % Log the stack trace
    dctSchedulerMessage(0, 'Caught a remote exception. Original exception message: %s\n%s', loggingMessage, stackTrace);
end

function err = iRecreateRemoteError(messageID, messageArguments)
err = [];
if ~isempty(messageID)
    % Try to recreate message in client locale
    if ~isempty(messageArguments)
        errorMessage = message(messageID, messageArguments{:});
    else
        % Message either has no arguments or they are not set
        errorMessage = message(messageID);
    end
    
    % Verify message can be recreated, otherwise fall back to remote values
    try
        errorMessage.getString();
        err = MException(errorMessage);
    catch
        % Failed to recreate message. Message arguments may be invalid or
        % message may not exist in local catalog.
        err = [];
        dctSchedulerMessage(6, 'Failed to recreate remote message with ID: %s\n', messageID);
    end
end
end

% Subset of the handled java exceptions in distcomp.handleJavaException.
% This includes java messages that do not correctly use the message catalog
% and handled runtime exceptions
function err = iMapKnownException(exceptionClass)
    msg = [];
    jvmType = 'worker';

    switch exceptionClass
        case 'com.mathworks.toolbox.distcomp.mjs.storage.CredentialStorageException'
            msg = message('parallel:cluster:MJSCredentialStorageFailed');
        case 'com.mathworks.toolbox.distcomp.mjs.storage.TaskNotFoundException'
            msg = message('parallel:task:MJSTaskNotFound');
        case 'com.mathworks.toolbox.distcomp.mjs.storage.JobNotFoundException'
            msg = message('parallel:job:MJSJobNotFound');
        case 'java.lang.OutOfMemoryError'
            msg = message('parallel:cluster:MJSNotEnoughJavaMemoryToAllocate', jvmType);
        case 'com.mathworks.toolbox.distcomp.mjs.storage.DataStorageException'
            msg = message('parallel:job:MJSDataStorageFailed');
    end
    
    if ~isempty(msg)
        err = MException(msg);
    else
        err = [];
    end
end
