% ConnMgrBuilder - builds instances of
% com.mathworks.toolbox.distcomp.pmode.io.ConnectionManager

% Copyright 2011-2017 The MathWorks, Inc.

classdef ( Hidden, Sealed ) ConnMgrBuilder

    methods ( Static, Access = private )
        function [minport, maxport, constants, hostname] = commonSetup()
        % commonSetup() - calculate common properties needed by both methods

            cfg          = pctconfig();
            portrange    = cfg.portrange;
            if isscalar(portrange)
                % Using ephemeral ports
                minport = 0;
                maxport = 0;
            else
                minport = portrange(1);
                maxport = portrange(2);
            end
            constants    = parallel.internal.pool.Constants();
            hostname     = cfg.hostname;
        end
        function commGroup = chooseCommGroup(strategy)
            import com.mathworks.toolbox.distcomp.pmode.matlabpool.sessions.ConnectionTopology;
            if strategy == ConnectionTopology.ALL_WORKERS_CONNECT_TO_CLIENT
                commGroup = 'com.mathworks.toolbox.distcomp.pmode.io.DirectCommunicationGroup';
            else
                commGroup = 'com.mathworks.toolbox.distcomp.pmode.io.IndirectCommunicationGroup';
            end
        end
    end
    methods ( Static )

        function [connMgr, strategy] = buildForMJS( brokerConnectInfo, ...
                                        workerAcceptInfoTemplate, ...
                                        useSecureMatlabPool, ...
                                        useOptimizedBroadcast )
        % buildForMJS() - build a ConnectionManager to be used by jobmanager/MJS.
        % Input arguments must be extracted from the jobmanager/MJS object
        % prior to invoking this method.
            import com.mathworks.toolbox.distcomp.pmode.poolmessaging.ConnectionManager;
            import parallel.internal.apishared.ConnMgrBuilder;

            [minport, maxport, constants, hostname] = ConnMgrBuilder.commonSetup();

            clientPeerInstance = ConnectionManager.createClientPeerInstance();

            strategy = iResolveConnectionTopology( useOptimizedBroadcast );
            [~, undoc] = pctconfig();
            
            acceptInfo = ConnectionManager.createAcceptInfo( ...
                                                clientPeerInstance, ...
                                                ConnMgrBuilder.chooseCommGroup(strategy), ...
                                                minport, ...
                                                maxport, ....
                                                constants.connectionBacklog, ...
                                                brokerConnectInfo, ...
                                                workerAcceptInfoTemplate, ...
                                                useSecureMatlabPool, ...
                                                constants.matlabpoolPollInterval, ...
                                                constants.matlabpoolTimeout, ...
                                                constants.handshakeConnectAttempts, ...
                                                undoc.reliableconnections);

            connMgr = ConnectionManager.buildClientConnManager( ...
                    hostname, clientPeerInstance, acceptInfo);
        end

        function [connMgr, strategy] = buildForCJS( useOptimizedBroadcast, usereliableconnections )
        % buildForCJS() - build a ConnectionManager for CJS-type clusters/schedulers.
            import com.mathworks.toolbox.distcomp.pmode.poolmessaging.ConnectionManager;
            import parallel.internal.apishared.ConnMgrBuilder;

            [minport, maxport, constants, hostname] = ConnMgrBuilder.commonSetup();

            strategy = iResolveConnectionTopology( useOptimizedBroadcast );

            clientPeerInstance = ConnectionManager.createClientPeerInstance();
            acceptInfo = ConnectionManager.createAcceptInfo( ...
                    clientPeerInstance, ...
                    ConnMgrBuilder.chooseCommGroup(strategy), ...
                    minport, maxport, constants.connectionBacklog, ...
                    constants.matlabpoolPollInterval, ...
                    constants.matlabpoolTimeout, ...
                    constants.handshakeConnectAttempts, ...
                    usereliableconnections);
            connMgr = ConnectionManager.buildClientConnManager( ...
                    hostname, clientPeerInstance, acceptInfo);
        end

        function acceptor = buildLabOneAcceptor(labOneInstance)
            import com.mathworks.toolbox.distcomp.pmode.poolmessaging.ConnectionManager;
            import com.mathworks.toolbox.distcomp.pmode.matlabpool.sessions.ConnectionTopology;
            import parallel.internal.apishared.ConnMgrBuilder;

            [minport, maxport, constants, hostname] = ConnMgrBuilder.commonSetup();

            % Connections between 'regular' workers and the proxying worker
            % should not be reliable.
            useReliableConnections = false;
            
            acceptInfo = ConnectionManager.createAcceptInfo( ...
                labOneInstance, ...
                ConnMgrBuilder.chooseCommGroup(ConnectionTopology.WORKERS_PROXIED_BY_LAB_ONE), ...
                minport, maxport, constants.connectionBacklog, ...
                constants.matlabpoolPollInterval, ...
                constants.matlabpoolTimeout, ...
                constants.handshakeConnectAttempts, ...
                useReliableConnections);
            acceptor = ConnectionManager.buildLabOneAcceptor( ...
                hostname, labOneInstance, acceptInfo);
        end
        
        function connMgr = buildPoolClientConnMgr()
        % The connection manager used for a batch parallel pool.
            import com.mathworks.toolbox.distcomp.pmode.poolmessaging.ConnectionManager;
            import parallel.internal.apishared.ConnMgrBuilder;
            
            % For batch parallel pools, reliable connections are turned off by default.
            useReliableConnections = false;

            [minport, maxport, constants, hostname] = ConnMgrBuilder.commonSetup();

            % Override 'hostname' with 'internalhostname' if set - this is to allow batch-pool workers to
            % connect in cases where they cannot use the external 'hostname' property.
            [~, undoc] = pctconfig();
            if ~isempty(undoc.internalhostname)
                dctSchedulerMessage(2, ['ConnMgrBuilder.buildPoolClientConnMgr: using internalhostname ', ...
                                    '"%s" to override "%s" for pool client.'], ...
                                    undoc.internalhostname, hostname);
                hostname = undoc.internalhostname;
            end
            
            clientPeerInstance = ConnectionManager.createClientPeerInstance();
            acceptInfo = ConnectionManager.createAcceptInfo( ...
                clientPeerInstance, ...
                constants.defaultCommunicationGroupName, ...
                minport, maxport, constants.connectionBacklog, ...
                constants.matlabpoolPollInterval, ...
                constants.matlabpoolTimeout, ...
                constants.handshakeConnectAttempts, ...
                useReliableConnections);
            connMgr = ConnectionManager.buildClientConnManager( ...
                hostname, clientPeerInstance, acceptInfo);
        end
    end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function strategy = iResolveConnectionTopology( useOptimizedBroadcast )
    
import com.mathworks.toolbox.distcomp.pmode.matlabpool.sessions.ConnectionTopology;

% Final word goes to pctconfig.
[~, undoc] = pctconfig();
featureValue = undoc.optimizedpoolbroadcast;
if islogical(featureValue) && isscalar(featureValue)
    useOptimizedBroadcast = featureValue;
end

if useOptimizedBroadcast
    strategy = ConnectionTopology.WORKERS_PROXIED_BY_LAB_ONE;
else
    strategy = ConnectionTopology.ALL_WORKERS_CONNECT_TO_CLIENT;
end
end
