% ValidationEventData - Event data for Validation updates

% Copyright 2011-2020 The MathWorks, Inc.

classdef (Hidden, Sealed) ValidationEventData < event.EventData
    properties (SetAccess = immutable)
        % The name of the profile
        Profile = ''
        % The validation suite
        Suite = parallel.internal.validator.ValidationSuite.empty();
        % A ValidationStages enum representing the stage
        Stage = parallel.internal.types.ValidationStages.empty();
        % A ValidationStatus enum representing the status
        StatusEnum = parallel.internal.types.ValidationStatus.NotRun
    end
    
    properties
        % The description of the status
        Description = ''
        % The command window output from the validation
        CommandWindowOutput = ''
        % The debugLog(s) for a job - this is always a cell array
        DebugLogCellstr = {}
        % The debug log(s) for the client
        ClientLog = ''
        % Errors that occurred
        Errors = {}
        % Timestamp of this event
        Timestamp = datetime.empty();
    end
    
    properties (Dependent)
        % Validation Status string
        Status
        % The error report without any hyperlinks
        ErrorReportNoLinks
        % The error report with or without hyperlinks as appropriate
        % for the current display options
        ErrorReport
        % The debug log as a string
        DebugLog
    end
    
    methods
        function status = get.Status(obj)
            status = obj.StatusEnum.Name;
        end
        
        function report = get.ErrorReportNoLinks(obj)
            getReportFcn = @(err) err.getReport('extended', 'hyperlinks', 'off');
            report = obj.getErrorReport(getReportFcn);
        end
        
        function report = get.ErrorReport(obj)
            getReportFcn = @(err) err.getReport('extended', 'hyperlinks', 'on');
            report = obj.getErrorReport(getReportFcn);
        end
        
        function log = get.DebugLog(obj)
            if isempty(obj.DebugLogCellstr)
                log = '';
            else
                log = strtrim(sprintf('%s\n', obj.DebugLogCellstr{:}));
            end
            
            % Append the client logs, if necessary
            if ~isempty(obj.ClientLog)
                log = strtrim(sprintf('%s\n\nCLIENT LOG OUTPUT\n%s', log, obj.ClientLog));
            end
        end
    end
    
    methods (Static)
        function obj = createValidationStarted(profileName, suite, statusEnum)
        % createValidationStarted(profileName, suite, statusEnum) - create event 
        % data for a validation started event
            import parallel.internal.settings.ValidationEventData

            obj = ValidationEventData(profileName, suite, parallel.internal.types.ValidationStages.empty(), statusEnum);
        end
        
        function obj = createOverallValidationStatus(profileName, statusEnum)
        % createOverallValidationStatus(profileName, statusEnum) - create event 
        % data for the overall validation status
            import parallel.internal.settings.ValidationEventData

            obj = ValidationEventData(profileName, parallel.internal.validator.ValidationSuite.empty(), ...
                parallel.internal.types.ValidationStages.empty(), statusEnum);
        end
        
        function obj = createStageRunning(profileName, stage)
        % createRunning(profileName, stage) - create event data for stage running
            import parallel.internal.settings.ValidationEventData
            import parallel.internal.types.ValidationStatus

            obj = ValidationEventData(profileName, parallel.internal.validator.ValidationSuite.empty(), ...
                stage, ValidationStatus.Running);
        end
        
        function obj = createStageStopped(profileName, stage)
        % createStageStopped(profileName, stage) - create event data for stage stopped
            import parallel.internal.settings.ValidationEventData
            import parallel.internal.types.ValidationStatus

            obj = ValidationEventData(profileName, parallel.internal.validator.ValidationSuite.empty(), ...
                stage, ValidationStatus.Stopped);
            obj.Description = getString(message('parallel:validation:StageStoppedUserCancelled'));
        end
        
        function obj = createStageFailed(profileName, stage, errors, description, commandWindowOutput, debugLog, clientLog)
        % createStageFailed(profileName, stage, errors, description,
        % commandWindowOutput, debugLog, clientLog) - create event data for stage
        % failed because an error (or errors) were thrown
            import parallel.internal.settings.ValidationEventData
            import parallel.internal.types.ValidationStatus
            
             % Convert errors to cell array, if necessary
            if ~iscell(errors)
                errors = errors(:);
                errors = mat2cell(errors, ones(size(errors)));
            end
            cellfun(@(err) validateattributes(err, {'MException'}, {'scalar'}), errors);
            
            validateattributes(description, {'char'}, {});

            if nargin < 5
                commandWindowOutput = '';
            else
                validateattributes(commandWindowOutput, {'char'}, {});
            end
            if nargin < 6
                debugLog = '';
            end
            if nargin < 7
                clientLog = '';
            end
            
            if ~iscell(debugLog)
                debugLog = {debugLog};
            end
            if ~iscellstr(debugLog) %#ok<ISCLSTR> debugLog must have been a char vector
                error(message('parallel:settings:ValidationEventDataDebugLogNotString'));
            end
            
            obj = ValidationEventData(profileName, parallel.internal.validator.ValidationSuite.empty(), ...
                stage, ValidationStatus.Failed);
            
            obj.Description = description;
            obj.CommandWindowOutput = commandWindowOutput;
            obj.Errors = errors;
            obj.DebugLogCellstr = debugLog;
            obj.ClientLog = strtrim(clientLog);
        end
        
        function obj = createStagePassed(profileName, stage, commandWindowOutput, description)
        % createPassed(profileName, stage, commandWindowOutput) - create event data 
        % for stage passed.
            import parallel.internal.settings.ValidationEventData
            import parallel.internal.types.ValidationStatus
            validateattributes(commandWindowOutput, {'char'}, {});
            if nargin < 4
                description = ''; % By default, don't show a description for stage passed
            else
                validateattributes(description, {'char'}, {});
            end
            
            obj = ValidationEventData(profileName, parallel.internal.validator.ValidationSuite.empty(), ...
                stage, ValidationStatus.Passed);
            
            obj.Description = description;
            obj.CommandWindowOutput = commandWindowOutput;
        end
        
        function obj = createStageSkipped(profileName, stage)
        % createSkipped(profileName, stage) - create event 
        % data for stage skipped because a previous stage errored.
            import parallel.internal.settings.ValidationEventData
            import parallel.internal.types.ValidationStatus
            obj = ValidationEventData(profileName, parallel.internal.validator.ValidationSuite.empty(), ...
                stage, ValidationStatus.Skipped);
            obj.Description = getString(message('parallel:validation:StageSkippedPreviousFailure'));
        end

        function obj = createStageSkippedUnsupported(profileName, stage)
        % createSkipped(profileName, stage) - create event 
        % data for stage skipped because it isn't supported.
            import parallel.internal.settings.ValidationEventData
            import parallel.internal.types.ValidationStatus
            obj = ValidationEventData(profileName, parallel.internal.validator.ValidationSuite.empty(), ...
                stage, ValidationStatus.Skipped);
            obj.Description = getString(message('parallel:validation:StageSkippedNotSupported'));
        end
        
        function obj = createStageNotRunUserCancelled(profileName, stage)
        % createStageNotRunUserCancelled(profileName, stage) - create event 
        % data for stage not run because of an earlier user cancellation
            import parallel.internal.settings.ValidationEventData
            import parallel.internal.types.ValidationStatus
            obj = ValidationEventData(profileName, parallel.internal.validator.ValidationSuite.empty(), ...
                stage, ValidationStatus.NotRun);
        end
    end
    
    methods (Access = private)
        function obj = ValidationEventData(profileName, suite, stage, statusEnum)
            validateattributes(profileName, {'char'}, {'row'});
            validateattributes(suite,       {'parallel.internal.validator.ValidationSuite'}, {});
            validateattributes(stage,       {'parallel.internal.types.ValidationStages'}, {});
            validateattributes(statusEnum,  {'parallel.internal.types.ValidationStatus'}, {'scalar'});

            obj.Profile     = profileName;
            obj.Suite       = suite;
            obj.Stage       = stage;
            obj.StatusEnum  = statusEnum;
            obj.Timestamp   = datetime('now', 'TimeZone', 'local');
        end
        
        function report = getErrorReport(obj, getReportFcn)
        % Returns an error report than can be easily serialized to
        % Java/JavaScript (i.e. a string). The error report is a stackless
        % summary of the MExceptions stored in obj.Errors, or '' if there
        % are no errors
            if isempty(obj.Errors)
                report = '';
            else
                stacklessErrors = cellfun(@iBuildStacklessException, obj.Errors, 'UniformOutput', false);
                errReports = cellfun(@(err) strtrim(getReportFcn(err)), stacklessErrors, 'UniformOutput', false);
                report = strtrim(sprintf('%s\n', errReports{:}));
            end
        end
    end
end

%---------------------------------------
function newEx = iBuildStacklessException(originalErr)
% Build an MException like the original, but that has no stack
newEx = MException(originalErr.identifier, '%s', originalErr.message);
for ii = 1:numel(originalErr.cause)
    newEx = newEx.addCause(originalErr.cause{ii});
end
end
