// **********************************************************************
//
// 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 Outgoing
{
    public
    Outgoing(Ice.ConnectionI connection, Reference ref, String operation, Ice.OperationMode mode,
             java.util.Map context, boolean compress)
        throws LocalExceptionWrapper
    {
        _connection = connection;
        _reference = ref;
        _state = StateUnsent;
        _is = new BasicStream(ref.getInstance());
        _os = new BasicStream(ref.getInstance());
        _compress = compress;

        writeHeader(operation, mode, context);
    }

    //
    // These functions allow this object to be reused, rather than reallocated.
    //
    public void
    reset(Reference ref, String operation, Ice.OperationMode mode, java.util.Map context, boolean compress)
        throws LocalExceptionWrapper
    {
        _reference = ref;
        _state = StateUnsent;
        _exception = null;
        _compress = compress;

        writeHeader(operation, mode, context);
    }

    public void
    reclaim()
    {
        _is.reset();
        _os.reset();
    }

    // Returns true if ok, false if user exception.
    public boolean
    invoke()
        throws LocalExceptionWrapper
    {
        assert(_state == StateUnsent);

        _os.endWriteEncaps();

        switch(_reference.getMode())
        {
            case Reference.ModeTwoway:
            {
                //
                // We let all exceptions raised by sending directly
                // propagate to the caller, because they can be
                // retried without violating "at-most-once". In case
                // of such exceptions, the connection object does not
                // call back on this object, so we don't need to lock
                // the mutex, keep track of state, or save exceptions.
                //
                _connection.sendRequest(_os, this, _compress);

                //
                // Wait until the request has completed, or until the
                // request times out.
                //

                boolean timedOut = false;

                synchronized(this)
                {
                    //
                    // It's possible that the request has already
                    // completed, due to a regular response, or because of
                    // an exception. So we only change the state to "in
                    // progress" if it is still "unsent".
                    //
                    if(_state == StateUnsent)
                    {
                        _state = StateInProgress;
                    }

                    int timeout = _connection.timeout();
                    while(_state == StateInProgress && !timedOut)
                    {
                        try
                        {
                            if(timeout >= 0)
                            {
                                wait(timeout);
                                
                                if(_state == StateInProgress)
                                {
                                    timedOut = true;
                                }
                            }
                            else
                            {
                                wait();
                            }
                        }
                        catch(InterruptedException ex)
                        {
                        }
                    }
                }
                
                if(timedOut)
                {
                    //
                    // Must be called outside the synchronization of
                    // this object
                    //
                    _connection.exception(new Ice.TimeoutException());

                    //
                    // We must wait until the exception set above has
                    // propagated to this Outgoing object.
                    //
                    synchronized(this)
                    {
                        while(_state == StateInProgress)
                        {
                            try
                            {
                                wait();
                            }
                            catch(InterruptedException ex)
                            {
                            }
                        }
                    }
                }

                if(_exception != null)
                {
                    _exception.fillInStackTrace();

                    //      
                    // A CloseConnectionException indicates graceful
                    // server shutdown, and is therefore always repeatable
                    // without violating "at-most-once". That's because by
                    // sending a close connection message, the server
                    // guarantees that all outstanding requests can safely
                    // be repeated.
                    //
                    // An ObjectNotExistException can always be retried as
                    // well without violating "at-most-once".
                    //
                    if(_exception instanceof Ice.CloseConnectionException || 
                       _exception instanceof Ice.ObjectNotExistException)
                    {
                        throw _exception;
                    }

                    //
                    // Throw the exception wrapped in a LocalExceptionWrapper, to
                    // indicate that the request cannot be resent without
                    // potentially violating the "at-most-once" principle.
                    //
                    throw new LocalExceptionWrapper(_exception, false);
                }

                if(_state == StateUserException)
                {
                    return false;
                }
                
                assert(_state == StateOK);
                break;
            }

            case Reference.ModeOneway:
            case Reference.ModeDatagram:
            {
                //
                // For oneway and datagram requests, the connection object
                // never calls back on this object. Therefore we don't
                // need to lock the mutex or save exceptions. We simply
                // let all exceptions from sending propagate to the
                // caller, because such exceptions can be retried without
                // violating "at-most-once".
                //
                _state = StateInProgress;
                _connection.sendRequest(_os, null, _compress);
                break;
            }

            case Reference.ModeBatchOneway:
            case Reference.ModeBatchDatagram:
            {
                //
                // For batch oneways and datagrams, the same rules as for
                // regular oneways and datagrams (see comment above)
                // apply.
                //
                _state = StateInProgress;
                _connection.finishBatchRequest(_os, _compress);
                break;
            }
        }

        return true;
    }

    public void
    abort(Ice.LocalException ex)
        throws LocalExceptionWrapper
    {
        assert(_state == StateUnsent);

        //
        // If we didn't finish a batch oneway or datagram request, we
        // must notify the connection about that we give up ownership
        // of the batch stream.
        //
        int mode = _reference.getMode();
        if(mode == Reference.ModeBatchOneway || mode == Reference.ModeBatchDatagram)
        {
            _connection.abortBatchRequest();

            //
            // If we abort a batch requests, we cannot retry, because
            // not only the batch request that caused the problem will
            // be aborted, but all other requests in the batch as
            // well.
            //
            throw new LocalExceptionWrapper(ex, false);
        }

        throw ex;
    }

    public synchronized void
    finished(BasicStream is)
    {
        assert(_reference.getMode() == Reference.ModeTwoway); // Can only be called for twoways.
        
        assert(_state <= StateInProgress);
        
        _is.swap(is);
        int status = (int)_is.readByte();
        
        switch(status)
        {
            case DispatchStatus._DispatchOK:
            {
                //
                // Input and output parameters are always sent in an
                // encapsulation, which makes it possible to forward
                // oneway requests as blobs.
                //
                _is.startReadEncaps();
                _state = StateOK; // The state must be set last, in case there is an exception.
                break;
            }
            
            case DispatchStatus._DispatchUserException:
            {
                //
                // Input and output parameters are always sent in an
                // encapsulation, which makes it possible to forward
                // oneway requests as blobs.
                //
                _is.startReadEncaps();
                _state = StateUserException; // The state must be set last, in case there is an exception.
                break;
            }
            
            case DispatchStatus._DispatchObjectNotExist:
            case DispatchStatus._DispatchFacetNotExist:
            case DispatchStatus._DispatchOperationNotExist:
            {
                Ice.RequestFailedException ex = null;
                switch((int)status)
                {
                    case DispatchStatus._DispatchObjectNotExist:
                    {
                        ex = new Ice.ObjectNotExistException();
                        break;
                    }
                    
                    case DispatchStatus._DispatchFacetNotExist:
                    {
                        ex = new Ice.FacetNotExistException();
                        break;
                    }
                    
                    case DispatchStatus._DispatchOperationNotExist:
                    {
                        ex = new Ice.OperationNotExistException();
                        break;
                    }
                    
                    default:
                    {
                        assert(false);
                        break;
                    }
                }
                
                ex.id = new Ice.Identity();
                ex.id.__read(_is);

                //
                // For compatibility with the old FacetPath.
                //
                String[] facetPath = _is.readStringSeq();
                if(facetPath.length > 0)
                {
                    if(facetPath.length > 1)
                    {
                        throw new Ice.MarshalException();
                    }
                    ex.facet = facetPath[0];
                }
                else
                {
                    ex.facet = "";
                }

                ex.operation = _is.readString();
                _exception = ex;

                _state = StateLocalException; // The state must be set last, in case there is an exception.
                break;
            }
            
            case DispatchStatus._DispatchUnknownException:
            case DispatchStatus._DispatchUnknownLocalException:
            case DispatchStatus._DispatchUnknownUserException:
            {
                Ice.UnknownException ex = null;
                switch((int)status)
                {
                    case DispatchStatus._DispatchUnknownException:
                    {
                        ex = new Ice.UnknownException();
                        break;
                    }
                    
                    case DispatchStatus._DispatchUnknownLocalException:
                    {
                        ex = new Ice.UnknownLocalException();
                        break;
                    }
                    
                    case DispatchStatus._DispatchUnknownUserException: 
                    {
                        ex = new Ice.UnknownUserException();
                        break;
                    }
                    
                    default:
                    {
                        assert(false);
                        break;
                    }
                }
                
                ex.unknown = _is.readString();
                _exception = ex;

                _state = StateLocalException; // The state must be set last, in case there is an exception.
                break;
            }
            
            default:
            {
                _exception = new Ice.UnknownReplyStatusException();
                _state = StateLocalException;
                break;
            }
        }

        notify();
    }

    public synchronized void
    finished(Ice.LocalException ex)
    {
        assert(_reference.getMode() == Reference.ModeTwoway); // Can only be called for twoways.
        
        assert(_state <= StateInProgress);

        _state = StateLocalException;
        _exception = ex;
        notify();
    }

    public BasicStream
    is()
    {
        return _is;
    }

    public BasicStream
    os()
    {
        return _os;
    }

    private void
    writeHeader(String operation, Ice.OperationMode mode, java.util.Map context)
        throws LocalExceptionWrapper
    {
        switch(_reference.getMode())
        {
            case Reference.ModeTwoway:
            case Reference.ModeOneway:
            case Reference.ModeDatagram:
            {
                _os.writeBlob(IceInternal.Protocol.requestHdr);
                break;
            }

            case Reference.ModeBatchOneway:
            case Reference.ModeBatchDatagram:
            {
                _connection.prepareBatchRequest(_os);
                break;
            }
        }

        try
        {
            _reference.getIdentity().__write(_os);

            //
            // For compatibility with the old FacetPath.
            //
            String facet = _reference.getFacet();
            if(facet == null || facet.length() == 0)
            {
                _os.writeStringSeq(null);
            }
            else
            {
                String[] facetPath = { facet };
                _os.writeStringSeq(facetPath);
            }

            _os.writeString(operation);

            _os.writeByte((byte)mode.value());

            if(context != null)
            {
                //
                // Explicit context
                //
                Ice.ContextHelper.write(_os, context);
            }
            else
            {
                //
                // Implicit context
                //
                Ice.ImplicitContextI implicitContext =
                    _reference.getInstance().getImplicitContext();

                java.util.Map prxContext = _reference.getContext();
                
                if(implicitContext == null)
                {
                    Ice.ContextHelper.write(_os, prxContext);
                }
                else
                {
                    implicitContext.write(prxContext, _os);
                }
            }

            //
            // Input and output parameters are always sent in an
            // encapsulation, which makes it possible to forward requests as
            // blobs.
            //
            _os.startWriteEncaps();
        }
        catch(Ice.LocalException ex)
        {
            abort(ex);
        }
    }

    private Ice.ConnectionI _connection;
    private Reference _reference;
    private Ice.LocalException _exception;

    private static final int StateUnsent = 0;
    private static final int StateInProgress = 1;
    private static final int StateOK = 2;
    private static final int StateUserException = 3;
    private static final int StateLocalException = 4;
    private int _state;

    private BasicStream _is;
    private BasicStream _os;

    private boolean _compress;

    public Outgoing next; // For use by Ice._ObjectDelM
}
