// **********************************************************************
//
// Copyright (c) 2003-2007 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, boolean threadPerConnection, 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();
            }

            //
            // TODO: Remove when we no longer support SSL for JDK 1.4.
            //
            for(int i = 0; i < endpoints.length; i++)
            {
                if(!threadPerConnection && endpoints[i].requiresThreadPerConnection())
                {
                    Ice.FeatureNotSupportedException ex = new Ice.FeatureNotSupportedException();
                    ex.unsupportedFeature = "endpoint requires thread-per-connection:\n" + endpoints[i].toString();
                    throw ex;
                }
            }

            //
            // 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. The connection must also match the
                        // requested thread-per-connection setting.
                        //
                        if(!connection.isDestroyed() && connection.threadPerConnection() == threadPerConnection)
                        {
                            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. The connection must also match the
                            // requested thread-per-connection setting.
                            //
                            if(!connection.isDestroyed() && connection.threadPerConnection() == threadPerConnection)
                            {
                                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, threadPerConnection);
                connection.start();
                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.ObjectAdapter adapter = routerInfo.getAdapter();
        DefaultsAndOverrides defaultsAndOverrides = _instance.defaultsAndOverrides();
        EndpointI[] endpoints = routerInfo.getClientEndpoints();
        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();
}
