classdef Dispatcher
    % DISPATCHER sends Pipeline updates through MessageQueue for the
    % MessageBroker
    properties (Access=private)
        MessageQueue(1,1) parallel.pool.DataQueue 
    end

    methods
        function obj = Dispatcher(msgQueue)
            arguments
                msgQueue(1,1) parallel.pool.DataQueue
            end

            obj.MessageQueue = msgQueue;
        end

        function dispatch(obj, level, msgString, type, uuid, mException)
            arguments
                obj(1,1)        bioinfo.pipeline.internal.Dispatcher
                level(1,1)      bioinfo.pipeline.LogLevel
                msgString(1,1)  string
                type(1,1)       bioinfo.pipeline.internal.DispatchType
                uuid(:,1)       string
                mException      MException {mustBeScalarOrEmpty} = MException.empty
            end

            % Format data into struct and send to MessageBroker thru MessageQueue
            dataStruct              = struct();
            dataStruct.Level        = level;
            dataStruct.Message      = msgString;
            dataStruct.TimeStamp    = datetime('now', 'Format','yyyy-MM-dd HH:mm:ss.SSSSSS');
            dataStruct.Type         = type;
            dataStruct.UUID         = uuid;
            dataStruct.MException   = mException;

            send(obj.MessageQueue, dataStruct);
        end

        function debug(obj, msg, type, uuid)
            arguments
                obj
                msg(1,1) message
                type(1,1) bioinfo.pipeline.internal.DispatchType
                uuid(:,1) string {mustBeNonempty}
            end
            level = bioinfo.pipeline.LogLevel.Debug;

            obj.dispatch(level, getString(msg), type, uuid);
        end

        function info(obj, msg, type, uuid)
            arguments
                obj
                msg(1,1) message
                type(1,1) bioinfo.pipeline.internal.DispatchType
                uuid(:,1) string {mustBeNonempty}
            end
            level = bioinfo.pipeline.LogLevel.Info;

            obj.dispatch(level, getString(msg), type, uuid);
        end

        function warn(obj, msg, type, uuid)
            arguments
                obj
                msg(1,1) message
                type(1,1) bioinfo.pipeline.internal.DispatchType
                uuid(:,1) string {mustBeNonempty}
            end
            level = bioinfo.pipeline.LogLevel.Warn;

            obj.dispatch(level, getString(msg), type, uuid);
        end

        function mException = error(obj, msg, type, uuid, NVPArgs)
            arguments
                obj
                msg(1,1)
                type(1,1) bioinfo.pipeline.internal.DispatchType
                uuid(:,1) string {mustBeNonempty}
                NVPArgs.Throw(1,1) logical  = true
            end
            level = bioinfo.pipeline.LogLevel.Error;

            % msg can be an MException from a try-catch block
            if isa(msg, "MException")
                msgString   = msg.message;
                mException  = msg;
            else % message
                msgString   = getString(msg);
                mException  = MException(msg);
            end

            obj.dispatch(level, msgString, type, uuid, mException);
            
            if NVPArgs.Throw
                throwAsCaller(mException);
            end
        end
    end
end