function enableClientLogging(logdir, loglevel, logfilePrefix)
% parallel.internal.logging.enableClientLogging(LOGDIR) 
% begins logging to a logfile at log level 4 named client.log.0 in the 
% directory LOGDIR. If the file client.log.0 already exists in this 
% location it will be appended to.
%
% parallel.internal.logging.enableClientLogging(LOGDIR,LOGLEVEL) 
% begins logging to a logfile at log level LOGLEVEL named client.log.0 
% in the directory LOGDIR. If the file client.log.0 already exists in 
% this location it will be appended to.
%
% parallel.internal.logging.enableClientLogging(LOGDIR,LOGLEVEL,LOGFILEPREFIX) 
% begins logging to a logfile at log level LOGLEVEL named LOGFILEPREFIX.log.0 
% in the directory LOGDIR. If the file LOGFILEPREFIX.log.0 already exists in 
% this location it will be appended to.
%
% parallel.internal.logging.enableClientLogging('') turns off logging.
%
% LOGDIR can be the path to any valid directory that 
% can be written to on the client machine.
%
% LOGLEVEL can be any integer from 0-6.
% 
%Examples:
% 
% %To start client logging in directory /home/matlabuser/
% parallel.internal.logging.enableClientLogging('/home/matlabuser/')
%
% %To start client logging at log level 2 in directory /home/matlabuser/
% parallel.internal.logging.enableClientLogging('/home/matlabuser/',2)
%
% %To start client logging at log level 2 in directory /home/matlabuser/ to
% a log file named 'myClient.log.0'
% parallel.internal.logging.enableClientLogging('/home/matlabuser/',2,'myClient')
%
% %To stop client logging
% parallel.internal.logging.enableClientLogging('')

% Copyright 2018-2020 The MathWorks, Inc.

if nargin < 2
    % Default logging level
    loglevel = 4;
else
    %validate log level is numberic and in range 0 to 6
    validateattributes( loglevel, {'numeric'}, {'scalar', '>=', 0, '<=', 6}, '','LOGLEVEL',2 );
end

if nargin < 3
    % Default log file name
    logfilePrefix = 'client';
end

% Enable java logging. dctSchedulerMessages are also directed to the java
% log files.
if usejava('jvm')
    iEnableJavaLogging(logdir, loglevel, logfilePrefix);
end

% If any experimental infrastructure is switched on, add C++ logging
% PCT_Java_Transition_TODO g2364333 - Remove
% ExperimentalVersion switching code and java paths
if parallel.internal.apishared.ExperimentalVersions.get().requireCppLogging()
    iEnableCppLogging(logdir, loglevel, logfilePrefix);
end
end

function iEnableCppLogging(logdir, loglevel, logfilePrefix)
    logfile = [logfilePrefix '.cpp.log'];
    % New file we are going to start logging to
    logFilePattern = fullfile(logdir, logfile);
    % Size and count for log rolling
    logFileMaxSize = 10*1e6; % 10MB
    logFileMaxNumber = 32;
    
    if isempty(logdir)
        parallel.internal.logging.log(4, 'Log handler stopping');
        parallel.internal.logging.setLogSpec('');
    else 
        % Lets write where we are going next so someone can follow the logs
        parallel.internal.logging.log(4, sprintf('Log handler moving to file %s', logFilePattern));
        parallel.internal.logging.initLogging(logFilePattern, logFileMaxSize, logFileMaxNumber);
        parallel.internal.logging.setLogLevel(loglevel);
        
        % Client dctSchedulerMessages are also useful to log inline with
        % the C++ messages too.
        cppHandler = @(message, level) parallel.internal.logging.log(level, message);
        javaHandler = setSchedulerMessageHandler(cppHandler);
        if ~isempty(javaHandler)
            teedHandler = @(message, level) iTeedHandler(javaHandler, cppHandler, message, level);
            setSchedulerMessageHandler(teedHandler);
        end
    end
end

function iEnableJavaLogging(logdir, loglevel, logfilePrefix)

    persistent aHandler
    persistent aLogFilename

    logfile = [logfilePrefix '.log'];
    % New file we are going to start logging to
    logFilePattern = fullfile(logdir, logfile);
    % Size and count for log rolling
    logFileMaxSize = 10*1e6; % 10MB
    logFileMaxNumber = 32;

    % mlock to hold the handler even if someone clears all MATLAB variables
    mlock;

    % The java logger we are going to use
    logger = com.mathworks.toolbox.parallel.pctutil.logging.RootLog.LOG;
    % Someone somewhere is resetting the java global LogManager, so reset
    % the logger's level to ALL here.
    logger.setLevel(com.mathworks.toolbox.parallel.pctutil.logging.DistcompLevel.getLevelFromValue(loglevel));
    if isempty(logdir)
        logger.log(java.util.logging.Level.INFO, 'Log handler stopping');
    else 
        % Lets write where we are going next so someone can follow the logs
        logger.log(java.util.logging.Level.INFO, sprintf('Log handler moving to file %s', logFilePattern));
    end
    % If there is already a handler then we need to remove it
    if ~isempty(aHandler)
        % Remove and close
        logger.removeHandler(aHandler);
        aHandler.close();
    end
    if isempty(logdir)
        % Turn off logging
        aHandler = [];
        aLogFilename = [];
    else
        % Just in case the file already exists we will append to it
        append = true;
        aHandler = com.mathworks.toolbox.parallel.pctutil.logging.RotatingFileHandler(logFilePattern, logFileMaxSize, logFileMaxNumber, append); 
        aHandler.setFormatter(com.mathworks.toolbox.parallel.pctutil.logging.DistcompSimpleFormatter);
        % Set the logging level and add the handler
        aHandler.setLevel(com.mathworks.toolbox.parallel.pctutil.logging.DistcompLevel.getLevelFromValue(loglevel));
        logger.addHandler(aHandler);
        % If we are following on from a previous log then indicate where we came
        % from just in case it is useful
        if ~isempty(aLogFilename)
            logger.log(java.util.logging.Level.INFO, sprintf('Continuing log from file %s', aLogFilename));
         end
        % Write a log message indicating that it has all been done
        logger.log(java.util.logging.Level.INFO, sprintf('Logging at level %d', loglevel));

        % Client dctSchedulerMessages are also useful to log inline with the java
        % messages too.
        setSchedulerMessageHandler(@(m,l)iLogJavaMessage(logger, m, l));

        % Persistently hold the name of this log file for when it is changed.
        aLogFilename = logFilePattern;
    end
end


function iLogJavaMessage(logger, message, level)
    logger.log(com.mathworks.toolbox.parallel.pctutil.logging.DistcompLevel.getLevelFromValue(level), message);
end

function iTeedHandler(handler1, handler2, message, level)
    % Send message to both handlers
    handler1(message, level);
    handler2(message, level);
end
