// **********************************************************************
//
// Copyright (c) 2003-2006 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************

package IceInternal;

public final class OutgoingConnectionFactory
{
    public synchronized void
    destroy()
    {
        if(_destroyed)
        {
            return;
        }

        java.util.Iterator p = _connections.values().iterator();
        while(p.hasNext())
        {
	    java.util.LinkedList connectionList = (java.util.LinkedList)p.next();
		
	    java.util.Iterator q = connectionList.iterator();
	    while(q.hasNext())
	    {
		Ice.ConnectionI connection = (Ice.ConnectionI)q.next();
		connection.destroy(Ice.ConnectionI.CommunicatorDestroyed);
	    }
	}

        _destroyed = true;
        notifyAll();
    }

    public void
    waitUntilFinished()
    {
	java.util.HashMap connections;

	synchronized(this)
	{
	    //
	    // First we wait until the factory is destroyed. We also
	    // wait until there are no pending connections
	    // anymore. Only then we can be sure the _connections
	    // contains all connections.
	    //
	    while(!_destroyed || !_pending.isEmpty())
	    {
		try
		{
		    wait();
		}
		catch(InterruptedException ex)
		{
		}
	    }

	    //
	    // We want to wait until all connections are finished outside the
	    // thread synchronization.
	    //
	    // For consistency with C#, we set _connections to null rather than to a
	    // new empty list so that our finalizer does not try to invoke any
	    // methods on member objects.
	    //
	    connections = _connections;
	    _connections = null;
	}
	
	//
	// Now we wait for until the destruction of each connection is
	// finished.
	//
        java.util.Iterator p = connections.values().iterator();
        while(p.hasNext())
        {
	    java.util.LinkedList connectionList = (java.util.LinkedList)p.next();
		
	    java.util.Iterator q = connectionList.iterator();
	    while(q.hasNext())
	    {
		Ice.ConnectionI connection = (Ice.ConnectionI)q.next();
		connection.waitUntilFinished();
	    }
        }
    }

    public Ice.ConnectionI
    create(EndpointI[] endpts, boolean hasMore, Ice.BooleanHolder compress)
    {
	assert(endpts.length > 0);
	EndpointI[] endpoints = new EndpointI[endpts.length];
	System.arraycopy(endpts, 0, endpoints, 0, endpts.length);

        DefaultsAndOverrides defaultsAndOverrides = _instance.defaultsAndOverrides();

	synchronized(this)
	{
	    if(_destroyed)
	    {
		throw new Ice.CommunicatorDestroyedException();
	    }

	    //
	    // Reap connections for which destruction has completed.
	    //
	    java.util.Iterator p = _connections.values().iterator();
	    while(p.hasNext())
	    {
		java.util.LinkedList connectionList = (java.util.LinkedList)p.next();
		
		java.util.Iterator q = connectionList.iterator();
		while(q.hasNext())
		{
		    Ice.ConnectionI con = (Ice.ConnectionI)q.next();
		    if(con.isFinished())
		    {
			q.remove();
		    }
		}

		if(connectionList.isEmpty())
		{
		    p.remove();
		}
	    }

	    //
	    // Modify endpoints with overrides.
	    //
	    for(int i = 0; i < endpoints.length; i++)
	    {
		if(defaultsAndOverrides.overrideTimeout)
		{
		    endpoints[i] = endpoints[i].timeout(defaultsAndOverrides.overrideTimeoutValue);
		}

		//
		// The Connection object does not take the compression flag of
		// endpoints into account, but instead gets the information
		// about whether messages should be compressed or not from
		// other sources. In order to allow connection sharing for
		// endpoints that differ in the value of the compression flag
		// only, we always set the compression flag to false here in
		// this connection factory.
		//
		endpoints[i] = endpoints[i].compress(false);
	    }

	    //
	    // Search for existing connections.
	    //
	    for(int i = 0; i < endpoints.length; i++)
	    {
		java.util.LinkedList connectionList = (java.util.LinkedList)_connections.get(endpoints[i]);
		if(connectionList != null)
		{
		    java.util.Iterator q = connectionList.iterator();
			
		    while(q.hasNext())
		    {
			Ice.ConnectionI connection = (Ice.ConnectionI)q.next();
			
			//
			// Don't return connections for which destruction has
			// been initiated.
			//
			if(!connection.isDestroyed())
			{
			    if(defaultsAndOverrides.overrideCompress)
			    {
				compress.value = defaultsAndOverrides.overrideCompressValue;
			    }
			    else
			    {
				compress.value = endpts[i].compress();
			    }

			    return connection;
			}
		    }
		}
	    }

	    //
	    // If some other thread is currently trying to establish a
	    // connection to any of our endpoints, we wait until this
	    // thread is finished.
	    //
	    boolean searchAgain = false;
	    while(!_destroyed)
	    {
		int i;
		for(i = 0; i < endpoints.length; i++)
		{
		    if(_pending.contains(endpoints[i]))
		    {
			break;
		    }
		}
		
		if(i == endpoints.length)
		{
		    break;
		}
		
		searchAgain = true;

		try
		{
		    wait();
		}
		catch(InterruptedException ex)
		{
		}
	    }

	    if(_destroyed)
	    {
		throw new Ice.CommunicatorDestroyedException();
	    }

	    //
	    // Search for existing connections again if we waited
	    // above, as new connections might have been added in the
	    // meantime.
	    //
	    if(searchAgain)
	    {
		for(int i = 0; i < endpoints.length; i++)
		{
		    java.util.LinkedList connectionList = (java.util.LinkedList)_connections.get(endpoints[i]);
		    if(connectionList != null)
		    {
			java.util.Iterator q = connectionList.iterator();
			
			while(q.hasNext())
			{
			    Ice.ConnectionI connection = (Ice.ConnectionI)q.next();
			    
			    //
			    // Don't return connections for which destruction has
			    // been initiated.
			    //
			    if(!connection.isDestroyed())
			    {
				if(defaultsAndOverrides.overrideCompress)
				{
				    compress.value = defaultsAndOverrides.overrideCompressValue;
				}
				else
				{
				    compress.value = endpts[i].compress();
				}

				return connection;
			    }
			}
		    }
		}
	    }

	    //
	    // No connection to any of our endpoints exists yet, so we
	    // will try to create one. To avoid that other threads try
	    // to create connections to the same endpoints, we add our
	    // endpoints to _pending.
	    //
	    for(int i = 0; i < endpoints.length; i++)
	    {
		_pending.add(endpoints[i]);
	    }
	}

	Ice.ConnectionI connection = null;
	Ice.LocalException exception = null;

	for(int i = 0; i < endpoints.length; i++)
	{
	    EndpointI endpoint = endpoints[i];
	    
	    try
	    {
		Transceiver transceiver = endpoint.clientTransceiver();
		if(transceiver == null)
		{
		    Connector connector = endpoint.connector();
		    assert(connector != null);

		    int timeout;
		    if(defaultsAndOverrides.overrideConnectTimeout)
		    {
			timeout = defaultsAndOverrides.overrideConnectTimeoutValue;
		    }
		    // It is not necessary to check for overrideTimeout,
		    // the endpoint has already been modified with this
		    // override, if set.
		    else
		    {
			timeout = endpoint.timeout();
		    }

		    transceiver = connector.connect(timeout);
		    assert(transceiver != null);
		}
		connection = new Ice.ConnectionI(_instance, transceiver, endpoint, null);
		connection.validate();

		if(defaultsAndOverrides.overrideCompress)
		{
		    compress.value = defaultsAndOverrides.overrideCompressValue;
		}
		else
		{
		    compress.value = endpts[i].compress();
		}
		break;
	    }
	    catch(Ice.LocalException ex)
	    {
		exception = ex;

		//
		// If a connection object was constructed, then validate()
		// must have raised the exception.
		//
		if(connection != null)
		{
		    connection.waitUntilFinished(); // We must call waitUntilFinished() for cleanup.
		    connection = null;
		}
	    }
	    
	    TraceLevels traceLevels = _instance.traceLevels();
	    if(traceLevels.retry >= 2)
	    {
		StringBuffer s = new StringBuffer();
		s.append("connection to endpoint failed");
		if(hasMore || i < endpoints.length - 1)
		{
		    s.append(", trying next endpoint\n");
		}
		else
		{
		    s.append(" and no more endpoints to try\n");
		}
		s.append(exception.toString());
		_instance.initializationData().logger.trace(traceLevels.retryCat, s.toString());
	    }
	}
	
	synchronized(this)
	{
	    //
	    // Signal other threads that we are done with trying to
	    // establish connections to our endpoints.
	    //
	    for(int i = 0; i < endpoints.length; i++)
	    {
		_pending.remove(endpoints[i]);
	    }
	    notifyAll();
	    
	    if(connection == null)
	    {
		assert(exception != null);
		throw exception;
	    }
	    else
	    {
		java.util.LinkedList connectionList = (java.util.LinkedList)_connections.get(connection.endpoint());
		if(connectionList == null)
		{
		    connectionList = new java.util.LinkedList();
		    _connections.put(connection.endpoint(), connectionList);
		}
		connectionList.add(connection);

		if(_destroyed)
		{
		    connection.destroy(Ice.ConnectionI.CommunicatorDestroyed);
		    throw new Ice.CommunicatorDestroyedException();
		}
		else
		{
		    connection.activate();
		}
	    }
	}
	
	assert(connection != null);
        return connection;
    }

    public synchronized void
    setRouterInfo(IceInternal.RouterInfo routerInfo)
    {
        if(_destroyed)
        {
            throw new Ice.CommunicatorDestroyedException();
        }

        assert(routerInfo != null);

	//
	// Search for connections to the router's client proxy
	// endpoints, and update the object adapter for such
	// connections, so that callbacks from the router can be
	// received over such connections.
	//
	Ice.ObjectPrx proxy = routerInfo.getClientProxy();
	Ice.ObjectAdapter adapter = routerInfo.getAdapter();
	DefaultsAndOverrides defaultsAndOverrides = _instance.defaultsAndOverrides();
	EndpointI[] endpoints = ((Ice.ObjectPrxHelperBase)proxy).__reference().getEndpoints();
	for(int i = 0; i < endpoints.length; i++)
	{
	    EndpointI endpoint = endpoints[i];

	    //
	    // Modify endpoints with overrides.
	    //
	    if(defaultsAndOverrides.overrideTimeout)
	    {
		endpoint = endpoint.timeout(defaultsAndOverrides.overrideTimeoutValue);
	    }

	    //
	    // The Connection object does not take the compression flag of
	    // endpoints into account, but instead gets the information
	    // about whether messages should be compressed or not from
	    // other sources. In order to allow connection sharing for
	    // endpoints that differ in the value of the compression flag
	    // only, we always set the compression flag to false here in
	    // this connection factory.
	    //
	    endpoint = endpoint.compress(false);

	    java.util.LinkedList connectionList = (java.util.LinkedList)_connections.get(endpoints[i]);
	    if(connectionList != null)
	    {
		java.util.Iterator p = connectionList.iterator();

		while(p.hasNext())
		{
		    Ice.ConnectionI connection = (Ice.ConnectionI)p.next();
		    try
		    {
			connection.setAdapter(adapter);
		    }
		    catch(Ice.LocalException ex)
		    {
			//
			// Ignore, the connection is being closed or closed.
			//
		    }
		}
	    }
	}
    }

    public synchronized void
    removeAdapter(Ice.ObjectAdapter adapter)
    {
        if(_destroyed)
        {
            return;
        }

        java.util.Iterator p = _connections.values().iterator();
        while(p.hasNext())
        {
	    java.util.LinkedList connectionList = (java.util.LinkedList)p.next();
		
	    java.util.Iterator q = connectionList.iterator();
	    while(q.hasNext())
	    {
		Ice.ConnectionI connection = (Ice.ConnectionI)q.next();
		if(connection.getAdapter() == adapter)
		{
		    try
		    {
			connection.setAdapter(null);
		    }
		    catch(Ice.LocalException ex)
		    {
			//
			// Ignore, the connection is being closed or closed.
			//
		    }
		}
	    }
	}
    }

    public void
    flushBatchRequests()
    {
	java.util.LinkedList c = new java.util.LinkedList();

        synchronized(this)
	{
	    java.util.Iterator p = _connections.values().iterator();
	    while(p.hasNext())
	    {
		java.util.LinkedList connectionList = (java.util.LinkedList)p.next();
		java.util.Iterator q = connectionList.iterator();
		while(q.hasNext())
		{
		    c.add(q.next());
		}
	    }
	}

	java.util.Iterator p = c.iterator();
	while(p.hasNext())
	{
	    Ice.ConnectionI conn = (Ice.ConnectionI)p.next();
	    try
	    {
		conn.flushBatchRequests();
	    }
	    catch(Ice.LocalException ex)
	    {
		// Ignore.
	    }
	}
    }

    //
    // Only for use by Instance.
    //
    OutgoingConnectionFactory(Instance instance)
    {
        _instance = instance;
	_destroyed = false;
    }

    protected synchronized void
    finalize()
        throws Throwable
    {
	IceUtil.Assert.FinalizerAssert(_destroyed);
	IceUtil.Assert.FinalizerAssert(_connections == null);

        super.finalize();
    }

    private final Instance _instance;
    private boolean _destroyed;
    private java.util.HashMap _connections = new java.util.HashMap();
    private java.util.HashSet _pending = new java.util.HashSet();
}
